@chisquare-tech/origine-ngx-apps-services 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -78,6 +78,10 @@ class ServicesApi {
|
|
|
78
78
|
deleteBackup(project, id, name) {
|
|
79
79
|
return this.api.delete(this.i(project, id, 'backup/' + enc(name)));
|
|
80
80
|
}
|
|
81
|
+
/** A link to a RabbitMQ instance's management UI, for the caller only (read-only for Developers). */
|
|
82
|
+
management(project, id) {
|
|
83
|
+
return this.api.post(this.i(project, id, 'management'), {}).pipe(map((r) => data(r)));
|
|
84
|
+
}
|
|
81
85
|
restore(project, id, body) {
|
|
82
86
|
return this.api.post(this.i(project, id, 'restore'), body).pipe(map((r) => data(r)));
|
|
83
87
|
}
|
|
@@ -281,6 +285,12 @@ const DAYS = [
|
|
|
281
285
|
{ id: 'sun', label: 'Sunday' }, { id: 'mon', label: 'Monday' }, { id: 'tue', label: 'Tuesday' }, { id: 'wed', label: 'Wednesday' },
|
|
282
286
|
{ id: 'thu', label: 'Thursday' }, { id: 'fri', label: 'Friday' }, { id: 'sat', label: 'Saturday' },
|
|
283
287
|
];
|
|
288
|
+
/** While the browser tab is hidden, the page's polls slow to once a minute (the cell is read for each). */
|
|
289
|
+
const HIDDEN_POLL_MS = 60_000;
|
|
290
|
+
/** The delay before the next poll: `visibleMs` while the page is seen, once a minute while it is hidden. */
|
|
291
|
+
function pollDelay(visibleMs) {
|
|
292
|
+
return typeof document !== 'undefined' && document.visibilityState === 'hidden' ? Math.max(visibleMs, HIDDEN_POLL_MS) : visibleMs;
|
|
293
|
+
}
|
|
284
294
|
|
|
285
295
|
// /{org}/{project}/services (prototype "services_list"): the project's instances with their state,
|
|
286
296
|
// what they are attached to, their backups and price, and the organization's quota line (SVC-01, 26).
|
|
@@ -396,7 +406,7 @@ class ServicesListPage {
|
|
|
396
406
|
</div>
|
|
397
407
|
<div class="o-g2">
|
|
398
408
|
<a class="o-card o-cb o-flex" [routerLink]="host.link('services/new')" [queryParams]="{ engine: 'nats' }" data-testid="svc-new-nats"><span class="o-sq" style="background:var(--o-ok)"></span><div class="o-col" style="gap:2px"><b>NATS with JetStream</b><span class="o-faint o-sm">Streams, consumers, KV buckets · 3-node cluster</span></div><span class="o-r o-faint o-sm">Create →</span></a>
|
|
399
|
-
<a class="o-card o-cb o-flex" [routerLink]="host.link('services/new')" [queryParams]="{ engine: 'rabbitmq' }" data-testid="svc-new-rabbitmq"><span class="o-sq" style="background:var(--o-run)"></span><div class="o-col" style="gap:2px"><b>RabbitMQ</b><span class="o-faint o-sm">Quorum queues ·
|
|
409
|
+
<a class="o-card o-cb o-flex" [routerLink]="host.link('services/new')" [queryParams]="{ engine: 'rabbitmq' }" data-testid="svc-new-rabbitmq"><span class="o-sq" style="background:var(--o-run)"></span><div class="o-col" style="gap:2px"><b>RabbitMQ</b><span class="o-faint o-sm">Quorum queues · management UI through the platform</span></div><span class="o-r o-faint o-sm">Create →</span></a>
|
|
400
410
|
</div>
|
|
401
411
|
<span class="o-faint o-sm">Lifecycle states: {{ lifecycle }}. Open-source engines only.</span>
|
|
402
412
|
</div>
|
|
@@ -446,7 +456,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
446
456
|
</div>
|
|
447
457
|
<div class="o-g2">
|
|
448
458
|
<a class="o-card o-cb o-flex" [routerLink]="host.link('services/new')" [queryParams]="{ engine: 'nats' }" data-testid="svc-new-nats"><span class="o-sq" style="background:var(--o-ok)"></span><div class="o-col" style="gap:2px"><b>NATS with JetStream</b><span class="o-faint o-sm">Streams, consumers, KV buckets · 3-node cluster</span></div><span class="o-r o-faint o-sm">Create →</span></a>
|
|
449
|
-
<a class="o-card o-cb o-flex" [routerLink]="host.link('services/new')" [queryParams]="{ engine: 'rabbitmq' }" data-testid="svc-new-rabbitmq"><span class="o-sq" style="background:var(--o-run)"></span><div class="o-col" style="gap:2px"><b>RabbitMQ</b><span class="o-faint o-sm">Quorum queues ·
|
|
459
|
+
<a class="o-card o-cb o-flex" [routerLink]="host.link('services/new')" [queryParams]="{ engine: 'rabbitmq' }" data-testid="svc-new-rabbitmq"><span class="o-sq" style="background:var(--o-run)"></span><div class="o-col" style="gap:2px"><b>RabbitMQ</b><span class="o-faint o-sm">Quorum queues · management UI through the platform</span></div><span class="o-r o-faint o-sm">Create →</span></a>
|
|
450
460
|
</div>
|
|
451
461
|
<span class="o-faint o-sm">Lifecycle states: {{ lifecycle }}. Open-source engines only.</span>
|
|
452
462
|
</div>
|
|
@@ -490,7 +500,9 @@ class ServicesNewPage {
|
|
|
490
500
|
return '3-node cluster';
|
|
491
501
|
if (e?.ha === 'standby')
|
|
492
502
|
return e.id === 'mysql' ? 'Standby with automatic failover (3 nodes: MOCO runs 1, 3 or 5)' : 'Standby node with automatic failover';
|
|
493
|
-
|
|
503
|
+
if (e?.ha === 'replica')
|
|
504
|
+
return 'Replica with automatic failover (three Sentinels watch the primary)';
|
|
505
|
+
return `${e?.name ?? 'This engine'} runs one node`;
|
|
494
506
|
}, ...(ngDevMode ? [{ debugName: "haLabel" }] : /* istanbul ignore next */ []));
|
|
495
507
|
this.keep = signal(7, ...(ngDevMode ? [{ debugName: "keep" }] : /* istanbul ignore next */ []));
|
|
496
508
|
this.busy = signal(false, ...(ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []));
|
|
@@ -1288,7 +1300,7 @@ class ServiceLogsTab {
|
|
|
1288
1300
|
this.lines.set(l.lines ?? []);
|
|
1289
1301
|
this.note.set(l.message ?? '');
|
|
1290
1302
|
this.loaded.set(true);
|
|
1291
|
-
this.timer = setTimeout(() => this.load(p, id, k), 10_000);
|
|
1303
|
+
this.timer = setTimeout(() => this.load(p, id, k), pollDelay(10_000));
|
|
1292
1304
|
},
|
|
1293
1305
|
error: (e) => {
|
|
1294
1306
|
this.loaded.set(true);
|
|
@@ -1595,7 +1607,7 @@ class ServicePage {
|
|
|
1595
1607
|
this.conn = computed(() => this.revealed()?.connection ?? this.d()?.connection ?? null, ...(ngDevMode ? [{ debugName: "conn" }] : /* istanbul ignore next */ []));
|
|
1596
1608
|
this.snippetKeys = computed(() => {
|
|
1597
1609
|
const s = this.conn()?.snippets ?? {};
|
|
1598
|
-
return ['uri', 'psql', 'mysql', 'valkey-cli', 'nats', 'node', 'cli'].filter((k) => k in s);
|
|
1610
|
+
return ['uri', 'psql', 'mysql', 'valkey-cli', 'sentinel', 'nats', 'node', 'cli'].filter((k) => k in s);
|
|
1599
1611
|
}, ...(ngDevMode ? [{ debugName: "snippetKeys" }] : /* istanbul ignore next */ []));
|
|
1600
1612
|
this.backups = computed(() => [...(this.d()?.backups ?? [])].sort((a, b) => (b.startedAt ?? '9').localeCompare(a.startedAt ?? '9')), ...(ngDevMode ? [{ debugName: "backups" }] : /* istanbul ignore next */ []));
|
|
1601
1613
|
this.attEnvs = computed(() => {
|
|
@@ -1688,7 +1700,19 @@ class ServicePage {
|
|
|
1688
1700
|
this.api.logs(this.key(), d, 'slow', 20).pipe(catchError(() => of(null))).subscribe((l) => this.slowLines.set(l?.lines ?? []));
|
|
1689
1701
|
});
|
|
1690
1702
|
});
|
|
1703
|
+
// Hidden, the polls slow to once a minute; seen again, the figures are read at once.
|
|
1704
|
+
const seen = () => {
|
|
1705
|
+
if (document.visibilityState !== 'visible' || !this.d())
|
|
1706
|
+
return;
|
|
1707
|
+
this.load();
|
|
1708
|
+
if (this.tab() !== 'overview' && this.tab() !== 'metrics')
|
|
1709
|
+
return;
|
|
1710
|
+
clearTimeout(this.metricsTimer);
|
|
1711
|
+
this.pollMetrics();
|
|
1712
|
+
};
|
|
1713
|
+
document.addEventListener('visibilitychange', seen);
|
|
1691
1714
|
inject(DestroyRef).onDestroy(() => {
|
|
1715
|
+
document.removeEventListener('visibilitychange', seen);
|
|
1692
1716
|
params.unsubscribe();
|
|
1693
1717
|
query.unsubscribe();
|
|
1694
1718
|
clearTimeout(this.loadTimer);
|
|
@@ -1729,7 +1753,7 @@ class ServicePage {
|
|
|
1729
1753
|
this.pitr.set(toLocalInput(new Date().toISOString()));
|
|
1730
1754
|
}
|
|
1731
1755
|
const settling = moving(d.state) || d.readyNodes < d.nodes || (d.dumps ?? []).some((x) => x.phase !== 'completed' && x.phase !== 'failed') || !!this.openRotation() || d.attachments.some((a) => a.state === 'pending') || (d.backups ?? []).some((b) => b.phase === 'running' || b.phase === 'pending');
|
|
1732
|
-
this.loadTimer = setTimeout(() => this.load(), settling ? 4000 : 30_000);
|
|
1756
|
+
this.loadTimer = setTimeout(() => this.load(), pollDelay(settling ? 4000 : 30_000));
|
|
1733
1757
|
},
|
|
1734
1758
|
error: (e) => {
|
|
1735
1759
|
this.error.set(e?.status === 404 ? 'No such service in this project.' : apiMessage(e, 'The service could not be loaded.'));
|
|
@@ -1743,9 +1767,9 @@ class ServicePage {
|
|
|
1743
1767
|
this.api.metrics(this.key(), d.id).subscribe({
|
|
1744
1768
|
next: (m) => {
|
|
1745
1769
|
this.readings.update((rs) => [...rs, m].slice(-90));
|
|
1746
|
-
this.metricsTimer = setTimeout(() => this.pollMetrics(), 10_000);
|
|
1770
|
+
this.metricsTimer = setTimeout(() => this.pollMetrics(), pollDelay(10_000));
|
|
1747
1771
|
},
|
|
1748
|
-
error: () => (this.metricsTimer = setTimeout(() => this.pollMetrics(), 30_000)),
|
|
1772
|
+
error: () => (this.metricsTimer = setTimeout(() => this.pollMetrics(), pollDelay(30_000))),
|
|
1749
1773
|
});
|
|
1750
1774
|
}
|
|
1751
1775
|
setTab(t) {
|
|
@@ -1756,7 +1780,7 @@ class ServicePage {
|
|
|
1756
1780
|
return `${cpu} / ${ram} / ${d.diskGb} GB`;
|
|
1757
1781
|
}
|
|
1758
1782
|
snippetLabel(k) {
|
|
1759
|
-
return { uri: 'URI', node: 'Node', cli: 'origine CLI', nats: 'nats' }[k] ?? k;
|
|
1783
|
+
return { uri: 'URI', node: 'Node', cli: 'origine CLI', nats: 'nats', sentinel: 'Sentinel' }[k] ?? k;
|
|
1760
1784
|
}
|
|
1761
1785
|
attState(a) {
|
|
1762
1786
|
return a.state === 'live' ? 'Injected' : a.state === 'failed' ? 'Failed' : 'Redeploying…';
|
|
@@ -1885,13 +1909,14 @@ class ServicePage {
|
|
|
1885
1909
|
return `${d.name}-restore-1`.slice(0, 40);
|
|
1886
1910
|
}
|
|
1887
1911
|
askRestore(d, b) {
|
|
1888
|
-
|
|
1912
|
+
const vols = d.engine === 'rabbitmq' && b.volumes?.phase === 'completed';
|
|
1913
|
+
this.dialog.set({ kind: 'restore', name: this.restoreName(d), point: '', backup: b.name, volumes: vols, hasVolumes: vols });
|
|
1889
1914
|
}
|
|
1890
1915
|
askPitr(d) {
|
|
1891
1916
|
this.dialog.set({ kind: 'restore', name: this.restoreName(d), point: this.pitr(), backup: '' });
|
|
1892
1917
|
}
|
|
1893
1918
|
restore(d, dl) {
|
|
1894
|
-
const body = dl.point ? { pointInTime: fromLocalInput(dl.point), name: dl.name } : { backup: dl.backup, name: dl.name };
|
|
1919
|
+
const body = dl.point ? { pointInTime: fromLocalInput(dl.point), name: dl.name } : { backup: dl.backup, name: dl.name, volumes: !!dl.volumes };
|
|
1895
1920
|
this.busy.set(true);
|
|
1896
1921
|
this.error.set('');
|
|
1897
1922
|
this.api.restore(this.key(), d.id, body).subscribe({
|
|
@@ -1946,6 +1971,35 @@ class ServicePage {
|
|
|
1946
1971
|
refresh() {
|
|
1947
1972
|
this.load();
|
|
1948
1973
|
}
|
|
1974
|
+
/** Opens the instance's management UI in a new tab: a link for this person that expires. */
|
|
1975
|
+
openManagement(d) {
|
|
1976
|
+
const tab = window.open('', '_blank');
|
|
1977
|
+
this.busy.set(true);
|
|
1978
|
+
this.error.set('');
|
|
1979
|
+
this.api.management(this.key(), d.id).subscribe({
|
|
1980
|
+
next: (l) => {
|
|
1981
|
+
this.busy.set(false);
|
|
1982
|
+
if (tab) {
|
|
1983
|
+
tab.opener = null;
|
|
1984
|
+
tab.location.href = l.url;
|
|
1985
|
+
}
|
|
1986
|
+
else
|
|
1987
|
+
window.open(l.url, '_blank', 'noopener');
|
|
1988
|
+
this.notice.set(`The management UI of ${d.name} opened in a new tab${l.mode === 'read' ? ', read-only for your role' : ''}. Its link works for you for 30 minutes.`);
|
|
1989
|
+
},
|
|
1990
|
+
error: (e) => {
|
|
1991
|
+
this.busy.set(false);
|
|
1992
|
+
tab?.close();
|
|
1993
|
+
this.error.set(apiMessage(e, 'The management UI could not be opened.'));
|
|
1994
|
+
},
|
|
1995
|
+
});
|
|
1996
|
+
}
|
|
1997
|
+
snapshotLine(d) {
|
|
1998
|
+
const v = d.volumeSnapshots;
|
|
1999
|
+
if (v?.class)
|
|
2000
|
+
return `Each backup also snapshots every node's volume (${v.class}): restores can bring the messages back.`;
|
|
2001
|
+
return `Backups hold the definitions only: ${v?.message || 'this cell has no CSI snapshot class'}.`;
|
|
2002
|
+
}
|
|
1949
2003
|
snapshotName(d) {
|
|
1950
2004
|
return d.engine === 'nats' ? 'stream snapshot' : d.engine === 'rabbitmq' ? 'definitions export' : 'RDB snapshot';
|
|
1951
2005
|
}
|
|
@@ -2016,6 +2070,7 @@ class ServicePage {
|
|
|
2016
2070
|
<div class="o-flex"><h1 class="o-t">{{ d.name }}</h1><span [class]="chip(d.state)" data-testid="svc-state">{{ d.state }}</span></div>
|
|
2017
2071
|
</div>
|
|
2018
2072
|
<div class="o-acts">
|
|
2073
|
+
@if (d.management && !d.deletedAt) {<button type="button" class="o-btn" (click)="openManagement(d)" [disabled]="busy() || d.state !== 'Running' && d.state !== 'Updating' && d.state !== 'Degraded'" [title]="d.management === 'read' ? 'Read-only for your role' : 'As the instance administrator'" data-testid="svc-management">Open management UI@if (d.management === 'read') {<span class="o-faint o-xs"> · read-only</span>}</button>}
|
|
2019
2074
|
@if (d.canManage && !d.deletedAt) {<button type="button" class="o-btn" (click)="askRotate(d)" [disabled]="busy() || !d.users?.length" data-testid="svc-rotate">Rotate credentials</button>}
|
|
2020
2075
|
@if (d.canManage && !d.deletedAt) {<button type="button" class="o-btn" (click)="setTab('settings')">Resize</button>}
|
|
2021
2076
|
<a class="o-btn" [routerLink]="host.link('dashboards')">Dashboard</a>
|
|
@@ -2171,7 +2226,7 @@ class ServicePage {
|
|
|
2171
2226
|
@for (b of backups(); track b.name) {
|
|
2172
2227
|
<div class="o-row" [attr.data-testid]="'svc-backup-' + b.name">
|
|
2173
2228
|
<span [class]="'o-dot ' + backupDot(b)"></span><span style="width:150px">{{ when(b.startedAt) || 'Pending' }}</span><span class="o-faint o-sm" style="width:80px">{{ backupNote(b) }}</span>
|
|
2174
|
-
<span class="o-chip">{{ kind(b) }}</span
|
|
2229
|
+
<span class="o-chip">{{ kind(b) }}</span>@if (b.volumes) {<span [class]="'o-chip ' + (b.volumes.phase === 'completed' ? 'o-grn' : b.volumes.phase === 'failed' ? 'o-red' : 'o-blu')" [title]="b.volumes.error || b.volumes.snapshots + ' volume snapshot(s)'" data-testid="svc-backup-volumes">{{ b.volumes.phase === 'completed' ? '+ queue data' : b.volumes.phase === 'failed' ? 'queue data failed' : 'snapshotting' }}</span>}<span class="o-faint o-sm">{{ keepLine(b, d.retentionDays) }}</span>
|
|
2175
2230
|
@if (b.phase === 'completed' && d.canManage) {<button type="button" class="o-lnk o-r" (click)="askRestore(d, b)">Restore</button>}
|
|
2176
2231
|
@if (b.kind === 'on-demand' && d.canManage && b.phase !== 'running') {<button type="button" class="o-lnk o-faint" [class.o-r]="b.phase !== 'completed'" (click)="deleteBackup(d, b)" [attr.aria-label]="'Delete ' + b.name" [disabled]="busy()">×</button>}
|
|
2177
2232
|
</div>
|
|
@@ -2187,7 +2242,8 @@ class ServicePage {
|
|
|
2187
2242
|
<button type="button" class="o-btn o-dark" (click)="askPitr(d)" [disabled]="!d.canManage || !pitr() || !d.window?.from" data-testid="svc-pitr-go">Restore…</button>
|
|
2188
2243
|
</div>
|
|
2189
2244
|
} @else {
|
|
2190
|
-
<div class="o-card o-cb o-col" style="gap:6px"><b>Restore a snapshot</b><span class="o-hint">{{ d.engineName }} restores from a daily {{ snapshotName(d) }}: pick one on the left. It always creates a new instance.</span
|
|
2245
|
+
<div class="o-card o-cb o-col" style="gap:6px"><b>Restore a snapshot</b><span class="o-hint">{{ d.engineName }} restores from a daily {{ snapshotName(d) }}: pick one on the left. It always creates a new instance.</span>
|
|
2246
|
+
@if (d.engine === 'rabbitmq') {<span class="o-hint" data-testid="svc-snapshot-support"><span [class]="'o-chip ' + (d.volumeSnapshots?.class ? 'o-grn' : '')">{{ d.volumeSnapshots?.class ? 'Queue data snapshots' : 'Definitions only' }}</span> {{ snapshotLine(d) }}</span>}</div>
|
|
2191
2247
|
}
|
|
2192
2248
|
<div class="o-card o-cb o-form">
|
|
2193
2249
|
<b>Schedule</b>
|
|
@@ -2242,9 +2298,18 @@ class ServicePage {
|
|
|
2242
2298
|
<div class="o-row"><span style="flex:1">3-node cluster<span class="o-faint o-sm"> · {{ d.engine === 'nats' ? 'streams replicated on three servers' : 'quorum queues on three nodes' }}</span></span><button type="button" class="o-tog" [class.o-on]="d.standby" [disabled]="!d.canManage || busy()" (click)="topology(d, !d.standby, 0)" aria-label="3-node cluster" data-testid="svc-standby"></button></div>
|
|
2243
2299
|
<div class="o-cf">Each node is billed at the plan's price · {{ nodesPrice(d) }}/mo now</div>
|
|
2244
2300
|
}
|
|
2301
|
+
@case ('replica') {
|
|
2302
|
+
<div class="o-row"><span style="flex:1">Replica with automatic failover<span class="o-faint o-sm"> · three Sentinels promote it if the primary fails</span></span><button type="button" class="o-tog" [class.o-on]="d.standby" [disabled]="!d.canManage || busy()" (click)="topology(d, !d.standby, 0)" aria-label="Replica with failover" data-testid="svc-standby"></button></div>
|
|
2303
|
+
<div class="o-row"><span style="flex:1">Second replica@if (d.sentinel; as sn) {<span class="o-faint o-sm o-mono"> · Sentinels {{ sn.hosts.length }} × :{{ sn.port }}, master {{ sn.master }}</span>}</span><span class="o-faint o-sm" data-testid="svc-replicas">{{ d.replicas }}</span>
|
|
2304
|
+
@if (d.canManage) {
|
|
2305
|
+
<button type="button" class="o-btn o-xs" (click)="topology(d, d.standby, d.replicas - 1)" [disabled]="busy() || d.replicas === 0">−</button>
|
|
2306
|
+
<button type="button" class="o-btn o-xs" (click)="topology(d, true, d.replicas + 1)" [disabled]="busy() || d.replicas + 1 > (engine()?.maxReplicas ?? 0)" data-testid="svc-replica-add">+ Replica</button>
|
|
2307
|
+
}</div>
|
|
2308
|
+
<div class="o-cf">Each node is billed at the plan's price · {{ nodesPrice(d) }}/mo now · apps get REDIS_SENTINEL_URL beside REDIS_URL</div>
|
|
2309
|
+
}
|
|
2245
2310
|
@default {
|
|
2246
|
-
<div class="o-row"><span style="flex:1">
|
|
2247
|
-
<div class="o-cf">{{ d.engineName }} runs one node
|
|
2311
|
+
<div class="o-row"><span style="flex:1">One node</span></div>
|
|
2312
|
+
<div class="o-cf">{{ d.engineName }} runs one node; daily snapshots keep its data.</div>
|
|
2248
2313
|
}
|
|
2249
2314
|
}
|
|
2250
2315
|
</div>
|
|
@@ -2299,6 +2364,7 @@ class ServicePage {
|
|
|
2299
2364
|
}
|
|
2300
2365
|
@case ('restore') {
|
|
2301
2366
|
<span>{{ dl.point ? 'Restores ' + d.name + ' as it was at ' + whenLocal(dl.point) : 'Restores backup ' + dl.backup }} into a new instance with the same users and passwords. {{ d.name }} keeps running untouched; move its apps over afterwards if you want.</span>
|
|
2367
|
+
@if (dl.hasVolumes) {<label class="o-flex o-sm"><input type="checkbox" [(ngModel)]="dl.volumes" data-testid="svc-restore-volumes" /> With the queue data (the backup's volume snapshots): messages, not only the definitions</label>}
|
|
2302
2368
|
<label class="o-fld"><span class="o-sm">New instance</span><input class="o-in o-mono" [(ngModel)]="dl.name" data-testid="svc-restore-name" autocomplete="off" /></label>
|
|
2303
2369
|
}
|
|
2304
2370
|
@case ('rotate') {
|
|
@@ -2339,6 +2405,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
2339
2405
|
<div class="o-flex"><h1 class="o-t">{{ d.name }}</h1><span [class]="chip(d.state)" data-testid="svc-state">{{ d.state }}</span></div>
|
|
2340
2406
|
</div>
|
|
2341
2407
|
<div class="o-acts">
|
|
2408
|
+
@if (d.management && !d.deletedAt) {<button type="button" class="o-btn" (click)="openManagement(d)" [disabled]="busy() || d.state !== 'Running' && d.state !== 'Updating' && d.state !== 'Degraded'" [title]="d.management === 'read' ? 'Read-only for your role' : 'As the instance administrator'" data-testid="svc-management">Open management UI@if (d.management === 'read') {<span class="o-faint o-xs"> · read-only</span>}</button>}
|
|
2342
2409
|
@if (d.canManage && !d.deletedAt) {<button type="button" class="o-btn" (click)="askRotate(d)" [disabled]="busy() || !d.users?.length" data-testid="svc-rotate">Rotate credentials</button>}
|
|
2343
2410
|
@if (d.canManage && !d.deletedAt) {<button type="button" class="o-btn" (click)="setTab('settings')">Resize</button>}
|
|
2344
2411
|
<a class="o-btn" [routerLink]="host.link('dashboards')">Dashboard</a>
|
|
@@ -2494,7 +2561,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
2494
2561
|
@for (b of backups(); track b.name) {
|
|
2495
2562
|
<div class="o-row" [attr.data-testid]="'svc-backup-' + b.name">
|
|
2496
2563
|
<span [class]="'o-dot ' + backupDot(b)"></span><span style="width:150px">{{ when(b.startedAt) || 'Pending' }}</span><span class="o-faint o-sm" style="width:80px">{{ backupNote(b) }}</span>
|
|
2497
|
-
<span class="o-chip">{{ kind(b) }}</span
|
|
2564
|
+
<span class="o-chip">{{ kind(b) }}</span>@if (b.volumes) {<span [class]="'o-chip ' + (b.volumes.phase === 'completed' ? 'o-grn' : b.volumes.phase === 'failed' ? 'o-red' : 'o-blu')" [title]="b.volumes.error || b.volumes.snapshots + ' volume snapshot(s)'" data-testid="svc-backup-volumes">{{ b.volumes.phase === 'completed' ? '+ queue data' : b.volumes.phase === 'failed' ? 'queue data failed' : 'snapshotting' }}</span>}<span class="o-faint o-sm">{{ keepLine(b, d.retentionDays) }}</span>
|
|
2498
2565
|
@if (b.phase === 'completed' && d.canManage) {<button type="button" class="o-lnk o-r" (click)="askRestore(d, b)">Restore</button>}
|
|
2499
2566
|
@if (b.kind === 'on-demand' && d.canManage && b.phase !== 'running') {<button type="button" class="o-lnk o-faint" [class.o-r]="b.phase !== 'completed'" (click)="deleteBackup(d, b)" [attr.aria-label]="'Delete ' + b.name" [disabled]="busy()">×</button>}
|
|
2500
2567
|
</div>
|
|
@@ -2510,7 +2577,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
2510
2577
|
<button type="button" class="o-btn o-dark" (click)="askPitr(d)" [disabled]="!d.canManage || !pitr() || !d.window?.from" data-testid="svc-pitr-go">Restore…</button>
|
|
2511
2578
|
</div>
|
|
2512
2579
|
} @else {
|
|
2513
|
-
<div class="o-card o-cb o-col" style="gap:6px"><b>Restore a snapshot</b><span class="o-hint">{{ d.engineName }} restores from a daily {{ snapshotName(d) }}: pick one on the left. It always creates a new instance.</span
|
|
2580
|
+
<div class="o-card o-cb o-col" style="gap:6px"><b>Restore a snapshot</b><span class="o-hint">{{ d.engineName }} restores from a daily {{ snapshotName(d) }}: pick one on the left. It always creates a new instance.</span>
|
|
2581
|
+
@if (d.engine === 'rabbitmq') {<span class="o-hint" data-testid="svc-snapshot-support"><span [class]="'o-chip ' + (d.volumeSnapshots?.class ? 'o-grn' : '')">{{ d.volumeSnapshots?.class ? 'Queue data snapshots' : 'Definitions only' }}</span> {{ snapshotLine(d) }}</span>}</div>
|
|
2514
2582
|
}
|
|
2515
2583
|
<div class="o-card o-cb o-form">
|
|
2516
2584
|
<b>Schedule</b>
|
|
@@ -2565,9 +2633,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
2565
2633
|
<div class="o-row"><span style="flex:1">3-node cluster<span class="o-faint o-sm"> · {{ d.engine === 'nats' ? 'streams replicated on three servers' : 'quorum queues on three nodes' }}</span></span><button type="button" class="o-tog" [class.o-on]="d.standby" [disabled]="!d.canManage || busy()" (click)="topology(d, !d.standby, 0)" aria-label="3-node cluster" data-testid="svc-standby"></button></div>
|
|
2566
2634
|
<div class="o-cf">Each node is billed at the plan's price · {{ nodesPrice(d) }}/mo now</div>
|
|
2567
2635
|
}
|
|
2636
|
+
@case ('replica') {
|
|
2637
|
+
<div class="o-row"><span style="flex:1">Replica with automatic failover<span class="o-faint o-sm"> · three Sentinels promote it if the primary fails</span></span><button type="button" class="o-tog" [class.o-on]="d.standby" [disabled]="!d.canManage || busy()" (click)="topology(d, !d.standby, 0)" aria-label="Replica with failover" data-testid="svc-standby"></button></div>
|
|
2638
|
+
<div class="o-row"><span style="flex:1">Second replica@if (d.sentinel; as sn) {<span class="o-faint o-sm o-mono"> · Sentinels {{ sn.hosts.length }} × :{{ sn.port }}, master {{ sn.master }}</span>}</span><span class="o-faint o-sm" data-testid="svc-replicas">{{ d.replicas }}</span>
|
|
2639
|
+
@if (d.canManage) {
|
|
2640
|
+
<button type="button" class="o-btn o-xs" (click)="topology(d, d.standby, d.replicas - 1)" [disabled]="busy() || d.replicas === 0">−</button>
|
|
2641
|
+
<button type="button" class="o-btn o-xs" (click)="topology(d, true, d.replicas + 1)" [disabled]="busy() || d.replicas + 1 > (engine()?.maxReplicas ?? 0)" data-testid="svc-replica-add">+ Replica</button>
|
|
2642
|
+
}</div>
|
|
2643
|
+
<div class="o-cf">Each node is billed at the plan's price · {{ nodesPrice(d) }}/mo now · apps get REDIS_SENTINEL_URL beside REDIS_URL</div>
|
|
2644
|
+
}
|
|
2568
2645
|
@default {
|
|
2569
|
-
<div class="o-row"><span style="flex:1">
|
|
2570
|
-
<div class="o-cf">{{ d.engineName }} runs one node
|
|
2646
|
+
<div class="o-row"><span style="flex:1">One node</span></div>
|
|
2647
|
+
<div class="o-cf">{{ d.engineName }} runs one node; daily snapshots keep its data.</div>
|
|
2571
2648
|
}
|
|
2572
2649
|
}
|
|
2573
2650
|
</div>
|
|
@@ -2622,6 +2699,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
2622
2699
|
}
|
|
2623
2700
|
@case ('restore') {
|
|
2624
2701
|
<span>{{ dl.point ? 'Restores ' + d.name + ' as it was at ' + whenLocal(dl.point) : 'Restores backup ' + dl.backup }} into a new instance with the same users and passwords. {{ d.name }} keeps running untouched; move its apps over afterwards if you want.</span>
|
|
2702
|
+
@if (dl.hasVolumes) {<label class="o-flex o-sm"><input type="checkbox" [(ngModel)]="dl.volumes" data-testid="svc-restore-volumes" /> With the queue data (the backup's volume snapshots): messages, not only the definitions</label>}
|
|
2625
2703
|
<label class="o-fld"><span class="o-sm">New instance</span><input class="o-in o-mono" [(ngModel)]="dl.name" data-testid="svc-restore-name" autocomplete="off" /></label>
|
|
2626
2704
|
}
|
|
2627
2705
|
@case ('rotate') {
|
|
@@ -2659,5 +2737,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
2659
2737
|
* Generated bundle index. Do not edit.
|
|
2660
2738
|
*/
|
|
2661
2739
|
|
|
2662
|
-
export { DAYS, ENGINE_COLOUR, LIFECYCLE, PRESETS, ServiceConsoleTab, ServiceDumpsCard, ServiceLogsTab, ServiceMetricsTab, ServicePage, ServicesApi, ServicesHost, ServicesListPage, ServicesNewPage, apiMessage, backupKind, backupsLine, bytes, fromLocalInput, hhmm, hhmmss, money, moving, num, pct, presetChip, ratio, shortDate, spark, sparkPath, stateChip, stateDot, toLocalInput, utcHour, when };
|
|
2740
|
+
export { DAYS, ENGINE_COLOUR, HIDDEN_POLL_MS, LIFECYCLE, PRESETS, ServiceConsoleTab, ServiceDumpsCard, ServiceLogsTab, ServiceMetricsTab, ServicePage, ServicesApi, ServicesHost, ServicesListPage, ServicesNewPage, apiMessage, backupKind, backupsLine, bytes, fromLocalInput, hhmm, hhmmss, money, moving, num, pct, pollDelay, presetChip, ratio, shortDate, spark, sparkPath, stateChip, stateDot, toLocalInput, utcHour, when };
|
|
2663
2741
|
//# sourceMappingURL=chisquare-tech-origine-ngx-apps-services.mjs.map
|