UI : badge de statut texte (À JOUR, ÉCARTS, BLOQUANT) sur les cartes à la place du liseré coloré

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
cedricandClaude Opus 5.5 committed 2026-10-23 19:49:58 +02:00
1 parent 4980cd4c7f
commit dc1a7ff40e
2 files changed
+15 -16

No files matched your search

+8 -9
View File
@@ -35,7 +35,7 @@
allKinds: 'Tous les types', allKinds: 'Tous les types',
st: { same: 'Conforme', create: 'À créer', update: 'À modifier', delete: 'À supprimer', extra: 'Hors primaire', error: 'Erreur' }, st: { same: 'Conforme', create: 'À créer', update: 'À modifier', delete: 'À supprimer', extra: 'Hors primaire', error: 'Erreur' },
filterDiff: 'Écarts et erreurs', filterAll: 'Tous les objets', filterSame: 'Conformes', filterError: 'Erreurs', filterDiff: 'Écarts et erreurs', filterAll: 'Tous les objets', filterSame: 'Conformes', filterError: 'Erreurs',
reference: 'PRIMAIRE', unreadable: 'illisible', inLine: 'conforme', nodes: '{n} nœud(s)', reference: 'PRIMAIRE', cst: { ok: 'À JOUR', diff: 'ÉCARTS', error: 'BLOQUANT' }, unreadable: 'illisible', inLine: 'conforme', nodes: '{n} nœud(s)',
readAt: 'lu à {t} ({ms} ms)', dryClient: 'Dry-run de la cible', syncClient: 'Répliquer vers la cible', readAt: 'lu à {t} ({ms} ms)', dryClient: 'Dry-run de la cible', syncClient: 'Répliquer vers la cible',
dryObj: 'Dry-run de cet objet', syncObj: 'Répliquer cet objet', dryObj: 'Dry-run de cet objet', syncObj: 'Répliquer cet objet',
refError: 'Primaire illisible', clientError: 'Cible illisible', planError: 'Bloquant', warning: 'Avertissement', refError: 'Primaire illisible', clientError: 'Cible illisible', planError: 'Bloquant', warning: 'Avertissement',
@@ -137,7 +137,7 @@
allKinds: 'All kinds', allKinds: 'All kinds',
st: { same: 'In sync', create: 'Create', update: 'Update', delete: 'Delete', extra: 'Not on primary', error: 'Error' }, st: { same: 'In sync', create: 'Create', update: 'Update', delete: 'Delete', extra: 'Not on primary', error: 'Error' },
filterDiff: 'Differences and errors', filterAll: 'All objects', filterSame: 'In sync', filterError: 'Errors', filterDiff: 'Differences and errors', filterAll: 'All objects', filterSame: 'In sync', filterError: 'Errors',
reference: 'PRIMARY', unreadable: 'unreadable', inLine: 'in sync', nodes: '{n} node(s)', reference: 'PRIMARY', cst: { ok: 'IN SYNC', diff: 'DIFFS', error: 'BLOCKED' }, unreadable: 'unreadable', inLine: 'in sync', nodes: '{n} node(s)',
readAt: 'read at {t} ({ms} ms)', dryClient: 'Target dry-run', syncClient: 'Replicate to target', readAt: 'read at {t} ({ms} ms)', dryClient: 'Target dry-run', syncClient: 'Replicate to target',
dryObj: 'Dry-run this object', syncObj: 'Replicate this object', dryObj: 'Dry-run this object', syncObj: 'Replicate this object',
refError: 'Primary unreadable', clientError: 'Target unreadable', planError: 'Blocking', warning: 'Warning', refError: 'Primary unreadable', clientError: 'Target unreadable', planError: 'Blocking', warning: 'Warning',
@@ -571,8 +571,9 @@
} }
// reference card // reference card
const r = st.reference; const r = st.reference;
strip.appendChild(h('div', { class: 'ccard ref' + (r.error ? ' st-error' : '') }, strip.appendChild(h('div', { class: 'ccard ref' },
h('div', { class: 'c-head' }, h('span', { class: 'c-role', text: t('reference') }), h('span', { class: 'c-name', text: r.name }), h('div', { class: 'c-head' }, h('span', { class: 'c-role', text: t('reference') }),
r.error ? h('span', { class: 'c-role st st-error', text: t('cst.error') }) : null, h('span', { class: 'c-name', text: r.name }),
h('span', { class: 'c-ver', text: r.version ? 'PVE ' + r.version : '' })), h('span', { class: 'c-ver', text: r.version ? 'PVE ' + r.version : '' })),
h('div', { class: 'c-url', text: r.url }), h('div', { class: 'c-url', text: r.url }),
r.error ? h('div', { class: 'c-err', text: tm(r.error), title: r.error }) r.error ? h('div', { class: 'c-err', text: tm(r.error), title: r.error })
@@ -585,17 +586,15 @@
counts.appendChild(h('span', { class: 'cnt error', text: t('unreadable') })); counts.appendChild(h('span', { class: 'cnt error', text: t('unreadable') }));
} else if (!r.error) { } else if (!r.error) {
const n = c.counts || {}; const n = c.counts || {};
let any = false;
for (const [k, sym] of [['create', '+'], ['update', '~'], ['delete', '−'], ['error', '!'], ['extra', '?']]) { for (const [k, sym] of [['create', '+'], ['update', '~'], ['delete', '−'], ['error', '!'], ['extra', '?']]) {
if (n[k]) { any = true; counts.appendChild(h('span', { class: 'cnt ' + k, title: t('st.' + k), text: sym + n[k] })); } if (n[k]) { counts.appendChild(h('span', { class: 'cnt ' + k, title: t('st.' + k), text: sym + n[k] })); }
} }
if (!any) counts.appendChild(h('span', { class: 'cnt ok', text: '✓ ' + t('inLine') }));
} }
strip.appendChild(h('button', { strip.appendChild(h('button', {
type: 'button', role: 'tab', class: 'ccard st-' + cs, 'aria-selected': String(c.name === S.client), type: 'button', role: 'tab', class: 'ccard', 'aria-selected': String(c.name === S.client),
onclick: () => { S.client = c.name; S.open.clear(); writeHash(); renderDiff(); }, onclick: () => { S.client = c.name; S.open.clear(); writeHash(); renderDiff(); },
}, },
h('div', { class: 'c-head' }, h('span', { class: 'c-name', text: c.name }), h('span', { class: 'c-ver', text: c.version ? 'PVE ' + c.version : '' })), h('div', { class: 'c-head' }, h('span', { class: 'c-role st st-' + cs, text: t('cst.' + cs) }), h('span', { class: 'c-name', text: c.name }), h('span', { class: 'c-ver', text: c.version ? 'PVE ' + c.version : '' })),
h('div', { class: 'c-url', text: c.url }), h('div', { class: 'c-url', text: c.url }),
counts, counts,
c.error ? h('div', { class: 'c-err', text: tm(c.error), title: c.error }) : null)); c.error ? h('div', { class: 'c-err', text: tm(c.error), title: c.error }) : null));
+7 -7
View File
@@ -254,21 +254,21 @@ body.busy .progress::after {
position: relative; display: flex; flex-direction: column; gap: 4px; min-width: 0; position: relative; display: flex; flex-direction: column; gap: 4px; min-width: 0;
padding: 10px 12px 10px 14px; text-align: left; padding: 10px 12px 10px 14px; text-align: left;
background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius); background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius);
/* status edge drawn inside the border: follows the rounded corners */
box-shadow: inset 3px 0 0 var(--st, var(--border));
} }
button.ccard:hover { background: var(--row-hover); } button.ccard:hover { background: var(--row-hover); }
.ccard[aria-selected="true"] { border-color: var(--accent); box-shadow: inset 3px 0 0 var(--st, var(--accent)), 0 0 0 1px var(--accent); } .ccard[aria-selected="true"] { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.ccard.ref { --st: var(--accent); background: color-mix(in srgb, var(--accent) 5%, var(--panel)); cursor: default; } .ccard.ref { background: color-mix(in srgb, var(--accent) 5%, var(--panel)); cursor: default; }
.ccard.st-ok { --st: var(--ok); }
.ccard.st-diff { --st: var(--warn); }
.ccard.st-error { --st: var(--err); }
.ccard .c-head { display: flex; align-items: center; gap: 8px; min-width: 0; } .ccard .c-head { display: flex; align-items: center; gap: 8px; min-width: 0; }
.ccard .c-name { font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .ccard .c-name { font-weight: 650; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ccard .c-role { .ccard .c-role {
flex: none; padding: 0 5px; border-radius: 5px; font-size: 10px; font-weight: 700; letter-spacing: .04em; flex: none; padding: 0 5px; border-radius: 5px; font-size: 10px; font-weight: 700; letter-spacing: .04em;
color: var(--accent); background: var(--accent-soft); color: var(--accent); background: var(--accent-soft);
} }
/* status badge, same shape as the role badge */
.ccard .c-role.st { --c: var(--muted); color: var(--c); background: color-mix(in srgb, var(--c) 14%, transparent); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--c) 40%, transparent); }
.ccard .c-role.st-ok { --c: var(--ok); }
.ccard .c-role.st-diff { --c: var(--warn); }
.ccard .c-role.st-error { --c: var(--err); }
.ccard .c-ver { margin-left: auto; flex: none; font: 11.5px var(--mono); color: var(--muted); } .ccard .c-ver { margin-left: auto; flex: none; font: 11.5px var(--mono); color: var(--muted); }
.ccard .c-url { font: 11.5px var(--mono); color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .ccard .c-url { font: 11.5px var(--mono); color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ccard .c-counts { display: flex; flex-wrap: wrap; gap: 4px; min-height: 20px; align-items: center; } .ccard .c-counts { display: flex; flex-wrap: wrap; gap: 4px; min-height: 20px; align-items: center; }