mirror of
https://github.com/taylanbakircioglu/haproxy-openmanager.git
synced 2026-10-04 04:21:30 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dbb9189f16 |
@@ -2474,6 +2474,7 @@ Developed with ❤️ for the HAProxy community
|
||||
|
||||
## Release Notes
|
||||
|
||||
- **v1.10.2** (2026-08-08) — **Dark mode fixes on Apply Management**: several panels on the Apply Management page were painted with light-mode colour literals, so in dark mode the **Pending Changes** box rendered as a cream panel with light text on it — measured contrast **1.03:1**, effectively unreadable, now **11.50:1**. The same bug affected the added/removed rows in the *View Change* diff (2.21:1 and 2.99:1, now 5.49:1 and 4.01:1), the ACME and pending-version panels, the VIP pending-delete row, and the agent-error recommendation box; all now derive from theme tokens. Separately, **static confirm dialogs came up white in dark mode**: in Ant Design 5 the static `Modal.confirm` / `message` / `notification` APIs render into their own detached root and never see the app's `ConfigProvider`, so they always used the light algorithm. Registering `ConfigProvider.config({ holderRender })` once at the app root fixes **every** static dialog in the application (12 components use them), not only this page. Light mode is byte-identical — each token resolves under the default algorithm to exactly the literal it replaced. Frontend only: no schema, API, environment or agent change.
|
||||
- **v1.10.1** (2026-08-08) — **CSR private key encrypted at rest** (Issue #53): the private key of a **pending** CSR is now Fernet-encrypted in the database instead of stored as PEM. It is the one key in the system worth protecting this way — it sits idle for the entire signing window (days to weeks), is never transmitted to an agent, and is destroyed the moment the signed certificate is imported; `ssl_certificates.private_key_content` and the ACME order keys are unchanged, because agents must receive those in plaintext on every poll. The token replaces the PEM in the **same column**, so there is **no schema change and no `SCHEMA_VERSION` bump** (and therefore no re-seed of the built-in roles). CSRs created before this release keep a raw PEM and are still read transparently, so anything already out for signature imports normally with no data migration. The key derives from `SECRET_KEY` via HKDF with its own info string, independent of the VIP/MFA/DNS keys, and an optional `CSR_ENCRYPTION_KEY` enables independent rotation — rotating `SECRET_KEY` without it makes pending CSR keys unrecoverable, which now fails with an explicit "delete and re-create this CSR" error rather than a misleading key-mismatch. `.env.template` now documents all four per-purpose encryption keys. No API, UI or agent change.
|
||||
- **v1.10.0** (2026-08-07) — **GoDaddy DNS provider for DNS-01** (Issue #35 follow-up): DNS-01 challenges can now be published and cleaned up automatically through **GoDaddy**, alongside the existing Manual and Cloudflare providers, so wildcard and internal-cluster certificates on GoDaddy-hosted zones **renew unattended**. Credentials are a **Production API Key + Secret** pair from `developer.godaddy.com/keys` (a **Personal Access Token** also works — paste it as the Key and leave the Secret blank, which is the forward path as GoDaddy retires `sso-key`); they are **verified against the GoDaddy API before being saved** and **encrypted at rest** (Fernet, the same path as Cloudflare), and are never returned by the API, logged, or written to an order event. GoDaddy's v1 API has **no per-value TXT write** — `PUT` replaces an entire RRset — so add/remove are read-modify-write with sibling values merged back, empty-`data` tombstones filtered out, and `DELETE` used for the last value (`PUT []` is rejected); this is what keeps the **apex + wildcard** case (two TXT values at one `_acme-challenge` name) working, and the record path is hard-gated so it can never collapse onto the zone-wide endpoint that would wipe SPF/DKIM/DMARC. Zone lookup probes the records API rather than the domain listing, so **delegated sub-zones** resolve and small accounts are not falsely rejected. Registry-only addition: one new provider module plus one registry line — no frontend change (the credential form is schema-driven). No schema, API-shape, agent, or rendered-config changes; Manual, Cloudflare and HTTP-01 are unaffected.
|
||||
- **v1.9.0** (2026-08-04) — **CSR creation** (in-app key + CSR generation and signed-certificate import): a new **CSR tab** on the SSL Certificates page generates a private key and Certificate Signing Request server-side (RSA 2048/4096 or ECDSA P-256/P-384; full subject — O/OU/L/ST/C/email — plus DNS SANs with wildcard support), for certificates signed by an **external or corporate CA**. The operator downloads/copies the CSR PEM, has it signed, then imports the signed certificate (+ optional chain): the backend verifies the certificate against the stored key (hard gate), rejects expired certs, warns on SAN drift, and creates a normal SSL certificate entry (source `CSR`) that flows through the standard **PENDING → Apply Management → agent pull** pipeline. The private key **never leaves the server** — no CSR endpoint returns it, and after import the CSR row's key copy is destroyed (the key then lives only on the certificate, like every other key). Additive schema change: one new table `ssl_csrs` (SCHEMA_VERSION 9 → 10, auto-migrated, no existing table altered); key generation runs off the event loop and is rate-limited per user; existing `ssl.*` permissions govern all new endpoints. No agent or rendered-config changes.
|
||||
|
||||
@@ -1,3 +1,25 @@
|
||||
# Upgrade Notes — v1.10.2 (Dark mode fixes on Apply Management)
|
||||
|
||||
**Frontend only. Nothing to do on upgrade.** No schema, no `SCHEMA_VERSION` bump, no API change,
|
||||
no environment variable, zero agent impact. Light mode is byte-identical: every colour swapped in
|
||||
this release resolves, under the default algorithm, to exactly the literal it replaced
|
||||
(`colorWarningBg` → `#fffbe6`, `colorSuccessBg` → `#f6ffed`, `colorErrorBg` → `#fff2f0`, …), so
|
||||
only dark mode changes.
|
||||
|
||||
- **Apply Management panels** were painted with light-mode colour literals, so in dark mode the
|
||||
"Pending Changes" box rendered as a cream panel with light text on it. Measured contrast was
|
||||
**1.03:1** — effectively invisible. It is now **11.50:1**. The same class of bug affected the
|
||||
diff rows in *View Change* (2.21:1 and 2.99:1, now 5.49:1 and 4.01:1), the ACME/pending version
|
||||
panels, the VIP pending-delete row and the agent-error recommendation box.
|
||||
- **Static confirm dialogs came up white in dark mode.** In Ant Design 5 the static
|
||||
`Modal.confirm` / `message` / `notification` APIs render into their own detached root and never
|
||||
see the app's `ConfigProvider`, so they always used the light algorithm. This release registers
|
||||
`ConfigProvider.config({ holderRender })` once at the app root, which fixes **every** static
|
||||
dialog in the app (12 components use them), not just Apply Management.
|
||||
- **Rollback:** downgrade freely. This release changes rendering only.
|
||||
|
||||
---
|
||||
|
||||
# Upgrade Notes — v1.10.1 (CSR private key encrypted at rest)
|
||||
|
||||
**Backward compatible.** Nothing to do on upgrade, and nothing changes for existing clusters,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.10.1",
|
||||
"releaseName": "CSR private key encrypted at rest",
|
||||
"version": "1.10.2",
|
||||
"releaseName": "Dark mode fixes on Apply Management",
|
||||
"releaseDate": "2026-08-08"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "haproxy-openmanager-frontend",
|
||||
"version": "1.10.1",
|
||||
"version": "1.10.2",
|
||||
"description": "HAProxy Load Balancer Management UI",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
|
||||
+23
-5
@@ -484,12 +484,30 @@ function AppContent() {
|
||||
|
||||
function ThemedApp() {
|
||||
const { isDarkMode } = useTheme();
|
||||
const antdTheme = {
|
||||
algorithm: isDarkMode ? theme.darkAlgorithm : theme.defaultAlgorithm,
|
||||
};
|
||||
|
||||
// Ant Design 5: the STATIC message/notification/Modal.confirm APIs render into their own
|
||||
// detached root, so they do not see this ConfigProvider and always fall back to the light
|
||||
// algorithm — a confirm dialog came up white while the app was in dark mode. `holderRender`
|
||||
// wraps that detached root in the same ConfigProvider, which fixes every static call in the
|
||||
// app at once (12 components use Modal.confirm) instead of migrating each one to
|
||||
// App.useApp(). In an effect rather than during render: ConfigProvider.config() mutates
|
||||
// antd module state, and effects still run long before a user can click anything that opens
|
||||
// a static modal. Re-registered on theme change so the toggle takes effect immediately.
|
||||
React.useEffect(() => {
|
||||
ConfigProvider.config({
|
||||
holderRender: (children) => (
|
||||
<ConfigProvider theme={{ algorithm: isDarkMode ? theme.darkAlgorithm : theme.defaultAlgorithm }}>
|
||||
{children}
|
||||
</ConfigProvider>
|
||||
),
|
||||
});
|
||||
}, [isDarkMode]);
|
||||
|
||||
return (
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
algorithm: isDarkMode ? theme.darkAlgorithm : theme.defaultAlgorithm,
|
||||
}}
|
||||
>
|
||||
<ConfigProvider theme={antdTheme}>
|
||||
<AuthProvider>
|
||||
<ClusterProvider>
|
||||
<ProgressProvider>
|
||||
|
||||
@@ -1092,7 +1092,7 @@ const ApplyManagement = () => {
|
||||
style={{
|
||||
marginBottom: 24,
|
||||
borderRadius: 8,
|
||||
border: '1px solid #ffccc7',
|
||||
border: `1px solid ${token.colorErrorBorder}`,
|
||||
boxShadow: '0 2px 8px rgba(255, 77, 79, 0.15)'
|
||||
}}
|
||||
message={
|
||||
@@ -1348,7 +1348,7 @@ const ApplyManagement = () => {
|
||||
HA / VIP Changes ({pendingChanges.vips.length})
|
||||
</Title>
|
||||
{pendingChanges.vips.map(item => (
|
||||
<div key={`vip-${item.id}`} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '8px 12px', marginBottom: 6, border: item.pending_delete ? '1px solid #ffccc7' : '1px solid #f0f0f0', borderRadius: 6, background: item.pending_delete ? '#fff1f0' : undefined }}>
|
||||
<div key={`vip-${item.id}`} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '8px 12px', marginBottom: 6, border: `1px solid ${item.pending_delete ? token.colorErrorBorder : token.colorBorderSecondary}`, borderRadius: 6, background: item.pending_delete ? token.colorErrorBg : undefined }}>
|
||||
<CloudServerOutlined style={{ color: item.pending_delete ? '#cf1322' : '#13c2c2' }} />
|
||||
<span style={{ fontWeight: 500 }}>{item.name}</span>
|
||||
<Tag>{item.virtual_ip}/{item.prefix_length}</Tag>
|
||||
@@ -1374,8 +1374,8 @@ const ApplyManagement = () => {
|
||||
const isEnable = /^cluster-\d+-acme-enable-/.test(v.version_name);
|
||||
return (
|
||||
<div key={v.id} style={{
|
||||
padding: 10, border: '1px dashed #1890ff', borderRadius: 6, marginBottom: 8,
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'space-between', backgroundColor: '#f0f8ff'
|
||||
padding: 10, border: `1px dashed ${token.colorPrimary}`, borderRadius: 6, marginBottom: 8,
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'space-between', backgroundColor: token.colorInfoBg
|
||||
}}>
|
||||
<span style={{ fontFamily: 'monospace' }}>{v.version_name}</span>
|
||||
<span>
|
||||
@@ -1419,7 +1419,7 @@ const ApplyManagement = () => {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
backgroundColor: '#f0f8ff'
|
||||
backgroundColor: token.colorInfoBg
|
||||
}}>
|
||||
<span style={{ fontFamily: 'monospace' }}>{v.version_name}</span>
|
||||
<Tag color="orange">PENDING</Tag>
|
||||
@@ -1443,7 +1443,7 @@ const ApplyManagement = () => {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
backgroundColor: '#f6ffed'
|
||||
backgroundColor: token.colorSuccessBg
|
||||
}}>
|
||||
<span style={{ fontFamily: 'monospace' }}>{v.version_name}</span>
|
||||
<Tag color="orange">PENDING</Tag>
|
||||
@@ -1518,9 +1518,13 @@ const ApplyManagement = () => {
|
||||
</Descriptions>
|
||||
</div>
|
||||
|
||||
{/* Pending Versions Section */}
|
||||
{/* Pending Versions Section.
|
||||
Theme tokens, not the light-mode literals #fffbe6/#ffe58f: in dark mode those
|
||||
produced a cream panel with light text on it, so the version name, timestamp
|
||||
and "View Change" link were unreadable. colorWarningBg/Border track the
|
||||
algorithm, so the "pending" tint survives in both themes. */}
|
||||
{pendingVersions.length > 0 && (
|
||||
<div style={{ marginBottom: 24, background: '#fffbe6', borderRadius: 8, border: '1px solid #ffe58f', padding: '16px 16px 8px' }}>
|
||||
<div style={{ marginBottom: 24, background: token.colorWarningBg, borderRadius: 8, border: `1px solid ${token.colorWarningBorder}`, padding: '16px 16px 8px' }}>
|
||||
<Title level={5} style={{ marginTop: 0 }}>
|
||||
<ClockCircleOutlined style={{ marginRight: 8, color: '#faad14' }} />
|
||||
Pending Changes ({pendingVersions.length})
|
||||
@@ -1765,8 +1769,8 @@ const ApplyManagement = () => {
|
||||
<div>
|
||||
{/* Parsed suggestion */}
|
||||
{agentSync.parsed_error?.suggestion && (
|
||||
<div style={{ marginBottom: 12, padding: '8px 12px', background: '#fff7e6', borderRadius: 4, border: '1px solid #ffd591' }}>
|
||||
<Text strong style={{ color: '#ad4e00' }}>Recommendation: </Text>
|
||||
<div style={{ marginBottom: 12, padding: '8px 12px', background: token.colorWarningBg, borderRadius: 4, border: `1px solid ${token.colorWarningBorder}` }}>
|
||||
<Text strong style={{ color: token.colorWarningText }}>Recommendation: </Text>
|
||||
<Text>{agentSync.parsed_error.suggestion}</Text>
|
||||
</div>
|
||||
)}
|
||||
@@ -1951,13 +1955,17 @@ const ApplyManagement = () => {
|
||||
{diffData.changes && diffData.changes.length > 0 ? (
|
||||
diffData.changes.map((change, index) => (
|
||||
<div key={index} style={{ marginBottom: '4px' }}>
|
||||
{/* Token-based, not the light literals #f6ffed/#fff2f0: those stayed
|
||||
near-white in dark mode, so the added/removed rows glared against
|
||||
the dark diff panel around them. colorSuccessBg/colorErrorBg darken
|
||||
with the algorithm while keeping the green/red semantics. */}
|
||||
{change.type === 'added' && (
|
||||
<div style={{ backgroundColor: '#f6ffed', color: '#52c41a', padding: '2px 8px', borderLeft: '3px solid #52c41a' }}>
|
||||
<div style={{ backgroundColor: token.colorSuccessBg, color: token.colorSuccessText, padding: '2px 8px', borderLeft: `3px solid ${token.colorSuccess}` }}>
|
||||
+ {change.line}
|
||||
</div>
|
||||
)}
|
||||
{change.type === 'removed' && (
|
||||
<div style={{ backgroundColor: '#fff2f0', color: '#ff4d4f', padding: '2px 8px', borderLeft: '3px solid #ff4d4f' }}>
|
||||
<div style={{ backgroundColor: token.colorErrorBg, color: token.colorErrorText, padding: '2px 8px', borderLeft: `3px solid ${token.colorError}` }}>
|
||||
- {change.line}
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user