diff --git a/README.md b/README.md index c98bae5..427bc6b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@

- + @@ -18,12 +18,24 @@ > ⚠️ **AVISO CRÍTICO:** Aplicação em estágio inicial de desenvolvimento. Não use em produção — há risco de perda de dados. -Versão atual: **0.0.5** +Versão atual: **0.0.6** --- ## � Changelog +### [0.0.6] — 2026-05-09 + +#### Adicionado +- **Seletor de temas:** nova seção na aba Configurações com 11 temas visuais (Padrão, Escuro, Amanhecer, Floresta, Oceano, Púrpura, Rosa, Laranja, Grafite, Safira, Alto Contraste). O tema selecionado é aplicado imediatamente e salvo no navegador. +- **Changelog dinâmico:** a aba Sobre agora busca e exibe o changelog diretamente do `README.md` do GitHub, sem necessidade de atualização manual na interface. + +#### Corrigido +- **Botões Run/Editar/Excluir:** estilizados usando o sistema de design existente (`.btn`). Run ficou azul/primário, Editar em cinza/secundário e Excluir em vermelho com borda. +- **`docker-compose.yml`:** `restart: unless-stopped` descomentado para garantir que o container reinicie automaticamente após uma atualização via botão da aba Sobre. + +--- + ### [0.0.5] — 2026-05-09 #### Adicionado diff --git a/package.json b/package.json index fd00261..e396fa3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dockerbackup-app", - "version": "0.0.5", + "version": "0.0.6", "description": "Aplicacao web para backup e restauracao de volumes Docker", "main": "src/server.js", "scripts": { diff --git a/public/app.js b/public/app.js index d39a174..189d3e2 100644 --- a/public/app.js +++ b/public/app.js @@ -16,6 +16,30 @@ function applyTranslations() { }); } +// ─── Themes ─────────────────────────────────────────────── +const VALID_THEMES = new Set([ + 'default','dark','sunrise','forest','ocean','purple','rose','orange','graphite','sapphire','contrast' +]); + +function applyTheme(theme) { + const safe = VALID_THEMES.has(theme) ? theme : 'default'; + if (safe === 'default') { + document.documentElement.removeAttribute('data-theme'); + } else { + document.documentElement.setAttribute('data-theme', safe); + } + localStorage.setItem('theme', safe); + const sel = document.querySelector('#settingsTheme'); + if (sel) sel.value = safe; +} + +// Apply saved theme immediately +applyTheme(localStorage.getItem('theme') || 'default'); + +document.addEventListener('change', (e) => { + if (e.target.id === 'settingsTheme') applyTheme(e.target.value); +}); + // ─── Auth ────────────────────────────────────────────────── let authToken = localStorage.getItem('authToken') || null; @@ -688,9 +712,9 @@ function backupButtons(profile) {

- - - + + +
`; @@ -1431,6 +1455,8 @@ function buildLanguageSelect() { async function loadSettingsView() { buildLanguageSelect(); + const themeSelect = document.querySelector('#settingsTheme'); + if (themeSelect) themeSelect.value = localStorage.getItem('theme') || 'default'; try { const settings = await api('/api/settings'); const select = document.querySelector('#settingsLanguage'); @@ -1471,17 +1497,66 @@ document.querySelector('#saveSettingsBtn')?.addEventListener('click', async () = }); // ─── About ──────────────────────────────────────────────── +function markdownInline(raw) { + return raw + .replace(/&/g, '&').replace(//g, '>') + .replace(/\*\*([^*]+)\*\*/g, '$1') + .replace(/`([^`]+)`/g, '$1'); +} + +function parseChangelogSection(markdown) { + // Extract from ## Changelog (or ## 🗂 Changelog etc.) to next ## + const start = markdown.search(/^##\s+[^\n]*[Cc]hangelog/m); + if (start === -1) return null; + const rest = markdown.slice(start); + const nextSection = rest.slice(1).search(/^## /m); + const section = nextSection === -1 ? rest : rest.slice(0, nextSection + 1); + + const lines = section.split('\n'); + let html = ''; + let inList = false; + + for (const line of lines) { + const h3 = line.match(/^###\s+(.+)/); + const h4 = line.match(/^####\s+(.+)/); + const li = line.match(/^-\s+(.+)/); + + if (h3) { + if (inList) { html += ''; inList = false; } + html += `

${markdownInline(h3[1].replace(/\[([^\]]+)\]/, '$1'))}

`; + } else if (h4) { + if (inList) { html += ''; inList = false; } + html += `
${markdownInline(h4[1])}
`; + } else if (li) { + if (!inList) { html += ''; inList = false; } + } + } + if (inList) html += ''; + return html || null; +} + async function loadAboutView() { const currentVerEl = document.querySelector('#aboutCurrentVersion'); const latestVerEl = document.querySelector('#aboutLatestVersion'); const updateWrap = document.querySelector('#aboutUpdateWrap'); const updateStatus = document.querySelector('#aboutUpdateStatus'); const updateBtn = document.querySelector('#aboutUpdateBtn'); + const changelogEl = document.querySelector('#aboutChangelog'); if (latestVerEl) latestVerEl.textContent = t('about.checking'); - try { - const about = await api('/api/about'); + // Fetch version info and changelog in parallel + const [aboutResult, changelogResult] = await Promise.allSettled([ + api('/api/about'), + fetch('https://raw.githubusercontent.com/asabino2/dockerbackup/main/README.md').then((r) => r.text()), + ]); + + // Version info + if (aboutResult.status === 'fulfilled') { + const about = aboutResult.value; const current = about.currentVersion || '—'; const latest = about.latestVersion || null; @@ -1499,10 +1574,20 @@ async function loadAboutView() { if (updateStatus) updateStatus.textContent = t('about.checkError'); if (updateBtn) updateBtn.classList.add('hidden'); } - } catch (error) { + } else { if (currentVerEl) currentVerEl.textContent = '—'; if (latestVerEl) latestVerEl.textContent = '—'; - showToast(error.message, true); + showToast(aboutResult.reason?.message || 'Erro ao buscar versão', true); + } + + // Changelog from GitHub README + if (changelogEl) { + if (changelogResult.status === 'fulfilled') { + const html = parseChangelogSection(changelogResult.value); + changelogEl.innerHTML = html || '

Changelog não encontrado.

'; + } else { + changelogEl.innerHTML = '

Não foi possível carregar o changelog.

'; + } } } diff --git a/public/index.html b/public/index.html index e08d50e..33eaeb3 100644 --- a/public/index.html +++ b/public/index.html @@ -228,6 +228,23 @@ +
+

Tema

+

Personalize a aparência da interface

+ +

Controle de Acesso

diff --git a/public/styles.css b/public/styles.css index 99237e5..e7c25bc 100644 --- a/public/styles.css +++ b/public/styles.css @@ -27,6 +27,168 @@ --shadow-md: 0 4px 12px rgba(0,0,0,0.08); } +/* --- Themes --------------------------------------------- */ +[data-theme="dark"] { + --sidebar-bg: #0a0a14; + --sidebar-text: #8892a4; + --sidebar-text-active: #e2e8f0; + --sidebar-active-bg: rgba(255,255,255,0.1); + --sidebar-hover-bg: rgba(255,255,255,0.06); + --sidebar-accent: #60a5fa; + --bg: #0f172a; + --surface: #1e293b; + --border: #334155; + --text: #e2e8f0; + --text-muted: #94a3b8; + --text-sm: #cbd5e1; + --accent: #60a5fa; + --accent-hover: #3b82f6; +} +[data-theme="sunrise"] { + --sidebar-bg: #7c2d12; + --sidebar-text: #fed7aa; + --sidebar-text-active: #fff7ed; + --sidebar-active-bg: rgba(255,255,255,0.12); + --sidebar-hover-bg: rgba(255,255,255,0.07); + --sidebar-accent: #fb923c; + --bg: #fff7ed; + --surface: #ffffff; + --border: #fed7aa; + --text: #431407; + --text-muted: #9a3412; + --text-sm: #7c2d12; + --accent: #ea580c; + --accent-hover: #c2410c; +} +[data-theme="forest"] { + --sidebar-bg: #14532d; + --sidebar-text: #bbf7d0; + --sidebar-text-active: #f0fdf4; + --sidebar-active-bg: rgba(255,255,255,0.1); + --sidebar-hover-bg: rgba(255,255,255,0.06); + --sidebar-accent: #4ade80; + --bg: #f0fdf4; + --surface: #ffffff; + --border: #bbf7d0; + --text: #14532d; + --text-muted: #166534; + --text-sm: #15803d; + --accent: #16a34a; + --accent-hover: #15803d; +} +[data-theme="ocean"] { + --sidebar-bg: #164e63; + --sidebar-text: #a5f3fc; + --sidebar-text-active: #ecfeff; + --sidebar-active-bg: rgba(255,255,255,0.1); + --sidebar-hover-bg: rgba(255,255,255,0.06); + --sidebar-accent: #22d3ee; + --bg: #ecfeff; + --surface: #ffffff; + --border: #a5f3fc; + --text: #164e63; + --text-muted: #0e7490; + --text-sm: #0891b2; + --accent: #0891b2; + --accent-hover: #0e7490; +} +[data-theme="purple"] { + --sidebar-bg: #3b0764; + --sidebar-text: #e9d5ff; + --sidebar-text-active: #faf5ff; + --sidebar-active-bg: rgba(255,255,255,0.1); + --sidebar-hover-bg: rgba(255,255,255,0.06); + --sidebar-accent: #c084fc; + --bg: #faf5ff; + --surface: #ffffff; + --border: #e9d5ff; + --text: #3b0764; + --text-muted: #7e22ce; + --text-sm: #6b21a8; + --accent: #9333ea; + --accent-hover: #7e22ce; +} +[data-theme="rose"] { + --sidebar-bg: #881337; + --sidebar-text: #fecdd3; + --sidebar-text-active: #fff1f2; + --sidebar-active-bg: rgba(255,255,255,0.1); + --sidebar-hover-bg: rgba(255,255,255,0.06); + --sidebar-accent: #fb7185; + --bg: #fff1f2; + --surface: #ffffff; + --border: #fecdd3; + --text: #881337; + --text-muted: #be123c; + --text-sm: #9f1239; + --accent: #e11d48; + --accent-hover: #be123c; +} +[data-theme="orange"] { + --sidebar-bg: #431407; + --sidebar-text: #fed7aa; + --sidebar-text-active: #fff7ed; + --sidebar-active-bg: rgba(255,255,255,0.1); + --sidebar-hover-bg: rgba(255,255,255,0.06); + --sidebar-accent: #fb923c; + --bg: #fff7ed; + --surface: #ffffff; + --border: #fed7aa; + --text: #1c1917; + --text-muted: #78350f; + --text-sm: #92400e; + --accent: #f97316; + --accent-hover: #ea580c; +} +[data-theme="graphite"] { + --sidebar-bg: #111827; + --sidebar-text: #9ca3af; + --sidebar-text-active: #f9fafb; + --sidebar-active-bg: rgba(255,255,255,0.08); + --sidebar-hover-bg: rgba(255,255,255,0.04); + --sidebar-accent: #9ca3af; + --bg: #f3f4f6; + --surface: #ffffff; + --border: #d1d5db; + --text: #111827; + --text-muted: #6b7280; + --text-sm: #374151; + --accent: #374151; + --accent-hover: #1f2937; +} +[data-theme="sapphire"] { + --sidebar-bg: #1e1b4b; + --sidebar-text: #c7d2fe; + --sidebar-text-active: #eef2ff; + --sidebar-active-bg: rgba(255,255,255,0.1); + --sidebar-hover-bg: rgba(255,255,255,0.06); + --sidebar-accent: #818cf8; + --bg: #eef2ff; + --surface: #ffffff; + --border: #c7d2fe; + --text: #1e1b4b; + --text-muted: #4338ca; + --text-sm: #3730a3; + --accent: #4f46e5; + --accent-hover: #4338ca; +} +[data-theme="contrast"] { + --sidebar-bg: #000000; + --sidebar-text: #e5e5e5; + --sidebar-text-active: #ffff00; + --sidebar-active-bg: rgba(255,255,0,0.15); + --sidebar-hover-bg: rgba(255,255,255,0.08); + --sidebar-accent: #ffff00; + --bg: #ffffff; + --surface: #ffffff; + --border: #000000; + --text: #000000; + --text-muted: #333333; + --text-sm: #1a1a1a; + --accent: #0000cc; + --accent-hover: #000099; +} + body { font-family: 'Inter', sans-serif; font-size: 14px; @@ -279,9 +441,17 @@ button, input, select { } .btn--ghost { + background: transparent; + color: var(--text-muted); + border: 1px solid var(--border); +} + +.btn--danger { background: #fff0f0; color: var(--danger); + border: 1px solid #fecaca; } +.btn--danger:not(:disabled):hover { background: #fee2e2; opacity: 1; } .btn--sm { padding: 5px 11px; font-size: 12.5px; } @@ -1020,6 +1190,19 @@ button, input, select { font-size: 0.82rem; color: var(--text-muted); } +.changelog-content h5.changelog-section { + font-size: 0.78rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--text-muted); + margin: 8px 0 4px; +} +.changelog-loading { + font-size: 0.82rem; + color: var(--text-muted); + font-style: italic; +} /* --- Modal ----------------------------------------------- */ .modal {