diff --git a/README.md b/README.md index 946cd3a..48a54bf 100644 --- a/README.md +++ b/README.md @@ -286,6 +286,15 @@ https://git.dr2.site/penguinehis/DragonCoreSSH-NewWEB.git Depois ele recompila o binário e atualiza o painel web e os scripts auxiliares, mantendo as configurações e dados existentes. +O painel do superadmin também mostra um cartão **Atualizações do painel** na tela inicial. Ele compara o commit compilado no servidor com o commit mais recente da branch configurada no Git. A verificação usa cache de 5 minutos; o botão **Verificar agora** força uma nova consulta. + +Variáveis opcionais do serviço para apontar a verificação para outro Git/branch: + +```text +DRAGON_UPDATE_REPO_URL=https://git.dr2.site/penguinehis/DragonCoreSSH-NewWEB.git +DRAGON_UPDATE_BRANCH=main +``` + O update preserva: ```text @@ -788,6 +797,15 @@ https://git.dr2.site/penguinehis/DragonCoreSSH-NewWEB.git Then it rebuilds the binary and updates the web panel and helper scripts while keeping existing configuration and user data. +The superadmin dashboard also shows a **Panel Updates** card. It compares the commit compiled into the running server with the latest commit on the configured Git branch. Results are cached for 5 minutes; **Check now** forces a fresh lookup. + +Optional service variables for a different repository/branch: + +```text +DRAGON_UPDATE_REPO_URL=https://git.dr2.site/penguinehis/DragonCoreSSH-NewWEB.git +DRAGON_UPDATE_BRANCH=main +``` + The update preserves: ```text diff --git a/admin/assets/app.css b/admin/assets/app.css index 88fb670..7419ef5 100644 --- a/admin/assets/app.css +++ b/admin/assets/app.css @@ -638,3 +638,14 @@ select:disabled { color:#f3f7ff; font-size:.78rem; } + +/* Git update status card */ +.update-commit{ + font-family:ui-monospace,SFMono-Regular,Consolas,"Liberation Mono",monospace; + font-size:1rem!important; + letter-spacing:.01em!important; + overflow-wrap:anywhere; +} +.update-check-time{font-size:.9rem!important;letter-spacing:0!important;} +.update-statusbar{align-items:center;gap:12px;flex-wrap:wrap;} +.btn-xs{padding:5px 8px!important;font-size:.67rem!important;border-radius:9px!important;} diff --git a/admin/assets/js/01-core.js b/admin/assets/js/01-core.js index d8d01b4..7700f3e 100644 --- a/admin/assets/js/01-core.js +++ b/admin/assets/js/01-core.js @@ -96,6 +96,22 @@ Object.assign(I18N_ALIASES, { "Public Key — share with dnstt clients":"Public Key — share with dnstt clients","Chave pública — compartilhe com clientes DNSTT":"Public Key — share with dnstt clients", "Máx. sessões UDP por cliente":"Max UDP Sessions Per Client","(não é o total de usuários do servidor)":"(not total server users)","Nome do serviço":"Service Name","Modo":"Mode","Protocolo":"Protocol","Porta":"Port","IP de listen":"Listen IP","Método":"Method","Caminho":"Path","Destino":"Dest","ID curto":"Short ID","Nome do servidor":"Server Name","Caminho do arquivo cert":"Cert File Path","Caminho do arquivo key":"Key File Path","Fonte do certificado:":"Certificate source:","Autoassinado":"Self-Signed","Colar PEM":"Paste PEM","Caminho do arquivo":"File Path","Salvar PEM":"Save PEM","Intervalo de reinício automático":"Auto Restart Interval","Atraso para reiniciar":"Restart Grace Delay","0s/off desativa":"0s/off disables","Chave pública":"Public Key","Nome do domínio":"Domain Name" }); +Object.assign(I18N_TEXT["en-US"], { + "Panel Updates":"Panel Updates","Checking…":"Checking…","Open Git":"Open Git","Check now":"Check now","Installed version":"Installed version","Latest Git version":"Latest Git version","Branch":"Branch","Last checked":"Last checked", + "Comparing the installed version with the Git repository.":"Comparing the installed version with the Git repository.","To update:":"To update:","Copy command":"Copy command","Up to date":"Up to date","Update available":"Update available","Local changes":"Local changes","Unknown":"Unknown", + "The installed version matches the latest commit on {branch}.":"The installed version matches the latest commit on {branch}.","A newer commit is available on {branch}.":"A newer commit is available on {branch}.","This build contains local changes, so it cannot be compared safely.":"This build contains local changes, so it cannot be compared safely.", + "Could not check for updates: {error}":"Could not check for updates: {error}","The update status could not be determined.":"The update status could not be determined.","Checking repository…":"Checking repository…","Update check timed out.":"Update check timed out.","Could not reach the Git repository.":"Could not reach the Git repository.","Current build commit is unavailable.":"Current build commit is unavailable.","Unknown update-check error.":"Unknown update-check error.","Copied":"Copied" +}); +Object.assign(I18N_TEXT["pt-BR"], { + "Panel Updates":"Atualizações do painel","Checking…":"Verificando…","Open Git":"Abrir Git","Check now":"Verificar agora","Installed version":"Versão instalada","Latest Git version":"Última versão no Git","Branch":"Branch","Last checked":"Última verificação", + "Comparing the installed version with the Git repository.":"Comparando a versão instalada com o repositório Git.","To update:":"Para atualizar:","Copy command":"Copiar comando","Up to date":"Atualizado","Update available":"Atualização disponível","Local changes":"Alterações locais","Unknown":"Desconhecido", + "The installed version matches the latest commit on {branch}.":"A versão instalada corresponde ao commit mais recente da branch {branch}.","A newer commit is available on {branch}.":"Existe um commit mais recente disponível na branch {branch}.","This build contains local changes, so it cannot be compared safely.":"Esta compilação contém alterações locais e não pode ser comparada com segurança.", + "Could not check for updates: {error}":"Não foi possível verificar atualizações: {error}","The update status could not be determined.":"Não foi possível determinar o status da atualização.","Checking repository…":"Verificando o repositório…","Update check timed out.":"A verificação de atualização excedeu o tempo limite.","Could not reach the Git repository.":"Não foi possível acessar o repositório Git.","Current build commit is unavailable.":"O commit da compilação atual não está disponível.","Unknown update-check error.":"Erro desconhecido ao verificar atualização.","Copied":"Copiado" +}); +Object.assign(I18N_ALIASES, { + "Atualizações do painel":"Panel Updates","Verificando…":"Checking…","Abrir Git":"Open Git","Verificar agora":"Check now","Versão instalada":"Installed version","Última versão no Git":"Latest Git version","Última verificação":"Last checked", + "Comparando a versão instalada com o repositório Git.":"Comparing the installed version with the Git repository.","Para atualizar:":"To update:","Copiar comando":"Copy command","Atualizado":"Up to date","Atualização disponível":"Update available","Alterações locais":"Local changes","Desconhecido":"Unknown" +}); const I18N_REVERSE = Object.fromEntries(SUPPORTED_LANGS.map(lang => [lang, Object.fromEntries(Object.entries(I18N_TEXT[lang] || {}).map(([k, v]) => [v, k]))])); let currentLang = detectInitialLanguage(); let i18nTranslating = false; diff --git a/admin/assets/js/02-shell.js b/admin/assets/js/02-shell.js index bd467bf..0bec7eb 100644 --- a/admin/assets/js/02-shell.js +++ b/admin/assets/js/02-shell.js @@ -124,6 +124,7 @@ function initAfterLogin() { if (currentRole === "superadmin") { loadDashboardStats(); + if (typeof loadUpdateStatus === "function") loadUpdateStatus(); statsTimer = setInterval(() => { loadDashboardStats(); if (currentTab === "stats") loadStats(); diff --git a/admin/assets/js/11-update-status.js b/admin/assets/js/11-update-status.js new file mode 100644 index 0000000..bde9825 --- /dev/null +++ b/admin/assets/js/11-update-status.js @@ -0,0 +1,119 @@ +// ─── Git update status ─────────────────────────────────────────────────────── +const DRAGON_UPDATE_COMMAND = "sudo bash /opt/sshpanel/update.sh"; + +function updateStatusErrorText(error) { + switch (String(error || "")) { + case "remote update check timed out": + return t("Update check timed out."); + case "could not read the remote Git branch": + return t("Could not reach the Git repository."); + case "current build commit is unavailable": + return t("Current build commit is unavailable."); + default: + return error || t("Unknown update-check error."); + } +} + +function setUpdateState(label, tone = "") { + const chip = document.getElementById("updateStateChip"); + if (!chip) return; + chip.className = "chip" + (tone ? ` ${tone}` : ""); + chip.textContent = label; +} + +function setCommitValue(elementID, shortValue, fullValue) { + const el = document.getElementById(elementID); + if (!el) return; + el.textContent = shortValue || "--"; + el.title = fullValue || ""; +} + +function renderUpdateStatus(data) { + setCommitValue("updateCurrentCommit", data.current_commit_short, data.current_commit); + setCommitValue("updateLatestCommit", data.latest_commit_short, data.latest_commit); + + const branch = document.getElementById("updateBranch"); + if (branch) branch.textContent = data.branch || "main"; + + const checked = document.getElementById("updateCheckedAt"); + if (checked) { + const date = data.checked_at ? new Date(data.checked_at) : null; + checked.textContent = date && Number.isFinite(date.getTime()) ? date.toLocaleString() : "--"; + } + + const repoLink = document.getElementById("updateRepoLink"); + if (repoLink && data.repo_web_url) repoLink.href = data.repo_web_url; + + const text = document.getElementById("updateStatusText"); + const commandWrap = document.getElementById("updateCommandWrap"); + commandWrap?.classList.toggle("hidden", !data.update_available); + + if (data.status === "up_to_date") { + setUpdateState(t("Up to date"), "green"); + if (text) text.textContent = t("The installed version matches the latest commit on {branch}.", { branch: data.branch || "main" }); + return; + } + + if (data.status === "update_available") { + setUpdateState(t("Update available"), "warn"); + if (text) text.textContent = t("A newer commit is available on {branch}.", { branch: data.branch || "main" }); + return; + } + + if (data.status === "local_changes") { + setUpdateState(t("Local changes"), "warn"); + if (text) text.textContent = t("This build contains local changes, so it cannot be compared safely."); + return; + } + + setUpdateState(t("Unknown"), "red"); + if (text) { + text.textContent = data.error + ? t("Could not check for updates: {error}", { error: updateStatusErrorText(data.error) }) + : t("The update status could not be determined."); + } +} + +async function loadUpdateStatus(force = false) { + if (currentRole !== "superadmin") return; + + const button = document.getElementById("checkUpdateBtn"); + const text = document.getElementById("updateStatusText"); + if (button) button.disabled = true; + setUpdateState(t("Checking…")); + if (text) text.textContent = t("Checking repository…"); + + try { + const path = "/api/system/update-status" + (force ? "?refresh=1" : ""); + const res = await api(path); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + renderUpdateStatus(await res.json()); + } catch (error) { + setUpdateState(t("Unknown"), "red"); + if (text) text.textContent = t("Could not check for updates: {error}", { error: error.message || t("Network error.") }); + } finally { + if (button) button.disabled = false; + } +} + +document.getElementById("checkUpdateBtn")?.addEventListener("click", () => loadUpdateStatus(true)); +document.getElementById("copyUpdateCommandBtn")?.addEventListener("click", async () => { + try { + await navigator.clipboard.writeText(DRAGON_UPDATE_COMMAND); + } catch { + const input = document.createElement("textarea"); + input.value = DRAGON_UPDATE_COMMAND; + input.style.position = "fixed"; + input.style.opacity = "0"; + document.body.appendChild(input); + input.select(); + document.execCommand("copy"); + input.remove(); + } + const button = document.getElementById("copyUpdateCommandBtn"); + if (button) { + const oldText = button.textContent; + button.textContent = t("Copied"); + setTimeout(() => { button.textContent = oldText; }, 1400); + } +}); diff --git a/admin/index.html b/admin/index.html index a4bec05..e41de4c 100644 --- a/admin/index.html +++ b/admin/index.html @@ -16,7 +16,7 @@ setTimeout(function(){document.documentElement.classList.remove("i18n-pending");},2500); })(); - +