feat: v1.1.0 — backup module, IPAM node binding, HA, task queue, theme CSS
- Backup module: full CRUD for storages, backup files, scheduled jobs (APScheduler) - Proxmox client: storage/backup/restore/HA methods - IPAM: proxmox_node field, server+node binding for networks and allocations - Task queue service: improvements and new task types - Monitoring worker: extended metrics and alerts - HA support: add/remove VMs from Proxmox HA groups - tasks.html: new task tracking page - proxmox_vms.html: major UI update (bulk ops, HA, snapshots) - backups.html: full backup management UI (3 tabs) - base.html: sidebar backup link, websocket manager - theme.css: full CSS variables theme, 395 lines added - migrations: migration #15 (BackupJob), #16 (proxmox_node) - RBAC: backup resource permissions - websocket_manager.py: new module for WS connections
This commit is contained in:
@@ -2303,3 +2303,398 @@ body {
|
||||
/* Rocky Linux использует тот же зелёный цвет */
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 192 192'%3E%3Cpath fill='%2310B981' fill-rule='evenodd' clip-rule='evenodd' d='M186.7 127.7C190.1 117.7 192 107.1 192 96c0-53-43-96-96-96S0 43 0 96c0 26.2 10.5 50 27.6 67.3l97.4-97.4L149 90l37.7 37.7zM169.1 158.2l-44.2-44.2-69.1 69.2C67.9 188.8 81.6 192 96 192c29.3 0 55.5-13.1 73.1-33.8z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
|
||||
/* ================================================================
|
||||
TASK MANAGER — Animations & Component Styles
|
||||
================================================================ */
|
||||
|
||||
/* ── Keyframes ────────────────────────────────────────────────── */
|
||||
@keyframes task-shimmer {
|
||||
0% { background-position: -200% center; }
|
||||
100% { background-position: 200% center; }
|
||||
}
|
||||
|
||||
@keyframes task-pulse-dot {
|
||||
0%, 100% { opacity: 1; transform: scale(1); }
|
||||
50% { opacity: .5; transform: scale(1.35); }
|
||||
}
|
||||
|
||||
@keyframes task-shake {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
20% { transform: translateX(-5px); }
|
||||
40% { transform: translateX(5px); }
|
||||
60% { transform: translateX(-4px); }
|
||||
80% { transform: translateX(4px); }
|
||||
}
|
||||
|
||||
@keyframes task-spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
@keyframes task-drawer-in {
|
||||
from { transform: translateX(100%); opacity: 0; }
|
||||
to { transform: translateX(0); opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes task-badge-pop {
|
||||
0% { transform: scale(0); }
|
||||
70% { transform: scale(1.3); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
|
||||
/* ── Status colours ───────────────────────────────────────────── */
|
||||
.task-status-running { color: #667eea; }
|
||||
.task-status-pending { color: #f97316; }
|
||||
.task-status-completed { color: #10b981; }
|
||||
.task-status-failed { color: #ef4444; }
|
||||
.task-status-cancelled { color: #6b7280; }
|
||||
|
||||
/* ── Status badges ────────────────────────────────────────────── */
|
||||
.task-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: .35rem;
|
||||
padding: 3px 10px;
|
||||
border-radius: var(--radius-full, 9999px);
|
||||
font-size: .72rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: .03em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.task-badge-running {
|
||||
background: rgba(102,126,234,.15);
|
||||
color: #667eea;
|
||||
border: 1px solid rgba(102,126,234,.3);
|
||||
}
|
||||
.task-badge-pending {
|
||||
background: rgba(249,115,22,.13);
|
||||
color: #f97316;
|
||||
border: 1px solid rgba(249,115,22,.3);
|
||||
}
|
||||
.task-badge-completed {
|
||||
background: rgba(16,185,129,.13);
|
||||
color: #10b981;
|
||||
border: 1px solid rgba(16,185,129,.3);
|
||||
}
|
||||
.task-badge-failed {
|
||||
background: rgba(239,68,68,.13);
|
||||
color: #ef4444;
|
||||
border: 1px solid rgba(239,68,68,.3);
|
||||
}
|
||||
.task-badge-cancelled {
|
||||
background: rgba(107,114,128,.1);
|
||||
color: #9ca3af;
|
||||
border: 1px solid rgba(107,114,128,.25);
|
||||
}
|
||||
|
||||
/* ── Progress bar ─────────────────────────────────────────────── */
|
||||
.task-progress-track {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
background: var(--bg-surface-hover, rgba(255,255,255,.08));
|
||||
border-radius: 9999px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.task-progress-fill {
|
||||
height: 100%;
|
||||
border-radius: 9999px;
|
||||
transition: width .5s ease;
|
||||
background: var(--gradient-primary, linear-gradient(90deg, #667eea, #764ba2));
|
||||
}
|
||||
.task-progress-fill.running {
|
||||
background: linear-gradient(90deg, #667eea 0%, #a78bfa 50%, #667eea 100%);
|
||||
background-size: 200% 100%;
|
||||
animation: task-shimmer 2s linear infinite;
|
||||
}
|
||||
.task-progress-fill.pending {
|
||||
background: linear-gradient(90deg, #f97316, #fb923c);
|
||||
animation: task-shimmer 2.5s linear infinite;
|
||||
background-size: 200% 100%;
|
||||
}
|
||||
.task-progress-fill.completed { background: linear-gradient(90deg, #10b981, #34d399); }
|
||||
.task-progress-fill.failed { background: linear-gradient(90deg, #ef4444, #f87171); }
|
||||
.task-progress-fill.cancelled { background: #6b7280; }
|
||||
|
||||
/* ── Spinning icon for running tasks ──────────────────────────── */
|
||||
.task-icon-spin {
|
||||
display: inline-block;
|
||||
animation: task-spin 1.2s linear infinite;
|
||||
}
|
||||
|
||||
/* ── Error shake ──────────────────────────────────────────────── */
|
||||
.task-shake {
|
||||
animation: task-shake .5s ease;
|
||||
}
|
||||
|
||||
/* ── Navbar tasks button & badge ──────────────────────────────── */
|
||||
.tasks-btn {
|
||||
position: relative;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 8px;
|
||||
border-radius: 50%;
|
||||
color: var(--text-secondary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: color var(--transition-fast), background var(--transition-fast);
|
||||
}
|
||||
.tasks-btn:hover {
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
.tasks-count-badge {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
min-width: 16px;
|
||||
height: 16px;
|
||||
padding: 0 4px;
|
||||
background: #ef4444;
|
||||
color: #fff;
|
||||
font-size: .62rem;
|
||||
font-weight: 700;
|
||||
border-radius: 9999px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
animation: task-badge-pop .25s ease;
|
||||
line-height: 1;
|
||||
}
|
||||
.tasks-count-badge.has-active {
|
||||
background: #667eea;
|
||||
animation: task-badge-pop .25s ease, task-pulse-dot 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
/* ── Status pulsing dot ───────────────────────────────────────── */
|
||||
.task-dot {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.task-dot.running { background: #667eea; animation: task-pulse-dot 1.2s ease-in-out infinite; }
|
||||
.task-dot.pending { background: #f97316; animation: task-pulse-dot 1.6s ease-in-out infinite; }
|
||||
.task-dot.completed { background: #10b981; }
|
||||
.task-dot.failed { background: #ef4444; }
|
||||
.task-dot.cancelled { background: #6b7280; }
|
||||
|
||||
/* ── Task Drawer (slide-in from right) ────────────────────────── */
|
||||
.task-drawer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 380px;
|
||||
max-width: 96vw;
|
||||
height: 100vh;
|
||||
background: var(--glass-bg, rgba(20,20,35,.95));
|
||||
backdrop-filter: blur(20px);
|
||||
border-left: 1px solid var(--glass-border, rgba(255,255,255,.08));
|
||||
z-index: 1200;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: -8px 0 40px rgba(0,0,0,.45);
|
||||
animation: task-drawer-in .25s ease;
|
||||
}
|
||||
.task-drawer-header {
|
||||
padding: 1.25rem 1.5rem;
|
||||
border-bottom: 1px solid var(--border-default);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.task-drawer-header h3 {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.task-drawer-close {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 4px 8px;
|
||||
border-radius: 6px;
|
||||
color: var(--text-muted);
|
||||
font-size: 1.25rem;
|
||||
line-height: 1;
|
||||
transition: color .15s, background .15s;
|
||||
}
|
||||
.task-drawer-close:hover { color: var(--text-primary); background: var(--bg-hover); }
|
||||
.task-drawer-body {
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
padding: .75rem 1rem;
|
||||
}
|
||||
.task-drawer-empty {
|
||||
text-align: center;
|
||||
padding: 3rem 1.5rem;
|
||||
color: var(--text-muted);
|
||||
font-size: .9rem;
|
||||
}
|
||||
.task-drawer-footer {
|
||||
padding: .75rem 1rem;
|
||||
border-top: 1px solid var(--border-default);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.task-drawer-footer a {
|
||||
display: block;
|
||||
text-align: center;
|
||||
font-size: .85rem;
|
||||
color: #667eea;
|
||||
text-decoration: none;
|
||||
padding: .5rem;
|
||||
border-radius: 8px;
|
||||
transition: background .15s;
|
||||
}
|
||||
.task-drawer-footer a:hover { background: rgba(102,126,234,.1); }
|
||||
|
||||
/* ── Mini task card (drawer) ──────────────────────────────────── */
|
||||
.task-mini-card {
|
||||
background: var(--bg-surface, rgba(255,255,255,.04));
|
||||
border: 1px solid var(--border-muted, rgba(255,255,255,.06));
|
||||
border-radius: 10px;
|
||||
padding: .75rem 1rem;
|
||||
margin-bottom: .5rem;
|
||||
transition: border-color .15s;
|
||||
}
|
||||
.task-mini-card:hover { border-color: var(--border-default); }
|
||||
.task-mini-card.running { border-left: 3px solid #667eea; }
|
||||
.task-mini-card.pending { border-left: 3px solid #f97316; }
|
||||
.task-mini-card.completed { border-left: 3px solid #10b981; }
|
||||
.task-mini-card.failed { border-left: 3px solid #ef4444; }
|
||||
.task-mini-card.cancelled { border-left: 3px solid #6b7280; }
|
||||
.task-mini-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
margin-bottom: .45rem;
|
||||
}
|
||||
.task-mini-name {
|
||||
font-size: .85rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.task-mini-time {
|
||||
font-size: .72rem;
|
||||
color: var(--text-muted);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.task-mini-progress { margin-top: .3rem; }
|
||||
|
||||
/* ── Full task card (page) ────────────────────────────────────── */
|
||||
.task-card {
|
||||
background: var(--glass-bg, rgba(255,255,255,.04));
|
||||
border: 1px solid var(--glass-border, rgba(255,255,255,.08));
|
||||
border-radius: 14px;
|
||||
padding: 1.25rem 1.5rem;
|
||||
margin-bottom: .75rem;
|
||||
transition: border-color .2s, box-shadow .2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.task-card:hover {
|
||||
border-color: rgba(102,126,234,.4);
|
||||
box-shadow: 0 4px 20px rgba(0,0,0,.2);
|
||||
}
|
||||
.task-card.running { border-left: 4px solid #667eea; }
|
||||
.task-card.pending { border-left: 4px solid #f97316; }
|
||||
.task-card.completed { border-left: 4px solid #10b981; }
|
||||
.task-card.failed { border-left: 4px solid #ef4444; }
|
||||
.task-card.cancelled { border-left: 4px solid #6b7280; }
|
||||
.task-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .75rem;
|
||||
margin-bottom: .75rem;
|
||||
}
|
||||
.task-card-title {
|
||||
flex: 1;
|
||||
font-weight: 600;
|
||||
font-size: .95rem;
|
||||
color: var(--text-primary);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.task-card-meta {
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
font-size: .8rem;
|
||||
color: var(--text-muted);
|
||||
margin-top: .6rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.task-card-meta span { display: flex; align-items: center; gap: .3rem; }
|
||||
|
||||
/* ── Trace modal ──────────────────────────────────────────────── */
|
||||
.task-trace-log {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: .78rem;
|
||||
line-height: 1.6;
|
||||
color: var(--text-secondary);
|
||||
background: var(--bg-input, rgba(0,0,0,.25));
|
||||
border: 1px solid var(--border-muted);
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
max-height: 320px;
|
||||
overflow-y: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
.task-results-table th { font-weight: 600; font-size: .8rem; color: var(--text-muted); }
|
||||
.task-results-table td { font-size: .82rem; padding: .5rem .75rem; vertical-align: middle; }
|
||||
.task-result-ok { color: #10b981; }
|
||||
.task-result-fail { color: #ef4444; }
|
||||
|
||||
/* ── Tab bar ──────────────────────────────────────────────────── */
|
||||
.tasks-tab-bar {
|
||||
display: flex;
|
||||
gap: .25rem;
|
||||
border-bottom: 2px solid var(--border-muted);
|
||||
margin-bottom: 1.5rem;
|
||||
overflow-x: auto;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.tasks-tab-bar::-webkit-scrollbar { display: none; }
|
||||
.tasks-tab {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: .6rem 1.1rem;
|
||||
font-size: .85rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-muted);
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -2px;
|
||||
transition: color .15s, border-color .15s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.tasks-tab:hover { color: var(--text-primary); }
|
||||
.tasks-tab.active { color: #667eea; border-bottom-color: #667eea; }
|
||||
.tasks-tab .tasks-tab-count {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
padding: 0 5px;
|
||||
background: var(--bg-surface-hover, rgba(255,255,255,.08));
|
||||
border-radius: 9999px;
|
||||
font-size: .7rem;
|
||||
font-weight: 700;
|
||||
margin-left: .35rem;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.tasks-tab.active .tasks-tab-count {
|
||||
background: rgba(102,126,234,.2);
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user