Files
catalog/ts_web/elements/sz-services-backups-view.ts

393 lines
13 KiB
TypeScript

import {
DeesElement,
customElement,
html,
css,
cssManager,
property,
type TemplateResult,
} from '@design.estate/dees-element';
declare global {
interface HTMLElementTagNameMap {
'sz-services-backups-view': SzServicesBackupsView;
}
}
export interface IBackupSchedule {
id: string;
scope: string;
retention: string;
schedule: string;
lastRun?: string;
nextRun?: string;
status: 'active' | 'failed' | 'disabled';
error?: string;
}
export interface IBackup {
id: string;
service: string;
createdAt: string;
size: string;
includes: string[];
}
@customElement('sz-services-backups-view')
export class SzServicesBackupsView extends DeesElement {
public static demo = () => html`
<div style="padding: 24px; max-width: 1200px;">
<sz-services-backups-view
.schedules=${[
{ id: '1', scope: 'All Services', retention: 'D:7, W:4, M:12', schedule: '0 2 * * *', lastRun: '1/2/2026, 2:00:03 AM', nextRun: '1/3/2026, 2:00:00 AM', status: 'active' },
]}
.backups=${[
{ id: '1', service: 'test-nginx', createdAt: '1/2/2026, 2:00:03 AM', size: '22.0 MB', includes: ['Image'] },
{ id: '2', service: 'hello-world', createdAt: '1/2/2026, 2:00:02 AM', size: '21.5 MB', includes: ['Image'] },
{ id: '3', service: 'test-ch-final', createdAt: '1/2/2026, 2:00:00 AM', size: '22.0 MB', includes: ['Image', 'clickhouse'] },
]}
></sz-services-backups-view>
</div>
`;
public static demoGroups = ['Services'];
@property({ type: Array })
public accessor schedules: IBackupSchedule[] = [];
@property({ type: Array })
public accessor backups: IBackup[] = [];
public static styles = [
cssManager.defaultStyles,
css`
:host {
display: block;
}
.section {
background: ${cssManager.bdTheme('#ffffff', '#09090b')};
border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
border-radius: 8px;
margin-bottom: 24px;
overflow: hidden;
}
.section-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: 16px;
border-bottom: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
}
.section-info {
display: flex;
flex-direction: column;
gap: 4px;
}
.section-title {
font-size: 16px;
font-weight: 600;
color: ${cssManager.bdTheme('#18181b', '#fafafa')};
}
.section-subtitle {
font-size: 13px;
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
}
.header-actions {
display: flex;
gap: 8px;
}
.action-button {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 14px;
background: ${cssManager.bdTheme('#ffffff', '#09090b')};
border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
border-radius: 6px;
font-size: 13px;
font-weight: 500;
color: ${cssManager.bdTheme('#18181b', '#fafafa')};
cursor: pointer;
transition: all 200ms ease;
}
.action-button:hover {
background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
}
.action-button.primary {
background: ${cssManager.bdTheme('#18181b', '#fafafa')};
color: ${cssManager.bdTheme('#fafafa', '#18181b')};
border: none;
}
.action-button.primary:hover {
opacity: 0.9;
}
.table-header {
display: grid;
gap: 16px;
padding: 12px 16px;
background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
border-bottom: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
}
.schedules-header {
grid-template-columns: 1fr 1fr 1fr 1.5fr 1.5fr 80px 120px;
}
.backups-header {
grid-template-columns: 1.5fr 1.5fr 100px 1fr 120px;
}
.table-row {
display: grid;
gap: 16px;
padding: 12px 16px;
border-bottom: 1px solid ${cssManager.bdTheme('#f4f4f5', '#27272a')};
font-size: 14px;
color: ${cssManager.bdTheme('#18181b', '#fafafa')};
align-items: center;
}
.schedules-row {
grid-template-columns: 1fr 1fr 1fr 1.5fr 1.5fr 80px 120px;
}
.backups-row {
grid-template-columns: 1.5fr 1.5fr 100px 1fr 120px;
}
.table-row:last-child {
border-bottom: none;
}
.table-row:hover {
background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
}
.status-badge {
display: inline-flex;
align-items: center;
padding: 2px 8px;
border-radius: 9999px;
font-size: 12px;
font-weight: 500;
}
.status-badge.active {
background: ${cssManager.bdTheme('#dcfce7', 'rgba(34, 197, 94, 0.2)')};
color: ${cssManager.bdTheme('#16a34a', '#22c55e')};
}
.status-badge.failed {
background: ${cssManager.bdTheme('#fee2e2', 'rgba(239, 68, 68, 0.2)')};
color: ${cssManager.bdTheme('#dc2626', '#ef4444')};
}
.status-badge.disabled {
background: ${cssManager.bdTheme('#f4f4f5', '#27272a')};
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
}
.meta-text {
font-size: 13px;
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
}
.includes-list {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.include-badge {
padding: 2px 8px;
background: ${cssManager.bdTheme('#dbeafe', 'rgba(59, 130, 246, 0.2)')};
color: ${cssManager.bdTheme('#2563eb', '#60a5fa')};
border-radius: 4px;
font-size: 11px;
font-weight: 500;
}
.row-actions {
display: flex;
gap: 4px;
}
.icon-button {
padding: 6px;
background: transparent;
border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
border-radius: 4px;
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
cursor: pointer;
transition: all 200ms ease;
}
.icon-button:hover {
background: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
color: ${cssManager.bdTheme('#18181b', '#fafafa')};
}
.icon-button.danger:hover {
background: ${cssManager.bdTheme('#fee2e2', 'rgba(239, 68, 68, 0.2)')};
color: ${cssManager.bdTheme('#dc2626', '#ef4444')};
border-color: ${cssManager.bdTheme('#fee2e2', 'rgba(239, 68, 68, 0.3)')};
}
`,
];
public render(): TemplateResult {
return html`
<div class="section">
<div class="section-header">
<div class="section-info">
<div class="section-title">Backup Schedules</div>
<div class="section-subtitle">Configure automated backup schedules for your services</div>
</div>
<div class="header-actions">
<button class="action-button" @click=${() => this.handleImport()}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
<polyline points="17 8 12 3 7 8"/>
<line x1="12" y1="3" x2="12" y2="15"/>
</svg>
Import Backup
</button>
<button class="action-button primary" @click=${() => this.handleCreateSchedule()}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
Create Schedule
</button>
</div>
</div>
<div class="table-header schedules-header">
<span>Scope</span>
<span>Retention</span>
<span>Schedule</span>
<span>Last Run</span>
<span>Next Run</span>
<span>Status</span>
<span>Actions</span>
</div>
${this.schedules.map(schedule => html`
<div class="table-row schedules-row">
<span>${schedule.scope}</span>
<span class="meta-text" title="Keep: ${schedule.retention}">${schedule.retention}</span>
<span class="meta-text">${schedule.schedule}</span>
<span class="meta-text">${schedule.lastRun || '-'}</span>
<span class="meta-text">${schedule.nextRun || '-'}</span>
<span>
<span class="status-badge ${schedule.status}" title="${schedule.error || ''}">
${schedule.status === 'active' ? 'Active' : schedule.status === 'failed' ? 'Failed' : 'Disabled'}
</span>
</span>
<span class="row-actions">
<button class="icon-button" title="Run backup now" @click=${() => this.handleRunNow(schedule)}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><polygon points="5,3 19,12 5,21"/></svg>
</button>
<button class="icon-button" title="${schedule.status === 'disabled' ? 'Enable' : 'Disable'}" @click=${() => this.handleToggle(schedule)}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
${schedule.status === 'disabled'
? html`<polyline points="20 6 9 17 4 12"></polyline>`
: html`<rect x="6" y="6" width="12" height="12" rx="1"/>`
}
</svg>
</button>
<button class="icon-button danger" title="Delete" @click=${() => this.handleDeleteSchedule(schedule)}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="3,6 5,6 21,6"/><path d="M19,6v14a2,2,0,0,1-2,2H7a2,2,0,0,1-2-2V6m3,0V4a2,2,0,0,1,2-2h4a2,2,0,0,1,2,2v2"/>
</svg>
</button>
</span>
</div>
`)}
</div>
<div class="section">
<div class="section-header">
<div class="section-info">
<div class="section-title">All Backups</div>
<div class="section-subtitle">Browse and manage all backups across services</div>
</div>
</div>
<div class="table-header backups-header">
<span>Service</span>
<span>Created</span>
<span>Size</span>
<span>Includes</span>
<span>Actions</span>
</div>
${this.backups.map(backup => html`
<div class="table-row backups-row">
<span>${backup.service}</span>
<span class="meta-text">${backup.createdAt}</span>
<span class="meta-text">${backup.size}</span>
<span class="includes-list">
${backup.includes.map(inc => html`<span class="include-badge">${inc}</span>`)}
</span>
<span class="row-actions">
<button class="icon-button" title="Download backup" @click=${() => this.handleDownload(backup)}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
<polyline points="7 10 12 15 17 10"/>
<line x1="12" y1="15" x2="12" y2="3"/>
</svg>
</button>
<button class="icon-button danger" title="Delete" @click=${() => this.handleDeleteBackup(backup)}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="3,6 5,6 21,6"/><path d="M19,6v14a2,2,0,0,1-2,2H7a2,2,0,0,1-2-2V6m3,0V4a2,2,0,0,1,2-2h4a2,2,0,0,1,2,2v2"/>
</svg>
</button>
</span>
</div>
`)}
</div>
`;
}
private handleImport() {
this.dispatchEvent(new CustomEvent('import', { bubbles: true, composed: true }));
}
private handleCreateSchedule() {
this.dispatchEvent(new CustomEvent('create-schedule', { bubbles: true, composed: true }));
}
private handleRunNow(schedule: IBackupSchedule) {
this.dispatchEvent(new CustomEvent('run-now', { detail: schedule, bubbles: true, composed: true }));
}
private handleToggle(schedule: IBackupSchedule) {
this.dispatchEvent(new CustomEvent('toggle-schedule', { detail: schedule, bubbles: true, composed: true }));
}
private handleDeleteSchedule(schedule: IBackupSchedule) {
this.dispatchEvent(new CustomEvent('delete-schedule', { detail: schedule, bubbles: true, composed: true }));
}
private handleDownload(backup: IBackup) {
this.dispatchEvent(new CustomEvent('download', { detail: backup, bubbles: true, composed: true }));
}
private handleDeleteBackup(backup: IBackup) {
this.dispatchEvent(new CustomEvent('delete-backup', { detail: backup, bubbles: true, composed: true }));
}
}