feat(secrets-mcp): /changelog 页(Markdown 渲染)、首页与 Dashboard 入口
- 新增 CHANGELOG.md 构建嵌入、pulldown-cmark 渲染、路由 /changelog - 首页页脚与 MCP Dashboard 页脚提供变更记录链接;同步 README、AGENTS - 版本 secrets-mcp 0.5.24
This commit is contained in:
@@ -42,7 +42,7 @@ secrets/
|
||||
Cargo.toml
|
||||
crates/
|
||||
secrets-core/ # db / crypto / models / audit / service
|
||||
secrets-mcp/ # rmcp tools、axum、OAuth、Dashboard
|
||||
secrets-mcp/ # rmcp tools、axum、OAuth、Dashboard;CHANGELOG.md → /changelog
|
||||
scripts/
|
||||
release-check.sh
|
||||
setup-gitea-actions.sh
|
||||
@@ -166,6 +166,10 @@ oauth_accounts (
|
||||
| `secrets.type` | 密钥类型(调用方提供,默认 `text`) | `text`, `password`, `key` |
|
||||
| `secrets.encrypted` | 密文 | AES-GCM |
|
||||
|
||||
### Web 变更记录(`/changelog`)
|
||||
|
||||
`crates/secrets-mcp/CHANGELOG.md` 在构建时嵌入,服务端以 **Markdown** 渲染为 HTML(`pulldown-cmark`)。**首页**(`/`)页脚与 **Dashboard**(`/dashboard`,MCP 配置页)页脚均提供「变更记录」链接;发版时随 `secrets-mcp` 版本更新该文件即可。
|
||||
|
||||
### Web JSON API 与会话
|
||||
|
||||
除页面路由使用的 `require_valid_user`(未登录或 `key_version` 与库不一致时重定向 `/login`)外,JSON API(`/api/...`)使用等价校验:会话中的 `key_version` 须与 `users.key_version` 一致,否则返回 **401** JSON,避免仅校验 `user_id` 时与页面行为不一致。
|
||||
|
||||
43
Cargo.lock
generated
43
Cargo.lock
generated
@@ -740,6 +740,15 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getopts"
|
||||
version = "0.2.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
@@ -1578,6 +1587,25 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c3a14896dfa883796f1cb410461aef38810ea05f2b2c33c5aded3649095fdad"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"getopts",
|
||||
"memchr",
|
||||
"pulldown-cmark-escape",
|
||||
"unicase",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pulldown-cmark-escape"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
|
||||
|
||||
[[package]]
|
||||
name = "quanta"
|
||||
version = "0.12.6"
|
||||
@@ -2065,7 +2093,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "secrets-mcp"
|
||||
version = "0.5.21"
|
||||
version = "0.5.24"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"askama",
|
||||
@@ -2075,6 +2103,7 @@ dependencies = [
|
||||
"dotenvy",
|
||||
"governor",
|
||||
"http",
|
||||
"pulldown-cmark",
|
||||
"rand 0.10.0",
|
||||
"reqwest",
|
||||
"rmcp",
|
||||
@@ -2985,6 +3014,12 @@ version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.18"
|
||||
@@ -3012,6 +3047,12 @@ version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
|
||||
@@ -46,7 +46,7 @@ SECRETS_DATABASE_SSL_ROOT_CERT=/etc/secrets/pg-ca.crt
|
||||
SECRETS_ENV=production
|
||||
```
|
||||
|
||||
- **Web**:`BASE_URL`(登录、Dashboard、设置密码短语、创建 API Key)。**条目**页 `/entries` 支持 folder 标签与条件筛选;表格列可在「显示列」中开关(名称与操作固定),**文件夹**列为可选列且默认显示。列可见性持久化见 [AGENTS.md](AGENTS.md)「Web 条目页表格列」。
|
||||
- **Web**:`BASE_URL`(登录、Dashboard、设置密码短语、创建 API Key)。**变更记录**页 **`/changelog`**:内容来自 `crates/secrets-mcp/CHANGELOG.md`(构建时嵌入并以 Markdown 渲染);首页页脚与 Dashboard(MCP)页脚均提供入口。**条目**页 `/entries` 支持 folder 标签与条件筛选;表格列可在「显示列」中开关(名称与操作固定),**文件夹**列为可选列且默认显示。列可见性持久化见 [AGENTS.md](AGENTS.md)「Web 条目页表格列」。
|
||||
- **MCP**:Streamable HTTP 基址 `{BASE_URL}/mcp`,需 `Authorization: Bearer <api_key>` + `X-Encryption-Key: <hex>` 请求头(读密文工具须带密钥)。
|
||||
|
||||
## PostgreSQL TLS 加固
|
||||
@@ -226,7 +226,7 @@ crates/secrets-core/ # db / crypto / models / audit / service
|
||||
src/
|
||||
taxonomy.rs # SECRET_TYPE_OPTIONS(secret 字段类型下拉选项)
|
||||
service/ # 业务逻辑(add, search, update, delete, export, env_map 等)
|
||||
crates/secrets-mcp/ # MCP HTTP、Web、OAuth、API Key
|
||||
crates/secrets-mcp/ # MCP HTTP、Web、OAuth、API Key;CHANGELOG.md 嵌入 /changelog
|
||||
scripts/
|
||||
release-check.sh # 发版前 fmt / clippy / test
|
||||
setup-gitea-actions.sh
|
||||
|
||||
20
crates/secrets-mcp/CHANGELOG.md
Normal file
20
crates/secrets-mcp/CHANGELOG.md
Normal file
@@ -0,0 +1,20 @@
|
||||
本文档在构建时嵌入 Web 的 `/changelog` 页面,并由服务端渲染为 HTML。
|
||||
|
||||
## [0.5.24] - 2026-04-11
|
||||
|
||||
### Changed
|
||||
|
||||
- 首页页脚将原「登录」入口改为「变更记录」(`/changelog`);顶部导航仍保留登录 / 进入控制台。
|
||||
|
||||
## [0.5.23] - 2026-04-11
|
||||
|
||||
### Added
|
||||
|
||||
- Changelog 页使用 **Markdown** 渲染(`pulldown-cmark`:表格、~~删除线~~、任务列表等)。
|
||||
|
||||
## [0.5.22] - 2026-04-11
|
||||
|
||||
### Added
|
||||
|
||||
- Dashboard(MCP)页脚版本旁增加「变更记录」链接,打开本变更说明页。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "secrets-mcp"
|
||||
version = "0.5.21"
|
||||
version = "0.5.24"
|
||||
edition.workspace = true
|
||||
|
||||
[[bin]]
|
||||
@@ -45,3 +45,4 @@ urlencoding = "2"
|
||||
schemars = "1"
|
||||
http = "1"
|
||||
url = "2"
|
||||
pulldown-cmark = "0.13.3"
|
||||
|
||||
48
crates/secrets-mcp/src/web/changelog.rs
Normal file
48
crates/secrets-mcp/src/web/changelog.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use askama::Template;
|
||||
use axum::{extract::State, http::StatusCode, response::Response};
|
||||
use pulldown_cmark::{Options, Parser, html};
|
||||
|
||||
use crate::AppState;
|
||||
|
||||
use super::render_template;
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "changelog.html")]
|
||||
pub(super) struct ChangelogTemplate {
|
||||
pub base_url: String,
|
||||
pub version: &'static str,
|
||||
pub changelog_html: String,
|
||||
}
|
||||
|
||||
fn markdown_to_html(md: &str) -> String {
|
||||
let mut opts = Options::empty();
|
||||
opts.insert(Options::ENABLE_TABLES);
|
||||
opts.insert(Options::ENABLE_STRIKETHROUGH);
|
||||
opts.insert(Options::ENABLE_TASKLISTS);
|
||||
let parser = Parser::new_ext(md, opts);
|
||||
let mut out = String::new();
|
||||
html::push_html(&mut out, parser);
|
||||
out
|
||||
}
|
||||
|
||||
pub(super) async fn changelog_page(State(state): State<AppState>) -> Result<Response, StatusCode> {
|
||||
let md = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/CHANGELOG.md"));
|
||||
render_template(ChangelogTemplate {
|
||||
base_url: state.base_url.clone(),
|
||||
version: env!("CARGO_PKG_VERSION"),
|
||||
changelog_html: markdown_to_html(md),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::markdown_to_html;
|
||||
|
||||
#[test]
|
||||
fn markdown_renders_heading_and_list() {
|
||||
let html = markdown_to_html("# Title\n\n- a\n");
|
||||
assert!(html.contains("<h1"));
|
||||
assert!(html.contains("Title"));
|
||||
assert!(html.contains("<ul") || html.contains("<li"));
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ mod account;
|
||||
mod assets;
|
||||
mod audit;
|
||||
mod auth;
|
||||
mod changelog;
|
||||
mod entries;
|
||||
|
||||
// ── Session keys ──────────────────────────────────────────────────────────────
|
||||
@@ -253,6 +254,7 @@ pub fn web_router() -> Router<AppState> {
|
||||
get(assets::oauth_protected_resource_metadata),
|
||||
)
|
||||
.route("/", get(auth::home_page))
|
||||
.route("/changelog", get(changelog::changelog_page))
|
||||
.route("/login", get(auth::login_page))
|
||||
.route("/auth/google", get(auth::auth_google))
|
||||
.route("/auth/google/callback", get(auth::auth_google_callback))
|
||||
|
||||
185
crates/secrets-mcp/templates/changelog.html
Normal file
185
crates/secrets-mcp/templates/changelog.html
Normal file
@@ -0,0 +1,185 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="canonical" href="{{ base_url }}/changelog">
|
||||
<link rel="icon" href="/favicon.svg?v={{ version }}" type="image/svg+xml">
|
||||
<title data-i18n="docTitle">变更记录 — Secrets</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600&family=Inter:wght@400;500;600&display=swap');
|
||||
:root {
|
||||
--bg: #0d1117; --surface: #161b22;
|
||||
--border: #30363d; --text: #e6edf3; --text-muted: #8b949e;
|
||||
--accent: #58a6ff; --accent-hover: #79b8ff;
|
||||
}
|
||||
body { background: var(--bg); color: var(--text); font-family: 'Inter', sans-serif; min-height: 100vh; }
|
||||
.wrap { max-width: 880px; margin: 0 auto; padding: 24px 20px 48px; }
|
||||
.top {
|
||||
display: flex; align-items: center; flex-wrap: wrap; gap: 12px 16px;
|
||||
margin-bottom: 24px; padding-bottom: 16px;
|
||||
border-bottom: 1px solid rgba(240,246,252,0.08);
|
||||
}
|
||||
.brand {
|
||||
font-size: 18px; font-weight: 700; color: #fff; text-decoration: none;
|
||||
}
|
||||
.brand:hover { color: var(--accent); }
|
||||
.top-actions { margin-left: auto; display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
|
||||
.lang-bar { display: flex; gap: 2px; background: rgba(240,246,252,0.06); border-radius: 8px; padding: 2px; }
|
||||
.lang-btn { padding: 4px 10px; border: none; background: none; color: #8b949e;
|
||||
font-size: 12px; cursor: pointer; border-radius: 6px; }
|
||||
.lang-btn.active { background: rgba(240,246,252,0.1); color: #fff; }
|
||||
.link-dash {
|
||||
font-size: 13px; color: var(--accent); text-decoration: none;
|
||||
}
|
||||
.link-dash:hover { text-decoration: underline; }
|
||||
h1 { font-size: 22px; font-weight: 700; margin-bottom: 16px; color: #fff; }
|
||||
.card {
|
||||
background: #111827; border: 1px solid rgba(240,246,252,0.08); border-radius: 18px;
|
||||
padding: 20px 22px;
|
||||
}
|
||||
/* Rendered Markdown (pulldown-cmark) */
|
||||
.changelog-md {
|
||||
font-size: 14px;
|
||||
line-height: 1.65;
|
||||
color: #c9d1d9;
|
||||
}
|
||||
.changelog-md > :first-child { margin-top: 0; }
|
||||
.changelog-md > :last-child { margin-bottom: 0; }
|
||||
.changelog-md h1 {
|
||||
font-size: 1.5rem; font-weight: 700; color: #fff;
|
||||
margin: 1.25em 0 0.5em; padding-bottom: 0.35em;
|
||||
border-bottom: 1px solid rgba(240,246,252,0.1);
|
||||
}
|
||||
.changelog-md h2 {
|
||||
font-size: 1.2rem; font-weight: 650; color: #f0f6fc;
|
||||
margin: 1.35em 0 0.5em;
|
||||
}
|
||||
.changelog-md h3 { font-size: 1.05rem; font-weight: 600; color: #e6edf3; margin: 1.1em 0 0.45em; }
|
||||
.changelog-md h4, .changelog-md h5, .changelog-md h6 { font-size: 1rem; font-weight: 600; color: #e6edf3; margin: 1em 0 0.4em; }
|
||||
.changelog-md p { margin: 0.65em 0; }
|
||||
.changelog-md ul, .changelog-md ol { margin: 0.65em 0; padding-left: 1.35em; }
|
||||
.changelog-md li { margin: 0.3em 0; }
|
||||
.changelog-md li > p { margin: 0.35em 0; }
|
||||
.changelog-md a { color: var(--accent); text-decoration: none; }
|
||||
.changelog-md a:hover { text-decoration: underline; }
|
||||
.changelog-md code {
|
||||
font-family: 'JetBrains Mono', ui-monospace, monospace;
|
||||
font-size: 0.88em;
|
||||
background: rgba(240,246,252,0.08);
|
||||
padding: 0.12em 0.4em;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.changelog-md pre {
|
||||
margin: 0.85em 0;
|
||||
padding: 12px 14px;
|
||||
overflow-x: auto;
|
||||
background: #0d1117;
|
||||
border: 1px solid rgba(240,246,252,0.1);
|
||||
border-radius: 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.changelog-md pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
font-size: inherit;
|
||||
border-radius: 0;
|
||||
}
|
||||
.changelog-md blockquote {
|
||||
margin: 0.75em 0;
|
||||
padding-left: 1em;
|
||||
border-left: 3px solid rgba(56,139,253,0.45);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.changelog-md hr {
|
||||
margin: 1.25em 0;
|
||||
border: none;
|
||||
border-top: 1px solid rgba(240,246,252,0.1);
|
||||
}
|
||||
.changelog-md table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 0.85em 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
.changelog-md th, .changelog-md td {
|
||||
border: 1px solid var(--border);
|
||||
padding: 8px 10px;
|
||||
text-align: left;
|
||||
}
|
||||
.changelog-md th { background: rgba(240,246,252,0.06); color: #f0f6fc; }
|
||||
.changelog-md input[type="checkbox"] { margin-right: 0.35em; vertical-align: middle; }
|
||||
.foot {
|
||||
margin-top: 28px; text-align: center; font-size: 11px; color: var(--text-muted);
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
}
|
||||
.foot a { color: var(--accent); text-decoration: none; }
|
||||
.foot a:hover { text-decoration: underline; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<header class="top">
|
||||
<a href="/" class="brand">secrets</a>
|
||||
<div class="top-actions">
|
||||
<a href="/dashboard" class="link-dash" data-i18n="backDash">控制台</a>
|
||||
<div class="lang-bar" role="group" aria-label="Language">
|
||||
<button type="button" class="lang-btn" onclick="setLang('zh-CN')">简</button>
|
||||
<button type="button" class="lang-btn" onclick="setLang('zh-TW')">繁</button>
|
||||
<button type="button" class="lang-btn" onclick="setLang('en')">EN</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<h1 data-i18n="pageTitle">变更记录</h1>
|
||||
<div class="card changelog-md">
|
||||
{{ changelog_html|safe }}
|
||||
</div>
|
||||
<footer class="foot">
|
||||
<span data-i18n="versionLabel">版本</span> {{ version }}
|
||||
</footer>
|
||||
</div>
|
||||
<script>
|
||||
const T = {
|
||||
'zh-CN': {
|
||||
docTitle: '变更记录 — Secrets',
|
||||
pageTitle: '变更记录',
|
||||
backDash: '控制台',
|
||||
versionLabel: '版本',
|
||||
},
|
||||
'zh-TW': {
|
||||
docTitle: '變更記錄 — Secrets',
|
||||
pageTitle: '變更記錄',
|
||||
backDash: '控制台',
|
||||
versionLabel: '版本',
|
||||
},
|
||||
'en': {
|
||||
docTitle: 'Changelog — Secrets',
|
||||
pageTitle: 'Changelog',
|
||||
backDash: 'Dashboard',
|
||||
versionLabel: 'Version',
|
||||
}
|
||||
};
|
||||
let currentLang = localStorage.getItem('lang') || 'zh-CN';
|
||||
function t(key) { return (T[currentLang] && T[currentLang][key]) || T['en'][key] || key; }
|
||||
function applyLang() {
|
||||
document.documentElement.lang = currentLang;
|
||||
document.title = t('docTitle');
|
||||
document.querySelectorAll('[data-i18n]').forEach(el => {
|
||||
el.textContent = t(el.getAttribute('data-i18n'));
|
||||
});
|
||||
document.querySelectorAll('.lang-btn').forEach(btn => {
|
||||
const map = { 'zh-CN': '简', 'zh-TW': '繁', 'en': 'EN' };
|
||||
btn.classList.toggle('active', btn.textContent === map[currentLang]);
|
||||
});
|
||||
}
|
||||
function setLang(lang) {
|
||||
currentLang = lang;
|
||||
localStorage.setItem('lang', lang);
|
||||
applyLang();
|
||||
}
|
||||
applyLang();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -57,6 +57,8 @@
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
margin-top: auto;
|
||||
}
|
||||
.app-footer a { color: var(--accent); text-decoration: none; }
|
||||
.app-footer a:hover { text-decoration: underline; }
|
||||
.card { background: #111827; border: 1px solid rgba(240,246,252,0.08); border-radius: 18px;
|
||||
padding: 20px; width: 100%; }
|
||||
.card-title { font-size: 22px; font-weight: 700; margin-bottom: 24px; color: #fff; }
|
||||
@@ -288,7 +290,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="app-footer">{{ version }}</footer>
|
||||
<footer class="app-footer">{{ version }} · <a href="/changelog" data-i18n="changelogLink">变更记录</a></footer>
|
||||
</div><!-- /main -->
|
||||
</div><!-- /content-shell -->
|
||||
</div><!-- /layout -->
|
||||
@@ -379,6 +381,7 @@ const T = {
|
||||
regenFailed: '重置失败,请刷新页面重试。',
|
||||
ariaShowPw: '显示密码',
|
||||
ariaHidePw: '隐藏密码',
|
||||
changelogLink: '变更记录',
|
||||
},
|
||||
'zh-TW': {
|
||||
navMcp: 'MCP', navEntries: '條目', navTrash: '回收站', navAudit: '審計',
|
||||
@@ -417,6 +420,7 @@ const T = {
|
||||
regenFailed: '重置失敗,請重新整理頁面再試。',
|
||||
ariaShowPw: '顯示密碼',
|
||||
ariaHidePw: '隱藏密碼',
|
||||
changelogLink: '變更記錄',
|
||||
},
|
||||
'en': {
|
||||
navMcp: 'MCP', navEntries: 'Entries', navTrash: 'Trash', navAudit: 'Audit',
|
||||
@@ -455,6 +459,7 @@ const T = {
|
||||
regenFailed: 'Reset failed. Please refresh and try again.',
|
||||
ariaShowPw: 'Show password',
|
||||
ariaHidePw: 'Hide password',
|
||||
changelogLink: 'Changelog',
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -178,10 +178,8 @@
|
||||
<a href="/llms.txt">llms.txt</a>
|
||||
<span data-i18n="sep"> · </span>
|
||||
<a href="https://gitea.refining.dev/refining/secrets" target="_blank" rel="noopener noreferrer" data-i18n="footRepo">源码仓库</a>
|
||||
{% if !is_logged_in %}
|
||||
<span data-i18n="sep"> · </span>
|
||||
<a href="/login" data-i18n="footLogin">登录</a>
|
||||
{% endif %}
|
||||
<a href="/changelog" data-i18n="footChangelog">变更记录</a>
|
||||
</footer>
|
||||
<script>
|
||||
const T = {
|
||||
@@ -200,7 +198,7 @@
|
||||
versionLabel: '版本',
|
||||
sep: ' · ',
|
||||
footRepo: '源码仓库',
|
||||
footLogin: '登录',
|
||||
footChangelog: '变更记录',
|
||||
},
|
||||
'zh-TW': {
|
||||
docTitle: 'Secrets MCP — 端到端加密的金鑰管理',
|
||||
@@ -217,7 +215,7 @@
|
||||
versionLabel: '版本',
|
||||
sep: ' · ',
|
||||
footRepo: '原始碼倉庫',
|
||||
footLogin: '登入',
|
||||
footChangelog: '變更記錄',
|
||||
},
|
||||
'en': {
|
||||
docTitle: 'Secrets MCP — End-to-end encrypted secrets',
|
||||
@@ -234,7 +232,7 @@
|
||||
versionLabel: 'Version',
|
||||
sep: ' · ',
|
||||
footRepo: 'Source repository',
|
||||
footLogin: 'Sign in',
|
||||
footChangelog: 'Changelog',
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user