dockerbackup/public/index.html

837 lines
41 KiB
HTML

<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>DockerBackup</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="/styles.css" />
<link rel="icon" type="image/png" href="/icon.png" />
</head>
<body>
<div class="app-shell">
<!-- ===== SIDEBAR ===== -->
<nav class="sidebar">
<div class="sidebar-brand">
<img src="/icon.png" class="brand-icon" alt="DockerBackup" />
<span>DockerBackup</span>
</div>
<ul class="nav-list">
<li>
<button class="nav-item active" data-view="dashboard">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/></svg>
<span>Dashboard</span>
</button>
</li>
<li>
<button class="nav-item" data-view="source">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="2" width="20" height="8" rx="2" ry="2"/><rect x="2" y="14" width="20" height="8" rx="2" ry="2"/><line x1="6" y1="6" x2="6.01" y2="6"/><line x1="6" y1="18" x2="6.01" y2="18"/></svg>
<span data-i18n="nav.source">Origens</span>
</button>
</li>
<li>
<button class="nav-item" data-view="storage">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>
<span>Storage Locations</span>
</button>
</li>
<li>
<button class="nav-item" data-view="profiles">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 16 12 12 8 16"/><line x1="12" y1="12" x2="12" y2="21"/><path d="M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3"/></svg>
<span>Backup Profiles</span>
</button>
</li>
<li>
<button class="nav-item" data-view="runs">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>
<span>Backup Runs</span>
</button>
</li>
<li>
<button class="nav-item" data-view="backups">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="21 8 21 21 3 21 3 8"/><rect x="1" y="3" width="22" height="5"/><line x1="10" y1="12" x2="14" y2="12"/></svg>
<span data-i18n="nav.backups">Backups</span>
</button>
</li>
<li>
<button class="nav-item" data-view="schedules">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
<span>Agendamentos</span>
</button>
</li>
<li>
<button class="nav-item" data-view="settings">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>
<span data-i18n="nav.settings">Configurações</span>
</button>
</li>
<li>
<button class="nav-item" data-view="about">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
<span data-i18n="nav.about">Sobre</span>
</button>
</li>
</ul>
<div class="sidebar-footer">
<button id="logoutBtn" class="btn btn--ghost btn--sm sidebar-logout hidden" data-i18n="login.logout">Sair</button>
</div>
</nav>
<div class="main-content">
<!-- VIEW: Dashboard -->
<div id="view-dashboard" class="view">
<div class="page-header">
<h1 class="page-title">Dashboard</h1>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-info">
<span class="stat-label">Total Servers</span>
<strong id="containerCount" class="stat-value">0</strong>
<span class="stat-sub">Active connections</span>
</div>
<div class="stat-icon stat-icon--blue">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
</div>
</div>
<div class="stat-card">
<div class="stat-info">
<span class="stat-label">Backup Profiles</span>
<strong id="profileCount" class="stat-value">0</strong>
<span class="stat-sub">Configured profiles</span>
</div>
<div class="stat-icon stat-icon--blue">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 16 12 12 8 16"/><line x1="12" y1="12" x2="12" y2="21"/><path d="M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3"/></svg>
</div>
</div>
<div class="stat-card">
<div class="stat-info">
<span class="stat-label">Successful</span>
<strong id="lastBackupTime" class="stat-value">0</strong>
<span class="stat-sub">Total com sucesso</span>
</div>
<div class="stat-icon stat-icon--green">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
</div>
</div>
<div class="stat-card">
<div class="stat-info">
<span class="stat-label">Failed</span>
<strong id="failedCount" class="stat-value">0</strong>
<span class="stat-sub">Total com falha</span>
</div>
<div class="stat-icon stat-icon--red">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
</div>
</div>
</div>
<div class="card">
<div class="card-toolbar">
<h2 class="card-title">Recent Backup Runs</h2>
<button id="createProfileBtn" class="btn btn--primary">+ Create Profile</button>
</div>
<div class="table-wrap">
<table class="data-table">
<thead>
<tr>
<th>ID</th>
<th>Profile</th>
<th>Status</th>
<th>Files</th>
<th>Size</th>
<th>Started</th>
<th>Duration</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="recentRunsBody">
<tr><td colspan="8" class="empty-row">Carregando...</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- VIEW: Backup Profiles -->
<div id="view-profiles" class="view hidden">
<div class="page-header">
<h1 class="page-title">Backup Profiles</h1>
<div class="page-header-actions">
<button id="openCreateProfileModal" class="btn btn--primary">+ Criar Profile</button>
<button id="reloadProfiles" class="btn btn--secondary">Reload</button>
</div>
</div>
<div class="card">
<div id="profilesList" class="profiles-list"></div>
</div>
</div>
<!-- VIEW: Backup Runs -->
<div id="view-runs" class="view hidden">
<div class="page-header">
<h1 class="page-title">Backup Runs</h1>
<button id="refreshRuns" class="btn btn--secondary">Refresh</button>
</div>
<div class="card">
<div class="card-toolbar">
<h2 class="card-title">All Runs</h2>
</div>
<div class="table-wrap">
<table class="data-table">
<thead>
<tr>
<th>#</th>
<th>Profile</th>
<th>Mode</th>
<th>Status</th>
<th>Containers</th>
<th>Files</th>
<th>Size</th>
<th>Started</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="allRunsBody">
<tr><td colspan="9" class="empty-row">Nenhum run encontrado.</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- VIEW: Backups -->
<div id="view-backups" class="view hidden">
<div class="page-header">
<h1 class="page-title">Backups</h1>
</div>
<div id="backupsViewList"></div>
</div>
<!-- VIEW: Schedules -->
<div id="view-schedules" class="view hidden">
<div class="page-header">
<h1 class="page-title">Agendamentos</h1>
<button id="openCreateScheduleModal" class="btn btn--primary">+ Novo Agendamento</button>
</div>
<div class="card">
<div id="schedulesList"></div>
</div>
</div>
<!-- VIEW: Source (Origens) -->
<div id="view-source" class="view hidden">
<div class="page-header">
<h1 class="page-title" data-i18n="source.title">Origens</h1>
<button id="openCreateSourceModal" class="btn btn--primary" data-i18n="source.new">+ Nova Origem</button>
</div>
<div class="card" id="sourceCard">
<div id="sourcesList"></div>
</div>
</div>
<!-- VIEW: Storage Locations -->
<div id="view-storage" class="view hidden">
<div class="page-header">
<h1 class="page-title" data-i18n="storage.title">Storage Locations</h1>
<button id="openCreateStorageModal" class="btn btn--primary" data-i18n="storage.new">+ Novo Local</button>
</div>
<div class="card" id="storageLocationsCard">
<div id="storageLocationsList"></div>
</div>
</div>
<!-- VIEW: Settings -->
<div id="view-settings" class="view hidden">
<div class="page-header">
<h1 class="page-title" data-i18n="settings.title">Configurações</h1>
</div>
<div class="card settings-card">
<div class="settings-section">
<h2 data-i18n="settings.language">Idioma</h2>
<p data-i18n="settings.languageDesc">Selecione o idioma de toda a interface</p>
<select id="settingsLanguage" class="settings-select">
</select>
</div>
<div class="settings-section">
<h2>Tema</h2>
<p>Personalize a aparência da interface</p>
<select id="settingsTheme" class="settings-select">
<option value="default">Padrão (Azul)</option>
<option value="dark">Escuro</option>
<option value="sunrise">Amanhecer</option>
<option value="forest">Floresta</option>
<option value="ocean">Oceano</option>
<option value="purple">Púrpura</option>
<option value="rose">Rosa</option>
<option value="orange">Laranja</option>
<option value="graphite">Grafite</option>
<option value="sapphire">Safira</option>
<option value="contrast">Alto Contraste</option>
</select>
</div>
<div class="settings-section">
<h2 data-i18n="settings.auth">Controle de Acesso</h2>
<label class="toggle-label">
<input type="checkbox" id="settingsRequireAuth" />
<span data-i18n="settings.authEnabled">Exigir usuário e senha para acesso</span>
</label>
<div id="authFields" class="auth-fields hidden">
<div class="form-field">
<label for="settingsUsername" data-i18n="settings.username">Usuário</label>
<input id="settingsUsername" type="text" autocomplete="username" />
</div>
<div class="form-field">
<label for="settingsPassword" data-i18n="settings.password">Senha (deixe em branco para não alterar)</label>
<input id="settingsPassword" type="password" autocomplete="new-password" />
</div>
</div>
</div>
<div class="form-actions">
<button id="saveSettingsBtn" class="btn btn--primary" data-i18n="settings.saveSettings">Salvar configurações</button>
</div>
</div>
</div>
<!-- VIEW: About -->
<div id="view-about" class="view hidden">
<div class="page-header">
<h1 class="page-title" data-i18n="about.title">Sobre</h1>
</div>
<div class="card about-card">
<div class="about-logo-wrap">
<img src="/icon.png" class="about-logo" alt="DockerBackup" />
<h2>DockerBackup</h2>
</div>
<p class="about-description" data-i18n="about.description">Aplicação web para backup e restauração de volumes Docker com suporte a snapshots incrementais e restore seletivo.</p>
<p class="about-author">Desenvolvido por Alexander Sabino em 2026</p>
<div class="about-version-grid">
<div class="about-version-item">
<span class="about-version-label" data-i18n="about.currentVersion">Versão atual</span>
<strong id="aboutCurrentVersion"></strong>
</div>
<div class="about-version-item">
<span class="about-version-label" data-i18n="about.latestVersion">Última versão</span>
<strong id="aboutLatestVersion" data-i18n="about.checking">Verificando...</strong>
</div>
</div>
<div id="aboutUpdateWrap" class="about-update-wrap hidden">
<span id="aboutUpdateStatus" class="about-update-status"></span>
<button id="aboutUpdateBtn" class="btn btn--primary hidden" data-i18n="about.update">Atualizar agora</button>
</div>
<div class="about-changelog">
<h3 data-i18n="about.changelog">Últimas alterações</h3>
<div id="aboutChangelog" class="changelog-content">
<p class="changelog-loading">Carregando changelog...</p>
</div>
</div>
</div>
</div>
</div><!-- /.main-content -->
</div><!-- /.app-shell -->
<aside id="toast" class="toast hidden"></aside>
<!-- Login overlay -->
<div id="loginOverlay" class="login-overlay hidden" aria-hidden="true">
<div class="login-card" role="dialog" aria-modal="true" aria-labelledby="loginTitle">
<img src="/icon.png" class="login-logo" alt="DockerBackup" />
<h2 id="loginTitle" data-i18n="login.title">Acesso restrito</h2>
<p data-i18n="login.subtitle">Faça login para continuar</p>
<form id="loginForm" class="login-form">
<div class="form-field">
<label for="loginUsername" data-i18n="login.username">Usuário</label>
<input id="loginUsername" type="text" autocomplete="username" required />
</div>
<div class="form-field">
<label for="loginPassword" data-i18n="login.password">Senha</label>
<input id="loginPassword" type="password" autocomplete="current-password" required />
</div>
<p id="loginError" class="login-error hidden" data-i18n="login.error">Usuário ou senha incorretos.</p>
<button class="btn btn--primary btn--full" type="submit" data-i18n="login.submit">Entrar</button>
</form>
</div>
</div>
<div id="restoreModal" class="modal hidden" aria-hidden="true">
<div class="modal-backdrop" data-action="close-restore-modal"></div>
<div class="modal-card" role="dialog" aria-modal="true" aria-labelledby="restoreModalTitle">
<div class="modal-header">
<h3 id="restoreModalTitle">Selecionar containers para restore</h3>
<button id="restoreModalClose" class="btn btn--ghost btn--sm" type="button">Fechar</button>
</div>
<p id="restoreModalSubtitle" class="modal-subtitle"></p>
<div id="restoreContainerOptions" class="modal-options"></div>
<div class="modal-actions">
<button id="restoreModalSelectAll" class="btn btn--secondary" type="button">Marcar todos</button>
<button id="restoreModalConfirm" class="btn btn--primary" type="button">Restaurar selecionados</button>
</div>
</div>
</div>
<div id="volumePickerModal" class="modal hidden" aria-hidden="true">
<div class="modal-backdrop" data-action="close-volume-picker"></div>
<div class="modal-card" role="dialog" aria-modal="true" aria-labelledby="volumePickerTitle">
<div class="modal-header">
<h3 id="volumePickerTitle">Selecionar volumes para backup</h3>
<button id="volumePickerClose" class="btn btn--ghost btn--sm" type="button" data-action="close-volume-picker">Fechar</button>
</div>
<p id="volumePickerSubtitle" class="modal-subtitle"></p>
<div id="volumePickerOptions" class="modal-options"></div>
<div class="modal-actions">
<button id="volumePickerSelectAll" class="btn btn--secondary" type="button">Marcar todos</button>
<button id="volumePickerConfirm" class="btn btn--primary" type="button">Confirmar seleção</button>
</div>
</div>
</div>
<!-- Modal: Snapshot/browse de arquivos do backup -->
<div id="snapshotModal" class="modal hidden" aria-hidden="true">
<div class="modal-backdrop" data-action="close-snapshot-modal"></div>
<div class="modal-card modal-card--wide modal-card--snapshot" role="dialog" aria-modal="true" aria-labelledby="snapshotModalTitle">
<div class="modal-header">
<div>
<h3 id="snapshotModalTitle">Snapshot do Backup</h3>
<p id="snapshotModalSubtitle" class="modal-subtitle"></p>
</div>
<button id="snapshotModalClose" class="btn btn--ghost btn--sm" type="button">Fechar</button>
</div>
<div id="snapshotContainerTabs" class="snapshot-tabs hidden"></div>
<div class="snapshot-toolbar">
<input id="snapshotSearch" type="search" class="snapshot-search" placeholder="Filtrar arquivos..." />
<span id="snapshotStats" class="snapshot-stats"></span>
</div>
<div id="snapshotLoading" class="snapshot-loading hidden">
<span class="spinner-inline"></span> Carregando arquivos do archive...
</div>
<div id="snapshotFileList" class="snapshot-file-list"></div>
<div class="modal-actions">
<button id="snapshotSelectAll" class="btn btn--secondary" type="button">Selecionar todos</button>
<button id="snapshotExtract" class="btn btn--primary" type="button" disabled>Extrair selecionados</button>
</div>
</div>
</div>
<div id="profileFormModal" class="modal hidden" aria-hidden="true">
<div class="modal-backdrop" data-action="close-profile-modal"></div>
<div class="modal-card modal-card--wide" role="dialog" aria-modal="true" aria-labelledby="profileModalTitle">
<div class="modal-header">
<h3 id="profileModalTitle">Novo Profile</h3>
<button id="profileModalClose" class="btn btn--ghost btn--sm" type="button">Fechar</button>
</div>
<form id="profileForm" class="profile-form">
<input type="hidden" id="profileId" />
<div class="form-field">
<label for="profileName">Nome do profile</label>
<input id="profileName" name="name" type="text" placeholder="Backup banco principal" required />
</div>
<div class="form-field">
<label for="storageLocationId">Local de armazenamento</label>
<select id="storageLocationId" name="storageLocationId" required>
<option value="">Selecione um local...</option>
</select>
</div>
<div class="form-field">
<label for="profileSourceId" data-i18n="source.title">Origem Docker</label>
<select id="profileSourceId" name="sourceId">
<option value="" data-i18n="source.defaultSource">Padrão (socket local)</option>
</select>
</div>
<fieldset class="form-fieldset">
<legend>Escopo do backup</legend>
<div class="radio-grid">
<label class="radio-card">
<input type="radio" name="backupScope" value="volumes" checked />
<span>Somente volumes</span>
<small>mantém o comportamento atual de backup de volumes e binds</small>
</label>
<label class="radio-card">
<input type="radio" name="backupScope" value="container" />
<span>Container inteiro</span>
<small>gera um único tar por container a partir de /</small>
</label>
</div>
</fieldset>
<div class="container-picker">
<div class="picker-header">
<span class="picker-label">Containers</span>
<button id="refreshContainers" type="button" class="btn btn--ghost btn--sm">Atualizar lista</button>
</div>
<div id="containerOptions" class="container-options"></div>
</div>
<div class="form-actions">
<button class="btn btn--primary" type="submit">Salvar profile</button>
<button class="btn btn--secondary" type="button" id="clearForm">Cancelar</button>
</div>
</form>
</div>
</div>
<!-- Modal: Selecionar backup full base para incremental -->
<div id="fullBackupPickerModal" class="modal hidden" aria-hidden="true">
<div class="modal-backdrop" data-action="close-full-backup-picker"></div>
<div class="modal-card" role="dialog" aria-modal="true" aria-labelledby="fullBackupPickerTitle">
<div class="modal-header">
<h3 id="fullBackupPickerTitle">Selecionar backup full base</h3>
<button id="fullBackupPickerClose" class="btn btn--ghost btn--sm" type="button" data-action="close-full-backup-picker">Fechar</button>
</div>
<p class="modal-subtitle">Selecione o backup full que será utilizado como base para o backup incremental:</p>
<div id="fullBackupPickerOptions" class="modal-options"></div>
<div class="modal-actions">
<button id="fullBackupPickerConfirm" class="btn btn--primary" type="button">Confirmar</button>
</div>
</div>
</div>
<!-- Modal: Criar/Editar Source (Origem) -->
<div id="sourceFormModal" class="modal hidden" aria-hidden="true">
<div class="modal-backdrop" data-action="close-source-modal"></div>
<div class="modal-card" role="dialog" aria-modal="true" aria-labelledby="sourceModalTitle">
<div class="modal-header">
<h3 id="sourceModalTitle" data-i18n="source.newSource">Nova Origem</h3>
<button id="sourceModalClose" class="btn btn--ghost btn--sm" type="button" data-action="close-source-modal">Fechar</button>
</div>
<form id="sourceForm" class="profile-form">
<input type="hidden" id="sourceFormId" />
<div class="form-field">
<label for="sourceFormName" data-i18n="source.name">Nome</label>
<input id="sourceFormName" type="text" placeholder="Docker Local" required />
</div>
<fieldset class="form-fieldset">
<legend data-i18n="source.type">Tipo de conexão</legend>
<div class="radio-grid">
<label class="radio-card" id="unixSocketRadioCard">
<input type="radio" name="sourceType" value="unix-socket" id="sourceTypeUnixSocket" />
<span data-i18n="source.typeUnixSocket">Unix Socket</span>
<small data-i18n="source.typeUnixSocketDesc">/var/run/docker.sock</small>
</label>
<label class="radio-card">
<input type="radio" name="sourceType" value="direct" id="sourceTypeDirect" checked />
<span data-i18n="source.typeDirect">Conexão Direta</span>
<small data-i18n="source.typeDirectDesc">TCP porta 2375</small>
</label>
<label class="radio-card">
<input type="radio" name="sourceType" value="agent" id="sourceTypeAgent" />
<span data-i18n="source.typeAgent">Docker Agent</span>
<small data-i18n="source.typeAgentDesc">Via agente remoto</small>
</label>
</div>
<p id="unixSocketUnavailableMsg" class="form-hint form-hint--warn hidden" data-i18n="source.socketUnavailable">
Socket Unix não disponível neste ambiente.
</p>
</fieldset>
<div id="sourceHostFields">
<div class="form-field">
<label for="sourceFormHost" data-i18n="source.host">Host</label>
<input id="sourceFormHost" type="text" placeholder="192.168.1.100" />
</div>
<div class="form-field">
<label for="sourceFormPort" data-i18n="source.port">Porta</label>
<input id="sourceFormPort" type="number" placeholder="2375" min="1" max="65535" />
</div>
</div>
<div class="form-actions">
<button class="btn btn--primary" type="submit" data-i18n="source.save">Salvar</button>
<button class="btn btn--secondary" type="button" id="cancelSourceForm">Cancelar</button>
</div>
</form>
</div>
</div>
<!-- Modal: Criar/Editar Storage Location -->
<div id="storageLocationFormModal" class="modal hidden" aria-hidden="true">
<div class="modal-backdrop" data-action="close-storage-modal"></div>
<div class="modal-card modal-card--wide" role="dialog" aria-modal="true" aria-labelledby="storageModalTitle">
<div class="modal-header">
<h3 id="storageModalTitle">Novo Local de Armazenamento</h3>
<button id="storageModalClose" class="btn btn--ghost btn--sm" type="button" data-action="close-storage-modal">Fechar</button>
</div>
<form id="storageLocationForm" class="profile-form">
<input type="hidden" id="storageFormId" />
<div class="form-field">
<label for="storageLocationName">Nome</label>
<input id="storageLocationName" type="text" placeholder="Backup principal" required />
</div>
<!-- Tipo de storage -->
<fieldset class="form-fieldset">
<legend>Tipo de storage</legend>
<div class="radio-grid">
<label class="radio-card">
<input type="radio" name="storageType" value="local" checked />
<span>Local</span>
<small>Diretório no servidor</small>
</label>
<label class="radio-card">
<input type="radio" name="storageType" value="ftp" />
<span>FTP</span>
<small>File Transfer Protocol</small>
</label>
<label class="radio-card">
<input type="radio" name="storageType" value="sftp" />
<span>SFTP</span>
<small>SSH File Transfer</small>
</label>
<label class="radio-card">
<input type="radio" name="storageType" value="webdav" />
<span>WebDAV</span>
<small>Web Distributed Authoring</small>
</label>
<label class="radio-card">
<input type="radio" name="storageType" value="google-drive" />
<span>Google Drive</span>
<small>Google Drive API v3</small>
</label>
</div>
</fieldset>
<!-- Local: diretório -->
<div id="storageFieldsLocal">
<div class="form-field">
<label for="storageLocationDir">Diretório</label>
<div class="dir-input-group">
<input id="storageLocationDir" type="text" placeholder="/srv/docker-backups" />
<button type="button" id="browseDirBtn" class="btn btn--secondary btn--sm dir-browse-btn" title="Procurar diretório">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="16" height="16"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
</button>
</div>
</div>
</div>
<!-- FTP / SFTP: campos comuns -->
<div id="storageFieldsFtpSftp" class="hidden">
<div class="form-row-2col">
<div class="form-field">
<label for="storageHost">Host</label>
<input id="storageHost" type="text" placeholder="ftp.exemplo.com" autocomplete="off" />
</div>
<div class="form-field form-field--port">
<label for="storagePort">Porta</label>
<input id="storagePort" type="number" min="1" max="65535" placeholder="21" />
</div>
</div>
<div class="form-row-2col" style="grid-template-columns:1fr 1fr">
<div class="form-field">
<label for="storageUsername">Usuário</label>
<input id="storageUsername" type="text" autocomplete="off" />
</div>
<div class="form-field">
<label for="storagePassword">Senha</label>
<input id="storagePassword" type="password" autocomplete="new-password" />
</div>
</div>
<div class="form-field">
<label for="storageRemotePath">Caminho remoto</label>
<input id="storageRemotePath" type="text" placeholder="/backups" />
</div>
</div>
<!-- FTP apenas: modo passivo -->
<div id="storageFieldsFtpOnly" class="hidden">
<div class="form-field">
<label class="checkbox-row">
<input type="checkbox" id="storagePassive" />
<span>Modo passivo (PASV)</span>
</label>
</div>
</div>
<!-- SFTP apenas: chave privada -->
<div id="storageFieldsSftpOnly" class="hidden">
<div class="form-field">
<label for="storagePrivateKey">Chave privada SSH <small>(opcional — alternativa à senha)</small></label>
<textarea id="storagePrivateKey" rows="5" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----&#10;...&#10;-----END OPENSSH PRIVATE KEY-----"></textarea>
</div>
</div>
<!-- WebDAV -->
<div id="storageFieldsWebdav" class="hidden">
<div class="form-field">
<label for="storageWebdavUrl">URL do servidor WebDAV</label>
<input id="storageWebdavUrl" type="url" placeholder="https://cloud.exemplo.com/remote.php/webdav" />
</div>
<div class="form-row-2col" style="grid-template-columns:1fr 1fr">
<div class="form-field">
<label for="storageWebdavUsername">Usuário</label>
<input id="storageWebdavUsername" type="text" autocomplete="off" />
</div>
<div class="form-field">
<label for="storageWebdavPassword">Senha</label>
<input id="storageWebdavPassword" type="password" autocomplete="new-password" />
</div>
</div>
<div class="form-field">
<label for="storageWebdavRemotePath">Caminho remoto <small>(opcional)</small></label>
<input id="storageWebdavRemotePath" type="text" placeholder="/Backups/Docker" />
</div>
</div>
<!-- Google Drive -->
<div id="storageFieldsGdrive" class="hidden">
<div class="form-field">
<label for="storageGdriveClientId">Client ID</label>
<input id="storageGdriveClientId" type="text" autocomplete="off" placeholder="xxxx.apps.googleusercontent.com" />
</div>
<div class="form-field">
<label for="storageGdriveClientSecret">Client Secret</label>
<input id="storageGdriveClientSecret" type="password" autocomplete="new-password" />
</div>
<div class="form-field">
<label for="storageGdriveRefreshToken">Refresh Token</label>
<input id="storageGdriveRefreshToken" type="password" autocomplete="new-password" />
</div>
<div class="form-field">
<label for="storageGdriveFolderId">ID da pasta de destino <small>(opcional)</small></label>
<input id="storageGdriveFolderId" type="text" placeholder="1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms" />
</div>
</div>
<div class="form-actions">
<button class="btn btn--primary" type="submit">Salvar</button>
<button class="btn btn--secondary" type="button" id="cancelStorageForm">Cancelar</button>
</div>
</form>
</div>
</div>
<!-- Modal: Navegador de Diretórios -->
<div id="dirBrowserModal" class="modal hidden" aria-hidden="true">
<div class="modal-backdrop" id="dirBrowserBackdrop"></div>
<div class="modal-card dir-browser-card" role="dialog" aria-modal="true" aria-labelledby="dirBrowserTitle">
<div class="modal-header">
<h3 id="dirBrowserTitle">Selecionar Diretório</h3>
<button id="dirBrowserClose" class="btn btn--ghost btn--sm" type="button">Fechar</button>
</div>
<div class="dir-browser-breadcrumb">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="14" height="14"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>
<span id="dirBrowserCurrent" class="dir-browser-current-path"></span>
</div>
<div id="dirBrowserList" class="dir-browser-list"></div>
<div class="modal-actions">
<button id="dirBrowserSelect" class="btn btn--primary" type="button">Selecionar este diretório</button>
<button id="dirBrowserCancel" class="btn btn--secondary" type="button">Cancelar</button>
</div>
</div>
</div>
<!-- Modal: Criar/Editar Agendamento -->
<div id="scheduleFormModal" class="modal hidden" aria-hidden="true">
<div class="modal-backdrop" data-action="close-schedule-modal"></div>
<div class="modal-card modal-card--wide" role="dialog" aria-modal="true" aria-labelledby="scheduleModalTitle">
<div class="modal-header">
<h3 id="scheduleModalTitle">Novo Agendamento</h3>
<button id="scheduleModalClose" class="btn btn--ghost btn--sm" type="button">Fechar</button>
</div>
<form id="scheduleForm" class="profile-form">
<input type="hidden" id="scheduleId" />
<div class="form-field">
<label for="scheduleName">Nome do agendamento <small>(opcional)</small></label>
<input id="scheduleName" type="text" placeholder="Ex: Backup diário do banco" />
</div>
<div class="form-field">
<label for="scheduleProfileId">Profile de backup</label>
<select id="scheduleProfileId" required>
<option value="">Selecione um profile...</option>
</select>
</div>
<fieldset class="form-fieldset">
<legend>Tipo de backup</legend>
<div class="radio-grid">
<label class="radio-card">
<input type="radio" name="scheduleBackupMode" value="full" checked />
<span>Full</span>
<small>Backup completo de todos os volumes</small>
</label>
<label class="radio-card">
<input type="radio" name="scheduleBackupMode" value="incremental" />
<span>Incremental</span>
<small>Apenas alterações desde o último full</small>
</label>
</div>
</fieldset>
<div id="scheduleFullBackupField" class="form-field hidden">
<label for="scheduleBasedOnFullBackupId">Backup full base</label>
<select id="scheduleBasedOnFullBackupId">
<option value="">Auto (usar o mais recente disponível)</option>
</select>
<small class="form-hint">Para agendamentos recorrentes, recomenda-se &quot;Auto&quot;.</small>
</div>
<div class="form-field">
<label for="scheduleFrequency">Frequência</label>
<select id="scheduleFrequency" required>
<option value="once">Única vez</option>
<option value="daily" selected>Diária</option>
<option value="weekly">Semanal</option>
<option value="monthly">Mensal</option>
</select>
</div>
<div class="form-field">
<label for="scheduleDateTime">Data e hora do primeiro backup</label>
<input id="scheduleDateTime" type="datetime-local" required />
</div>
<div class="form-field">
<label class="toggle-label">
<input type="checkbox" id="scheduleEnabled" checked />
<span>Agendamento ativo</span>
</label>
</div>
<div class="form-actions">
<button class="btn btn--primary" type="submit">Salvar agendamento</button>
<button class="btn btn--secondary" type="button" id="cancelScheduleForm">Cancelar</button>
</div>
</form>
</div>
</div>
<!-- Modal: Log do Backup Run -->
<div id="runLogModal" class="modal hidden" aria-hidden="true">
<div class="modal-backdrop" data-action="close-run-log-modal"></div>
<div class="modal-card modal-card--wide" role="dialog" aria-modal="true" aria-labelledby="runLogTitle">
<div class="modal-header">
<h3 id="runLogTitle">Log do Backup</h3>
<button id="runLogModalClose" class="btn btn--ghost btn--sm" type="button">Fechar</button>
</div>
<div id="runLogContent" class="run-log-content">
<p class="changelog-loading">Carregando log...</p>
</div>
</div>
</div>
<script src="/translations.js"></script>
<script src="/app.js"></script>
</body>
</html>