Check Update

This commit is contained in:
2026-07-11 02:04:36 -03:00
parent 6f7fa2fad1
commit aea27916e8
11 changed files with 569 additions and 13 deletions
+11
View File
@@ -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;}
+16
View File
@@ -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;
+1
View File
@@ -124,6 +124,7 @@ function initAfterLogin() {
if (currentRole === "superadmin") {
loadDashboardStats();
if (typeof loadUpdateStatus === "function") loadUpdateStatus();
statsTimer = setInterval(() => {
loadDashboardStats();
if (currentTab === "stats") loadStats();
+119
View File
@@ -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);
}
});
+32 -11
View File
@@ -16,7 +16,7 @@
setTimeout(function(){document.documentElement.classList.remove("i18n-pending");},2500);
})();
</script>
<link rel="stylesheet" href="assets/app.css?v=20260511visualsafesave1"/>
<link rel="stylesheet" href="assets/app.css?v=20260711updatecheck1"/>
</head>
<body>
<div class="app">
@@ -158,6 +158,26 @@
</div>
</div>
<div class="card superadmin-only hidden" id="updateStatusCard" style="margin-top:18px;">
<div class="card-hdr">
<div class="card-title">Panel Updates <span class="chip" id="updateStateChip">Checking…</span></div>
<div class="card-actions">
<a class="btn btn-ghost btn-sm" id="updateRepoLink" href="https://git.dr2.site/penguinehis/DragonCoreSSH-NewWEB" target="_blank" rel="noopener noreferrer">Open Git</a>
<button class="btn btn-ghost btn-sm" type="button" id="checkUpdateBtn">Check now</button>
</div>
</div>
<div class="metrics">
<div class="metric"><div class="m-label">Installed version</div><div class="m-val update-commit" id="updateCurrentCommit">--</div></div>
<div class="metric"><div class="m-label">Latest Git version</div><div class="m-val update-commit" id="updateLatestCommit">--</div></div>
<div class="metric"><div class="m-label">Branch</div><div class="m-val update-commit" id="updateBranch">main</div></div>
<div class="metric"><div class="m-label">Last checked</div><div class="m-val update-check-time" id="updateCheckedAt">--</div></div>
</div>
<div class="statusbar update-statusbar">
<span id="updateStatusText">Comparing the installed version with the Git repository.</span>
<span class="hint hidden" id="updateCommandWrap">To update: <code>sudo bash /opt/sshpanel/update.sh</code> <button class="btn btn-ghost btn-xs" type="button" id="copyUpdateCommandBtn">Copy command</button></span>
</div>
</div>
<div class="grid2 dashboard-lower">
<div class="card hidden" id="dnsttDashboardCard">
<div class="card-hdr">
@@ -1241,15 +1261,16 @@
<!-- app.js was split into ordered modules for maintainability. They are plain
classic scripts sharing one global scope; `defer` preserves execution order,
so behavior is identical to the old single file. Keep this load order. -->
<script defer src="assets/js/01-core.js?v=20260704ytquicfix1"></script>
<script defer src="assets/js/02-shell.js?v=20260704ytquicfix1"></script>
<script defer src="assets/js/03-ssh-users.js?v=20260704ytquicfix1"></script>
<script defer src="assets/js/04-xray.js?v=20260704ytquicfix1"></script>
<script defer src="assets/js/05-resellers.js?v=20260704ytquicfix1"></script>
<script defer src="assets/js/06-servers.js?v=20260704ytquicfix1"></script>
<script defer src="assets/js/07-stats-logs.js?v=20260704ytquicfix1"></script>
<script defer src="assets/js/08-server-config.js?v=20260704ytquicfix1"></script>
<script defer src="assets/js/09-xray-wizard.js?v=20260704ytquicfix1"></script>
<script defer src="assets/js/10-boot.js?v=20260704ytquicfix1"></script>
<script defer src="assets/js/01-core.js?v=20260711updatecheck1"></script>
<script defer src="assets/js/02-shell.js?v=20260711updatecheck1"></script>
<script defer src="assets/js/03-ssh-users.js?v=20260711updatecheck1"></script>
<script defer src="assets/js/04-xray.js?v=20260711updatecheck1"></script>
<script defer src="assets/js/05-resellers.js?v=20260711updatecheck1"></script>
<script defer src="assets/js/06-servers.js?v=20260711updatecheck1"></script>
<script defer src="assets/js/07-stats-logs.js?v=20260711updatecheck1"></script>
<script defer src="assets/js/08-server-config.js?v=20260711updatecheck1"></script>
<script defer src="assets/js/09-xray-wizard.js?v=20260711updatecheck1"></script>
<script defer src="assets/js/11-update-status.js?v=20260711updatecheck1"></script>
<script defer src="assets/js/10-boot.js?v=20260711updatecheck1"></script>
</body>
</html>