mirror of
https://github.com/LamGC/quickly-conf-sshd.git
synced 2026-10-12 23:33:56 +00:00
feat: independently publish server SSH keys with safe sync and Worker homepage
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
(() => {
|
||||
"use strict";
|
||||
|
||||
const themeKey = "quickly-conf-sshd-theme";
|
||||
const root = document.documentElement;
|
||||
const themePicker = document.getElementById("theme-picker");
|
||||
const status = document.getElementById("copy-status");
|
||||
const dialog = document.getElementById("copy-fallback");
|
||||
const fallbackText = document.getElementById("fallback-text");
|
||||
const closeFallback = document.getElementById("close-copy-fallback");
|
||||
const allKeysButton = document.getElementById("copy-all-keys");
|
||||
const validThemes = new Set(["system", "light", "dark"]);
|
||||
const resetTimers = new WeakMap();
|
||||
let fallbackTrigger = null;
|
||||
let statusTimer;
|
||||
|
||||
function applyTheme(theme) {
|
||||
const choice = validThemes.has(theme) ? theme : "system";
|
||||
if (choice === "system") root.removeAttribute("data-theme");
|
||||
else root.setAttribute("data-theme", choice);
|
||||
if (themePicker) themePicker.value = choice;
|
||||
return choice;
|
||||
}
|
||||
|
||||
let savedTheme = "system";
|
||||
try {
|
||||
savedTheme = localStorage.getItem(themeKey) || "system";
|
||||
} catch {
|
||||
// Storage may be unavailable in private or restricted browsing contexts.
|
||||
}
|
||||
applyTheme(savedTheme);
|
||||
themePicker?.addEventListener("change", () => {
|
||||
const choice = applyTheme(themePicker.value);
|
||||
try {
|
||||
localStorage.setItem(themeKey, choice);
|
||||
} catch {
|
||||
// The selected theme still applies for this visit.
|
||||
}
|
||||
});
|
||||
|
||||
function announce(message) {
|
||||
if (!status) return;
|
||||
clearTimeout(statusTimer);
|
||||
status.textContent = "";
|
||||
statusTimer = setTimeout(() => { status.textContent = message; }, 30);
|
||||
}
|
||||
|
||||
function showManualCopy(text, trigger) {
|
||||
announce("自动复制未成功。请在手动复制窗口中复制已选中的完整内容。");
|
||||
if (!dialog || !fallbackText) {
|
||||
announce("自动复制未成功。请手动选择页面中的完整内容并复制。");
|
||||
return;
|
||||
}
|
||||
fallbackTrigger = trigger;
|
||||
fallbackText.value = text;
|
||||
if (!dialog.open) {
|
||||
if (typeof dialog.showModal === "function") dialog.showModal();
|
||||
else dialog.setAttribute("open", "");
|
||||
}
|
||||
fallbackText.focus();
|
||||
fallbackText.select();
|
||||
}
|
||||
|
||||
closeFallback?.addEventListener("click", () => {
|
||||
if (typeof dialog.close === "function") dialog.close();
|
||||
else {
|
||||
dialog.removeAttribute("open");
|
||||
fallbackTrigger?.focus();
|
||||
}
|
||||
});
|
||||
dialog?.addEventListener("close", () => { fallbackTrigger?.focus(); });
|
||||
|
||||
async function copy(text, button) {
|
||||
if (button.disabled || typeof text !== "string" || text.length === 0) return;
|
||||
const label = button.dataset.copyLabel || button.getAttribute("aria-label") || "复制";
|
||||
button.dataset.copyLabel = label;
|
||||
clearTimeout(resetTimers.get(button));
|
||||
delete button.dataset.state;
|
||||
button.setAttribute("aria-label", label);
|
||||
button.disabled = true;
|
||||
button.setAttribute("aria-busy", "true");
|
||||
try {
|
||||
if (!navigator.clipboard || typeof navigator.clipboard.writeText !== "function") {
|
||||
throw new Error("Clipboard unavailable");
|
||||
}
|
||||
await navigator.clipboard.writeText(text);
|
||||
button.dataset.state = "success";
|
||||
button.setAttribute("aria-label", `${label},已复制`);
|
||||
button.setAttribute("title", "已复制");
|
||||
announce("已复制完整内容。");
|
||||
resetTimers.set(button, setTimeout(() => {
|
||||
delete button.dataset.state;
|
||||
button.setAttribute("aria-label", label);
|
||||
button.setAttribute("title", label);
|
||||
}, 2000));
|
||||
} catch {
|
||||
button.setAttribute("title", "自动复制未成功,点击重试");
|
||||
showManualCopy(text, button);
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
button.removeAttribute("aria-busy");
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelectorAll(".code-block pre").forEach((pre) => {
|
||||
pre.setAttribute("tabindex", "0");
|
||||
});
|
||||
document.querySelectorAll("[data-copy-target]").forEach((button) => {
|
||||
const target = document.getElementById(button.dataset.copyTarget);
|
||||
if (!target) {
|
||||
button.disabled = true;
|
||||
button.setAttribute("title", "复制内容不可用");
|
||||
return;
|
||||
}
|
||||
button.addEventListener("click", () => copy(target.textContent, button));
|
||||
});
|
||||
|
||||
let keys = [];
|
||||
try {
|
||||
const release = JSON.parse(document.getElementById("release-data")?.textContent || "{}");
|
||||
if (Array.isArray(release.keys) && release.keys.every((key) => typeof key.text === "string" && key.text.length > 0)) {
|
||||
keys = release.keys.map((key) => key.text);
|
||||
}
|
||||
} catch {
|
||||
// Prerendered content remains usable if release metadata is unavailable.
|
||||
}
|
||||
if (keys.length === 0) {
|
||||
keys = Array.from(document.querySelectorAll(".key-card pre code"), (code) => code.textContent).filter(Boolean);
|
||||
}
|
||||
if (allKeysButton) {
|
||||
allKeysButton.disabled = keys.length === 0;
|
||||
if (keys.length === 0) allKeysButton.setAttribute("title", "暂无可复制的公钥");
|
||||
allKeysButton.addEventListener("click", () => copy(`${keys.join("\n")}\n`, allKeysButton));
|
||||
}
|
||||
|
||||
root.classList.add("enhanced");
|
||||
})();
|
||||
@@ -0,0 +1,223 @@
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--page: #f7f9f8;
|
||||
--surface: #ffffff;
|
||||
--surface-soft: #f0f4f2;
|
||||
--code: #f6f8f7;
|
||||
--text: #1c2923;
|
||||
--muted: #59685f;
|
||||
--subtle: #66766c;
|
||||
--line: #dce5df;
|
||||
--accent: #216f4f;
|
||||
--accent-soft: #e8f4ec;
|
||||
--accent-line: #c5dfce;
|
||||
--shadow: 0 5px 22px rgb(24 48 34 / 3%);
|
||||
--focus: #237e59;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
:root[data-theme="dark"] {
|
||||
color-scheme: dark;
|
||||
--page: #101713;
|
||||
--surface: #17201b;
|
||||
--surface-soft: #1e2a22;
|
||||
--code: #111b15;
|
||||
--text: #e4ede7;
|
||||
--muted: #afbeb3;
|
||||
--subtle: #a1b4a7;
|
||||
--line: #2d3c32;
|
||||
--accent: #91d8ac;
|
||||
--accent-soft: #203b2b;
|
||||
--accent-line: #345941;
|
||||
--shadow: 0 5px 22px rgb(0 0 0 / 9%);
|
||||
--focus: #8ce0ad;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme="light"]) {
|
||||
color-scheme: dark;
|
||||
--page: #101713;
|
||||
--surface: #17201b;
|
||||
--surface-soft: #1e2a22;
|
||||
--code: #111b15;
|
||||
--text: #e4ede7;
|
||||
--muted: #afbeb3;
|
||||
--subtle: #a1b4a7;
|
||||
--line: #2d3c32;
|
||||
--accent: #91d8ac;
|
||||
--accent-soft: #203b2b;
|
||||
--accent-line: #345941;
|
||||
--shadow: 0 5px 22px rgb(0 0 0 / 9%);
|
||||
--focus: #8ce0ad;
|
||||
}
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
html { scroll-padding-top: 28px; }
|
||||
body { margin: 0; background: var(--page); color: var(--text); font-size: 14px; line-height: 1.65; }
|
||||
button, select, textarea { font: inherit; }
|
||||
a { color: inherit; text-decoration: none; }
|
||||
a:hover { color: var(--accent); }
|
||||
button, select { color: inherit; }
|
||||
button, select, summary { cursor: pointer; }
|
||||
:focus-visible { outline: 3px solid var(--focus); outline-offset: 4px; }
|
||||
[hidden], [data-enhance], .copy-button { display: none !important; }
|
||||
.enhanced [data-enhance] { display: inline-flex !important; }
|
||||
.enhanced .copy-button { display: inline-flex !important; }
|
||||
.page-shell { width: min(1120px, calc(100% - 96px)); margin-inline: auto; }
|
||||
.site-header { display: flex; justify-content: space-between; align-items: center; gap: 24px; min-height: 104px; border-bottom: 1px solid var(--line); }
|
||||
.brand { display: flex; align-items: center; gap: 13px; min-width: 0; }
|
||||
.brand-mark { display: grid; place-items: center; flex: 0 0 42px; height: 42px; border: 1px solid var(--accent-line); border-radius: 12px; background: var(--accent-soft); color: var(--accent); font: 700 21px ui-monospace, SFMono-Regular, Consolas, monospace; letter-spacing: -2px; padding-right: 2px; }
|
||||
.brand strong { display: block; font-size: 14px; font-weight: 650; line-height: 1.4; }
|
||||
.brand-caption { color: var(--subtle); font: 11px ui-monospace, SFMono-Regular, Consolas, monospace; }
|
||||
.header-actions { display: flex; align-items: center; gap: 22px; flex-shrink: 0; }
|
||||
.repository-link { font-size: 13px; font-weight: 600; white-space: nowrap; }
|
||||
.repository-link > span { margin-left: 3px; }
|
||||
.theme-control { align-items: center; gap: 8px; color: var(--muted); font-size: 12px; }
|
||||
.theme-control select { min-height: 36px; max-width: 120px; padding: 4px 26px 4px 10px; border: 1px solid var(--line); border-radius: 7px; background: var(--surface); font-size: 12px; }
|
||||
.hero { display: grid; grid-template-columns: 1fr 350px; align-items: center; gap: 72px; padding: 63px 0 65px; }
|
||||
.eyebrow, .section-index { font: 11px/1.5 ui-monospace, SFMono-Regular, Consolas, monospace; font-weight: 550; letter-spacing: 1.5px; color: var(--accent); }
|
||||
.eyebrow { display: flex; align-items: center; gap: 9px; margin: 0 0 18px; }
|
||||
.status-dot { width: 6px; height: 6px; border-radius: 100%; background: var(--accent); box-shadow: 0 0 0 4px var(--accent-soft); }
|
||||
h1 { margin: 0; font-size: clamp(30px, 3.4vw, 42px); font-weight: 650; letter-spacing: -1.1px; line-height: 1.45; }
|
||||
.hero-description { margin: 18px 0 21px; color: var(--muted); line-height: 1.85; }
|
||||
.hero-links { display: flex; gap: 25px; color: var(--accent); font-size: 13px; font-weight: 600; }
|
||||
.hero-links a { display: flex; align-items: center; gap: 11px; }
|
||||
.release-card { background: var(--surface); border: 1px solid var(--line); border-radius: 16px; padding: 22px 24px 18px; box-shadow: var(--shadow); min-width: 0; }
|
||||
.release-heading { display: flex; justify-content: space-between; align-items: center; gap: 12px; }
|
||||
.release-heading h2 { font-size: 14px; margin: 0; font-weight: 650; }
|
||||
.release-badge { color: var(--accent); background: var(--accent-soft); border: 1px solid var(--accent-line); padding: 2px 8px; border-radius: 5px; font: 10px/1.6 ui-monospace, SFMono-Regular, Consolas, monospace; }
|
||||
.release-details { margin: 20px 0 14px; }
|
||||
.release-details > div { display: grid; grid-template-columns: 70px minmax(0, 1fr); gap: 12px; margin-top: 12px; }
|
||||
.release-details dt { font-size: 12px; color: var(--subtle); }
|
||||
.release-details dd { margin: 0; font-size: 12px; min-width: 0; overflow-wrap: anywhere; }
|
||||
.release-revision { font-size: 11px; }
|
||||
.source-label { overflow-wrap: anywhere; }
|
||||
.source-status { color: var(--accent); font-weight: 600; }
|
||||
.release-details time { font-size: 11px; font-variant-numeric: tabular-nums; }
|
||||
.key-count { font-size: 20px; line-height: 1; font-weight: 600; }
|
||||
.count-unit { color: var(--subtle); }
|
||||
.release-note { margin: 17px 0 0; padding-top: 14px; border-top: 1px solid var(--line); color: var(--subtle); font-size: 11px; line-height: 1.7; }
|
||||
.content-section { border-top: 1px solid var(--line); padding-top: 32px; margin-bottom: 40px; scroll-margin-top: 24px; }
|
||||
.section-heading { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; margin-bottom: 20px; }
|
||||
.section-index { color: var(--subtle); font-size: 10px; margin: 0 0 8px; }
|
||||
.section-heading h2 { font-size: 23px; font-weight: 650; letter-spacing: -0.45px; line-height: 1.4; margin: 0; }
|
||||
.section-heading p:not(.section-index) { color: var(--muted); font-size: 12px; margin: 7px 0 0; max-width: 690px; }
|
||||
.section-actions { display: flex; align-items: center; gap: 18px; flex-shrink: 0; padding-bottom: 2px; }
|
||||
.text-link { white-space: nowrap; color: var(--muted); font-size: 12px; }
|
||||
.text-link span { margin-left: 4px; }
|
||||
.keys-notice { margin: 0 0 15px; padding: 12px 15px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface-soft); color: var(--muted); font-size: 12px; }
|
||||
.keys-notice:empty { display: none; }
|
||||
.key-list, .command-list { display: grid; gap: 14px; min-width: 0; }
|
||||
.command-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
||||
.key-card, .command-card { min-width: 0; border: 1px solid var(--line); border-radius: 12px; background: var(--surface); overflow: hidden; }
|
||||
.key-card { padding: 18px; }
|
||||
.card-heading { display: flex; align-items: flex-start; gap: 12px; justify-content: space-between; min-width: 0; }
|
||||
.card-heading h3 { min-width: 0; overflow-wrap: anywhere; font-size: 13px; font-weight: 600; margin: 0; line-height: 1.65; }
|
||||
.key-type { flex-shrink: 0; max-width: 40%; overflow-wrap: anywhere; border: 1px solid var(--line); background: var(--surface-soft); border-radius: 4px; padding: 1px 7px; color: var(--muted); font: 10px/1.6 ui-monospace, SFMono-Regular, Consolas, monospace; }
|
||||
.fingerprint { color: var(--subtle); font: 11px/1.65 ui-monospace, SFMono-Regular, Consolas, monospace; margin: 6px 0 12px; overflow-wrap: anywhere; }
|
||||
.code-block { position: relative; min-width: 0; border: 1px solid var(--line); border-radius: 7px; background: var(--code); }
|
||||
pre { overflow-x: auto; margin: 0; padding: 14px 62px 14px 14px; white-space: pre; scrollbar-width: thin; scrollbar-color: var(--line) transparent; }
|
||||
code { font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace; font-size: 11px; line-height: 1.8; }
|
||||
.copy-button { --icon-center: 50%; align-items: center; justify-content: center; position: relative; width: 36px; height: 36px; min-width: 36px; padding: 0; border: 1px solid var(--line); border-radius: 6px; background: var(--surface); color: var(--muted); }
|
||||
.code-block > .copy-button { position: absolute; top: 7px; right: 7px; }
|
||||
.copy-button::before, .copy-button::after { content: ""; display: block; position: absolute; width: 11px; height: 11px; border: 1.5px solid currentColor; border-radius: 2px; }
|
||||
.copy-button::before { left: calc(var(--icon-center) - 8px); top: 9px; }
|
||||
.copy-button::after { left: calc(var(--icon-center) - 4px); top: 13px; background: inherit; }
|
||||
.copy-button:hover { color: var(--accent); border-color: var(--accent-line); background: var(--accent-soft); }
|
||||
.copy-button:disabled { cursor: progress; }
|
||||
.copy-button[data-state="success"] { color: var(--accent); background: var(--accent-soft); border-color: var(--accent-line); }
|
||||
.copy-button[data-state="success"]::before { width: 7px; height: 13px; left: calc(var(--icon-center) - 4px); top: 8px; border: solid currentColor; border-width: 0 2px 2px 0; border-radius: 0; transform: rotate(45deg); }
|
||||
.copy-button[data-state="success"]::after { display: none; }
|
||||
.copy-all { --icon-center: 19px; width: auto; gap: 8px; padding: 0 12px 0 38px; color: var(--accent); font-size: 12px; font-weight: 600; }
|
||||
.copy-all:disabled { opacity: 0.55; }
|
||||
.public-notice { display: flex; align-items: flex-start; gap: 9px; margin-top: 15px; color: var(--subtle); font-size: 11px; }
|
||||
.public-notice p { margin: 0; }
|
||||
.notice-icon { display: grid; place-items: center; flex: 0 0 14px; height: 14px; margin-top: 2px; border: 1px solid var(--subtle); border-radius: 50%; font: 10px/1 serif; }
|
||||
.command-card { display: flex; flex-direction: column; padding: 17px; }
|
||||
.command-description { color: var(--muted); font-size: 12px; line-height: 1.75; margin: 6px 0 13px; }
|
||||
.command-card .code-block { margin-top: auto; }
|
||||
.command-card code { font-size: 10.5px; }
|
||||
.advanced-commands { grid-column: 1 / -1; border: 1px solid var(--line); border-radius: 10px; background: var(--surface); }
|
||||
.advanced-commands summary { padding: 15px 18px; color: var(--muted); font-size: 12px; font-weight: 600; }
|
||||
.advanced-commands summary::marker { color: var(--subtle); }
|
||||
.advanced-commands[open] summary { border-bottom: 1px solid var(--line); }
|
||||
.advanced-commands > .command-list { padding: 15px; }
|
||||
.command-note { font-size: 11px; color: var(--subtle); margin: 14px 0 0; }
|
||||
.empty-state { padding: 26px; border: 1px dashed var(--line); border-radius: 10px; color: var(--muted); text-align: center; }
|
||||
.preview-notice { margin: 24px 0 0; padding: 12px 16px; border: 1px solid var(--accent-line); border-radius: 8px; background: var(--accent-soft); color: var(--accent); font-size: 12px; }
|
||||
.noscript-notice { padding: 16px; border: 1px solid var(--line); border-radius: 8px; color: var(--muted); font-size: 12px; }
|
||||
.site-footer { display: flex; justify-content: space-between; align-items: center; gap: 20px; padding: 24px 0 32px; border-top: 1px solid var(--line); color: var(--subtle); font-size: 11px; }
|
||||
.site-footer > div { display: flex; gap: 22px; }
|
||||
.site-footer > div a { font-family: ui-monospace, SFMono-Regular, Consolas, monospace; }
|
||||
.sr-only { position: absolute !important; width: 1px !important; height: 1px !important; padding: 0 !important; margin: -1px !important; overflow: hidden !important; clip: rect(0, 0, 0, 0); white-space: nowrap !important; border: 0 !important; }
|
||||
.skip-link { position: fixed; z-index: 20; top: -100px; left: 16px; padding: 9px 16px; background: var(--surface); border: 1px solid var(--focus); border-radius: 6px; }
|
||||
.skip-link:focus { top: 12px; }
|
||||
dialog { width: min(640px, calc(100% - 32px)); max-height: calc(100% - 48px); padding: 25px; background: var(--surface); color: var(--text); border: 1px solid var(--line); border-radius: 14px; box-shadow: 0 20px 90px rgb(0 0 0 / 20%); }
|
||||
dialog::backdrop { background: rgb(10 22 15 / 52%); }
|
||||
.fallback-heading { display: flex; justify-content: space-between; align-items: center; gap: 16px; }
|
||||
.fallback-heading h2 { margin: 0; font-size: 18px; }
|
||||
.dialog-close { width: 36px; height: 36px; font-size: 24px; line-height: 1; border: 1px solid var(--line); border-radius: 6px; background: var(--surface-soft); }
|
||||
#fallback-description { color: var(--muted); font-size: 13px; margin: 14px 0; }
|
||||
#fallback-text { display: block; width: 100%; min-height: 150px; resize: vertical; padding: 12px; border: 1px solid var(--line); border-radius: 6px; background: var(--code); color: var(--text); font: 12px/1.8 ui-monospace, SFMono-Regular, Consolas, monospace; }
|
||||
.fallback-note { color: var(--subtle); font-size: 11px; margin: 12px 0 0; }
|
||||
@media (hover: hover) and (pointer: fine) {
|
||||
.code-block > .copy-button { opacity: 0; transition: opacity 120ms ease; }
|
||||
.code-block:hover > .copy-button, .code-block:focus-within > .copy-button, .code-block > .copy-button[data-state="success"] { opacity: 1; }
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.page-shell { width: calc(100% - 56px); }
|
||||
.hero { grid-template-columns: 1fr 305px; gap: 36px; padding-block: 45px; }
|
||||
.release-card { padding: 19px; }
|
||||
.brand strong { max-width: 230px; font-size: 13px; }
|
||||
.header-actions { gap: 16px; }
|
||||
.theme-control label { display: none; }
|
||||
h1 { font-size: 31px; }
|
||||
.wide-only { display: none; }
|
||||
}
|
||||
@media (max-width: 680px) {
|
||||
.page-shell { width: calc(100% - 36px); }
|
||||
.site-header { min-height: 108px; flex-wrap: wrap; align-content: center; gap: 12px; padding-block: 17px; }
|
||||
.brand { gap: 10px; }
|
||||
.brand strong { max-width: none; font-size: 12px; }
|
||||
.brand-mark { height: 35px; flex-basis: 35px; border-radius: 9px; font-size: 18px; }
|
||||
.brand-caption { font-size: 10px; }
|
||||
.header-actions { width: 100%; justify-content: space-between; }
|
||||
.theme-control label { display: inline; }
|
||||
.theme-control select { min-height: 34px; }
|
||||
.repository-link { font-size: 12px; }
|
||||
.hero { grid-template-columns: minmax(0, 1fr); gap: 27px; padding: 35px 0; }
|
||||
h1 { font-size: clamp(28px, 7vw, 36px); letter-spacing: -0.7px; }
|
||||
.hero-description { font-size: 13px; margin: 13px 0 17px; }
|
||||
.eyebrow { font-size: 10px; margin-bottom: 14px; }
|
||||
.hero-links { font-size: 12px; }
|
||||
.release-details { margin: 15px 0 0; }
|
||||
.release-details > div { grid-template-columns: 72px minmax(0, 1fr); margin-top: 10px; }
|
||||
.release-note { margin-top: 15px; padding-top: 12px; }
|
||||
.content-section { padding-top: 25px; margin-bottom: 31px; }
|
||||
.section-heading { align-items: flex-start; flex-wrap: wrap; gap: 15px; margin-bottom: 17px; }
|
||||
.section-heading h2 { font-size: 21px; }
|
||||
.section-actions { width: 100%; justify-content: space-between; }
|
||||
.commands-section > .section-heading > .text-link { margin-top: -3px; }
|
||||
.key-card, .command-card { padding: 14px; }
|
||||
.command-list { grid-template-columns: minmax(0, 1fr); gap: 12px; }
|
||||
.card-heading { gap: 8px; }
|
||||
.fingerprint { font-size: 10px; }
|
||||
pre { padding-left: 12px; padding-block: 16px; }
|
||||
.copy-button { width: 40px; height: 40px; }
|
||||
.copy-button::before { top: 11px; }
|
||||
.copy-button::after { top: 15px; }
|
||||
.copy-button[data-state="success"]::before { top: 10px; }
|
||||
.copy-all { width: auto; }
|
||||
.public-notice { font-size: 11px; }
|
||||
.site-footer { flex-direction: column; align-items: flex-start; gap: 12px; padding: 22px 0 26px; }
|
||||
dialog { padding: 20px; }
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { transition: none !important; scroll-behavior: auto !important; } }
|
||||
@media print {
|
||||
.header-actions, .hero-links, .section-actions, .copy-button, .enhanced .copy-button, .commands-section .text-link, .site-footer, dialog { display: none !important; }
|
||||
.page-shell { width: 100%; }
|
||||
.hero { padding-block: 24px; }
|
||||
pre { white-space: pre-wrap; overflow-wrap: anywhere; }
|
||||
.key-card, .command-card { break-inside: avoid; }
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
<meta name="description" content="查看当前已发布的 SSH 公钥,复制服务器配置命令。Quickly Configuring SSH Server。">
|
||||
<title>SSH 公钥与快速部署 · Quickly Configuring SSH Server</title>
|
||||
<link rel="stylesheet" href="/assets/style.css">
|
||||
<script src="/assets/app.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<a class="skip-link" href="#public-keys">跳到 SSH 公钥</a>
|
||||
<div class="page-shell">
|
||||
<header class="site-header">
|
||||
<a class="brand" href="/" aria-label="Quickly Configuring SSH Server 首页">
|
||||
<span class="brand-mark" aria-hidden="true">>_</span>
|
||||
<span><strong>Quickly Configuring SSH Server</strong><span class="brand-caption">quickly-conf-sshd</span></span>
|
||||
</a>
|
||||
<nav class="header-actions" aria-label="项目与显示设置">
|
||||
<div class="theme-control" data-enhance>
|
||||
<label for="theme-picker">外观</label>
|
||||
<select id="theme-picker" aria-label="选择页面外观">
|
||||
<option value="system">跟随系统</option>
|
||||
<option value="light">浅色</option>
|
||||
<option value="dark">深色</option>
|
||||
</select>
|
||||
</div>
|
||||
<a class="repository-link" href="@@REPOSITORY_URL@@" target="_blank" rel="noopener noreferrer">GitHub <span aria-hidden="true">↗</span><span class="sr-only">(新窗口打开)</span></a>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
@@PREVIEW_NOTICE@@
|
||||
<section class="hero" aria-labelledby="page-title">
|
||||
<div class="hero-copy">
|
||||
<p class="eyebrow"><span class="status-dot" aria-hidden="true"></span> SSH KEY DISTRIBUTION</p>
|
||||
<h1 id="page-title">公钥集中管理,<br>服务器快速就绪。</h1>
|
||||
<p class="hero-description">核对公钥,选择命令,配置 SSH。<br class="wide-only">查看当前公钥来源与内容版本,按需部署。</p>
|
||||
<div class="hero-links"><a href="#public-keys">查看公钥 <span aria-hidden="true">↓</span></a><a href="#quick-start">快速开始 <span aria-hidden="true">↓</span></a></div>
|
||||
</div>
|
||||
<aside class="release-card" aria-labelledby="release-title">
|
||||
<div class="release-heading"><h2 id="release-title">版本与公钥来源</h2><span class="release-badge">@@RELEASE_LABEL@@</span></div>
|
||||
<dl class="release-details">
|
||||
<div><dt>代码版本</dt><dd><code class="release-revision">@@REVISION@@</code></dd></div>
|
||||
<div><dt>公钥版本</dt><dd><code class="release-revision">@@CONTENT_REVISION@@</code></dd></div>
|
||||
<div><dt>公钥来源</dt><dd class="source-label">@@SOURCE_LABEL@@</dd></div>
|
||||
<div><dt>来源状态</dt><dd class="source-status">@@SOURCE_STATUS@@</dd></div>
|
||||
<div><dt>发布时间</dt><dd><time datetime="@@PUBLISHED_AT@@">@@PUBLISHED_AT@@</time></dd></div>
|
||||
<div><dt>公开密钥</dt><dd><strong class="key-count">@@KEY_COUNT@@</strong><span class="count-unit"> 个</span></dd></div>
|
||||
</dl>
|
||||
<p class="release-note">此处展示代码发布与当前公钥来源,不代表所有服务器均已同步。</p>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<section id="public-keys" class="content-section" aria-labelledby="keys-title">
|
||||
<div class="section-heading">
|
||||
<div><p class="section-index">01 / PUBLIC KEYS</p><h2 id="keys-title">SSH 公钥</h2><p>部署前,请核对指纹及密钥归属。</p></div>
|
||||
<div class="section-actions"><a class="text-link" href="/ssh.keys">原始文件 <span aria-hidden="true">↗</span></a><button id="copy-all-keys" class="copy-button copy-all" type="button" aria-label="复制全部公钥" title="复制全部公钥" data-enhance><span>复制全部</span></button></div>
|
||||
</div>
|
||||
<p class="keys-notice" role="note">@@KEYS_NOTICE@@</p>
|
||||
<div class="key-list">@@KEYS_HTML@@</div>
|
||||
<div class="public-notice"><span class="notice-icon" aria-hidden="true">i</span><p>这里展示的是公开密钥。私钥应始终由你保管,本页面不会要求上传或输入私钥。</p></div>
|
||||
</section>
|
||||
|
||||
<section id="quick-start" class="content-section commands-section" aria-labelledby="commands-title">
|
||||
<div class="section-heading"><div><p class="section-index">02 / QUICK START</p><h2 id="commands-title">按需选择,复制即用</h2><p>以下命令会在你执行后修改服务器配置。请先检查脚本,并保留当前 SSH 会话以验证新连接。</p></div><a class="text-link" href="/script.sh">查看脚本 <span aria-hidden="true">↗</span></a></div>
|
||||
<div class="command-list">@@COMMANDS_HTML@@</div>
|
||||
<p class="command-note">每条命令独立使用。高级选项涉及 SSH 登录策略或卸载,请阅读说明后操作。</p>
|
||||
</section>
|
||||
<noscript><p class="noscript-notice">JavaScript 已关闭,公钥与命令仍可查看并手动选择复制。启用 JavaScript 后可使用复制按钮与外观设置。</p></noscript>
|
||||
</main>
|
||||
|
||||
<footer class="site-footer"><span>Quickly Configuring SSH Server</span><div><a href="/ssh.keys">ssh.keys</a><a href="/script.sh">script.sh</a><a href="@@REPOSITORY_URL@@" target="_blank" rel="noopener noreferrer">源代码<span class="sr-only">(新窗口打开)</span></a></div></footer>
|
||||
</div>
|
||||
|
||||
<p id="copy-status" class="sr-only" role="status" aria-live="polite" aria-atomic="true"></p>
|
||||
<dialog id="copy-fallback" aria-labelledby="fallback-title" aria-describedby="fallback-description">
|
||||
<div class="fallback-heading"><h2 id="fallback-title">手动复制完整内容</h2><button id="close-copy-fallback" class="dialog-close" type="button" aria-label="关闭手动复制窗口">×</button></div>
|
||||
<p id="fallback-description">浏览器未允许自动复制。内容已选中,请按 Ctrl+C 或 ⌘C,也可以长按文本进行复制。</p>
|
||||
<label class="sr-only" for="fallback-text">要复制的完整内容</label>
|
||||
<textarea id="fallback-text" readonly spellcheck="false" wrap="off"></textarea>
|
||||
<p class="fallback-note">关闭此窗口后,可继续查看页面。</p>
|
||||
</dialog>
|
||||
<script id="release-data" type="application/json">@@RELEASE_JSON@@</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user