feat(ui): translate config, cron, skills, debug views to Chinese
- Update config.ts with i18n translations for sidebar, actions, forms - Update cron.ts with translations for scheduler, job form, history - Update skills.ts with translations for skill list, filters, actions - Update debug.ts with translations for snapshots, RPC, events - Add missing translation keys to en-US and zh-TW locale files
This commit is contained in:
parent
a2497ecaab
commit
eccf005580
@ -304,8 +304,8 @@ export const enUS = {
|
||||
cron: {
|
||||
title: "Cron Jobs",
|
||||
desc: "Scheduled agent wakeups and recurring tasks.",
|
||||
noJobs: "No cron jobs configured.",
|
||||
addJob: "Add Job",
|
||||
noJobs: "No jobs yet.",
|
||||
addJob: "Add job",
|
||||
runNow: "Run",
|
||||
remove: "Remove",
|
||||
enable: "Enable",
|
||||
@ -314,6 +314,57 @@ export const enUS = {
|
||||
lastRun: "Last run",
|
||||
nextRun: "Next run",
|
||||
|
||||
// Scheduler card
|
||||
scheduler: "Scheduler",
|
||||
schedulerDesc: "Gateway-owned cron scheduler status.",
|
||||
jobs: "Jobs",
|
||||
|
||||
// New job form
|
||||
newJob: "New Job",
|
||||
newJobDesc: "Create a scheduled wakeup or agent run.",
|
||||
name: "Name",
|
||||
description: "Description",
|
||||
agentId: "Agent ID",
|
||||
agentIdPlaceholder: "default",
|
||||
scheduleKind: "Schedule",
|
||||
everyLabel: "Every",
|
||||
atLabel: "At",
|
||||
cronLabel: "Cron",
|
||||
runAt: "Run at",
|
||||
every: "Every",
|
||||
unit: "Unit",
|
||||
minutes: "Minutes",
|
||||
hours: "Hours",
|
||||
days: "Days",
|
||||
expression: "Expression",
|
||||
timezone: "Timezone (optional)",
|
||||
session: "Session",
|
||||
main: "Main",
|
||||
isolated: "Isolated",
|
||||
wakeMode: "Wake mode",
|
||||
nextHeartbeat: "Next heartbeat",
|
||||
now: "Now",
|
||||
payload: "Payload",
|
||||
systemEvent: "System event",
|
||||
agentTurn: "Agent turn",
|
||||
systemText: "System text",
|
||||
agentMessage: "Agent message",
|
||||
deliver: "Deliver",
|
||||
to: "To",
|
||||
toPlaceholder: "+1555… or chat id",
|
||||
timeoutSeconds: "Timeout (seconds)",
|
||||
postToMainPrefix: "Post to main prefix",
|
||||
|
||||
// Jobs list
|
||||
jobsList: "Jobs",
|
||||
jobsListDesc: "All scheduled jobs stored in the gateway.",
|
||||
|
||||
// Run history
|
||||
runHistory: "Run history",
|
||||
runHistoryDesc: "Latest runs for",
|
||||
selectJob: "Select a job to inspect run history.",
|
||||
noRuns: "No runs yet.",
|
||||
|
||||
form: {
|
||||
schedule: "Schedule (cron)",
|
||||
message: "Message",
|
||||
@ -336,8 +387,9 @@ export const enUS = {
|
||||
desc: "Manage bundled and installed skills.",
|
||||
noSkills: "No skills found.",
|
||||
filter: "Filter skills",
|
||||
apiKey: "API Key",
|
||||
saveKey: "Save Key",
|
||||
shown: "shown",
|
||||
apiKey: "API key",
|
||||
saveKey: "Save key",
|
||||
install: "Install",
|
||||
installing: "Installing…",
|
||||
enabled: "Enabled",
|
||||
@ -345,6 +397,11 @@ export const enUS = {
|
||||
toggle: "Toggle",
|
||||
keySaved: "API key saved",
|
||||
keyError: "Failed to save API key",
|
||||
eligible: "eligible",
|
||||
blocked: "blocked",
|
||||
missing: "Missing",
|
||||
reason: "Reason",
|
||||
blockedByAllowlist: "blocked by allowlist",
|
||||
},
|
||||
|
||||
// Nodes page
|
||||
@ -427,14 +484,29 @@ export const enUS = {
|
||||
status: "Status",
|
||||
health: "Health",
|
||||
models: "Models",
|
||||
modelsDesc: "Catalog from models.list.",
|
||||
heartbeat: "Heartbeat",
|
||||
lastHeartbeat: "Last heartbeat",
|
||||
events: "Events",
|
||||
eventLog: "Event Log",
|
||||
eventLogDesc: "Latest gateway events.",
|
||||
noEvents: "No events yet.",
|
||||
rpcCall: "RPC Call",
|
||||
manualRpc: "Manual RPC",
|
||||
manualRpcDesc: "Send a raw gateway method with JSON params.",
|
||||
method: "Method",
|
||||
params: "Params",
|
||||
paramsJson: "Params (JSON)",
|
||||
call: "Call",
|
||||
result: "Result",
|
||||
noResult: "No result yet.",
|
||||
snapshots: "Snapshots",
|
||||
snapshotsDesc: "Status, health, and heartbeat data.",
|
||||
securityAudit: "Security audit",
|
||||
critical: "critical",
|
||||
warnings: "warnings",
|
||||
noCritical: "No critical issues",
|
||||
runAuditCmd: "Run for details.",
|
||||
},
|
||||
|
||||
// Logs page
|
||||
|
||||
@ -311,7 +311,7 @@ export const zhTW = {
|
||||
cron: {
|
||||
title: "排程任務",
|
||||
desc: "排程代理喚醒與週期性任務。",
|
||||
noJobs: "尚未設定排程任務。",
|
||||
noJobs: "尚無任務。",
|
||||
addJob: "新增任務",
|
||||
runNow: "立即執行",
|
||||
remove: "移除",
|
||||
@ -321,6 +321,57 @@ export const zhTW = {
|
||||
lastRun: "上次執行",
|
||||
nextRun: "下次執行",
|
||||
|
||||
// 排程器卡片
|
||||
scheduler: "排程器",
|
||||
schedulerDesc: "閘道器所管理的排程器狀態。",
|
||||
jobs: "任務數",
|
||||
|
||||
// 新任務表單
|
||||
newJob: "新增任務",
|
||||
newJobDesc: "建立排程喚醒或代理執行。",
|
||||
name: "名稱",
|
||||
description: "描述",
|
||||
agentId: "代理 ID",
|
||||
agentIdPlaceholder: "default",
|
||||
scheduleKind: "排程類型",
|
||||
everyLabel: "間隔",
|
||||
atLabel: "指定時間",
|
||||
cronLabel: "Cron",
|
||||
runAt: "執行時間",
|
||||
every: "每隔",
|
||||
unit: "單位",
|
||||
minutes: "分鐘",
|
||||
hours: "小時",
|
||||
days: "天",
|
||||
expression: "運算式",
|
||||
timezone: "時區(選填)",
|
||||
session: "工作階段",
|
||||
main: "主要",
|
||||
isolated: "獨立",
|
||||
wakeMode: "喚醒模式",
|
||||
nextHeartbeat: "下次心跳",
|
||||
now: "立即",
|
||||
payload: "內容類型",
|
||||
systemEvent: "系統事件",
|
||||
agentTurn: "代理回合",
|
||||
systemText: "系統文字",
|
||||
agentMessage: "代理訊息",
|
||||
deliver: "傳送",
|
||||
to: "收件者",
|
||||
toPlaceholder: "+1555… 或聊天 ID",
|
||||
timeoutSeconds: "逾時(秒)",
|
||||
postToMainPrefix: "發送至主工作階段前綴",
|
||||
|
||||
// 任務列表
|
||||
jobsList: "任務",
|
||||
jobsListDesc: "所有儲存在閘道器的排程任務。",
|
||||
|
||||
// 執行歷史
|
||||
runHistory: "執行歷史",
|
||||
runHistoryDesc: "最近的執行記錄:",
|
||||
selectJob: "選擇任務以檢視執行歷史。",
|
||||
noRuns: "尚無執行記錄。",
|
||||
|
||||
form: {
|
||||
schedule: "排程(cron 格式)",
|
||||
message: "訊息",
|
||||
@ -343,6 +394,7 @@ export const zhTW = {
|
||||
desc: "管理內建與已安裝的技能。",
|
||||
noSkills: "找不到技能。",
|
||||
filter: "篩選技能",
|
||||
shown: "個顯示中",
|
||||
apiKey: "API 金鑰",
|
||||
saveKey: "儲存金鑰",
|
||||
install: "安裝",
|
||||
@ -352,6 +404,11 @@ export const zhTW = {
|
||||
toggle: "切換",
|
||||
keySaved: "API 金鑰已儲存",
|
||||
keyError: "儲存 API 金鑰失敗",
|
||||
eligible: "可用",
|
||||
blocked: "已封鎖",
|
||||
missing: "缺少",
|
||||
reason: "原因",
|
||||
blockedByAllowlist: "被許可清單封鎖",
|
||||
},
|
||||
|
||||
// 節點頁面
|
||||
@ -434,14 +491,29 @@ export const zhTW = {
|
||||
status: "狀態",
|
||||
health: "健康狀態",
|
||||
models: "模型",
|
||||
modelsDesc: "來自 models.list 的目錄。",
|
||||
heartbeat: "心跳",
|
||||
lastHeartbeat: "上次心跳",
|
||||
events: "事件",
|
||||
eventLog: "事件日誌",
|
||||
eventLogDesc: "最新的閘道器事件。",
|
||||
noEvents: "尚無事件。",
|
||||
rpcCall: "RPC 呼叫",
|
||||
manualRpc: "手動 RPC",
|
||||
manualRpcDesc: "使用 JSON 參數發送原始閘道器方法。",
|
||||
method: "方法",
|
||||
params: "參數",
|
||||
paramsJson: "參數(JSON)",
|
||||
call: "呼叫",
|
||||
result: "結果",
|
||||
noResult: "尚無結果。",
|
||||
snapshots: "快照",
|
||||
snapshotsDesc: "狀態、健康狀態與心跳資料。",
|
||||
securityAudit: "安全稽核",
|
||||
critical: "嚴重",
|
||||
warnings: "警告",
|
||||
noCritical: "無嚴重問題",
|
||||
runAuditCmd: "執行以檢視詳情。",
|
||||
},
|
||||
|
||||
// 日誌頁面
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { html, nothing } from "lit";
|
||||
import { t } from "../../i18n";
|
||||
import type { ConfigUiHints } from "../types";
|
||||
import { analyzeConfigSchema, renderConfigForm, SECTION_META } from "./config-form";
|
||||
import {
|
||||
@ -73,21 +74,15 @@ const sidebarIcons = {
|
||||
default: html`<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>`,
|
||||
};
|
||||
|
||||
// Section definitions
|
||||
const SECTIONS: Array<{ key: string; label: string }> = [
|
||||
{ key: "env", label: "Environment" },
|
||||
{ key: "update", label: "Updates" },
|
||||
{ key: "agents", label: "Agents" },
|
||||
{ key: "auth", label: "Authentication" },
|
||||
{ key: "channels", label: "Channels" },
|
||||
{ key: "messages", label: "Messages" },
|
||||
{ key: "commands", label: "Commands" },
|
||||
{ key: "hooks", label: "Hooks" },
|
||||
{ key: "skills", label: "Skills" },
|
||||
{ key: "tools", label: "Tools" },
|
||||
{ key: "gateway", label: "Gateway" },
|
||||
{ key: "wizard", label: "Setup Wizard" },
|
||||
];
|
||||
// Section definitions - labels are resolved via t() at render time
|
||||
const SECTION_KEYS = [
|
||||
"env", "update", "agents", "auth", "channels", "messages",
|
||||
"commands", "hooks", "skills", "tools", "gateway", "wizard",
|
||||
] as const;
|
||||
|
||||
function getSectionLabel(key: string): string {
|
||||
return t(`config.sections.${key}`) || humanize(key);
|
||||
}
|
||||
|
||||
type SubsectionEntry = {
|
||||
key: string;
|
||||
@ -191,13 +186,15 @@ export function renderConfig(props: ConfigProps) {
|
||||
|
||||
// Get available sections from schema
|
||||
const schemaProps = analysis.schema?.properties ?? {};
|
||||
const availableSections = SECTIONS.filter(s => s.key in schemaProps);
|
||||
const knownKeys = new Set(SECTION_KEYS);
|
||||
const availableSections = SECTION_KEYS
|
||||
.filter(k => k in schemaProps)
|
||||
.map(k => ({ key: k, label: getSectionLabel(k) }));
|
||||
|
||||
// Add any sections in schema but not in our list
|
||||
const knownKeys = new Set(SECTIONS.map(s => s.key));
|
||||
const extraSections = Object.keys(schemaProps)
|
||||
.filter(k => !knownKeys.has(k))
|
||||
.map(k => ({ key: k, label: k.charAt(0).toUpperCase() + k.slice(1) }));
|
||||
.map(k => ({ key: k, label: getSectionLabel(k) }));
|
||||
|
||||
const allSections = [...availableSections, ...extraSections];
|
||||
|
||||
@ -255,8 +252,8 @@ export function renderConfig(props: ConfigProps) {
|
||||
<!-- Sidebar -->
|
||||
<aside class="config-sidebar">
|
||||
<div class="config-sidebar__header">
|
||||
<div class="config-sidebar__title">Settings</div>
|
||||
<span class="pill pill--sm ${validity === "valid" ? "pill--ok" : validity === "invalid" ? "pill--danger" : ""}">${validity}</span>
|
||||
<div class="config-sidebar__title">${t("config.title")}</div>
|
||||
<span class="pill pill--sm ${validity === "valid" ? "pill--ok" : validity === "invalid" ? "pill--danger" : ""}">${t(`config.${validity}`)}</span>
|
||||
</div>
|
||||
|
||||
<!-- Search -->
|
||||
@ -268,7 +265,7 @@ export function renderConfig(props: ConfigProps) {
|
||||
<input
|
||||
type="text"
|
||||
class="config-search__input"
|
||||
placeholder="Search settings..."
|
||||
placeholder=${t("config.searchSettings")}
|
||||
.value=${props.searchQuery}
|
||||
@input=${(e: Event) => props.onSearchChange((e.target as HTMLInputElement).value)}
|
||||
/>
|
||||
@ -287,7 +284,7 @@ export function renderConfig(props: ConfigProps) {
|
||||
@click=${() => props.onSectionChange(null)}
|
||||
>
|
||||
<span class="config-nav__icon">${sidebarIcons.all}</span>
|
||||
<span class="config-nav__label">All Settings</span>
|
||||
<span class="config-nav__label">${t("config.allSettings")}</span>
|
||||
</button>
|
||||
${allSections.map(section => html`
|
||||
<button
|
||||
@ -308,13 +305,13 @@ export function renderConfig(props: ConfigProps) {
|
||||
?disabled=${props.schemaLoading || !props.schema}
|
||||
@click=${() => props.onFormModeChange("form")}
|
||||
>
|
||||
Form
|
||||
${t("config.form")}
|
||||
</button>
|
||||
<button
|
||||
class="config-mode-toggle__btn ${props.formMode === "raw" ? "active" : ""}"
|
||||
@click=${() => props.onFormModeChange("raw")}
|
||||
>
|
||||
Raw
|
||||
${t("config.raw")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -326,35 +323,35 @@ export function renderConfig(props: ConfigProps) {
|
||||
<div class="config-actions">
|
||||
<div class="config-actions__left">
|
||||
${hasChanges ? html`
|
||||
<span class="config-changes-badge">${props.formMode === "raw" ? "Unsaved changes" : `${diff.length} unsaved change${diff.length !== 1 ? "s" : ""}`}</span>
|
||||
<span class="config-changes-badge">${props.formMode === "raw" ? t("config.unsavedChanges") : t("config.unsavedCount", { count: diff.length })}</span>
|
||||
` : html`
|
||||
<span class="config-status muted">No changes</span>
|
||||
<span class="config-status muted">${t("config.noChanges")}</span>
|
||||
`}
|
||||
</div>
|
||||
<div class="config-actions__right">
|
||||
<button class="btn btn--sm" ?disabled=${props.loading} @click=${props.onReload}>
|
||||
${props.loading ? "Loading…" : "Reload"}
|
||||
${props.loading ? t("common.loading") : t("config.reload")}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn--sm primary"
|
||||
?disabled=${!canSave}
|
||||
@click=${props.onSave}
|
||||
>
|
||||
${props.saving ? "Saving…" : "Save"}
|
||||
${props.saving ? t("common.saving") : t("common.save")}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn--sm"
|
||||
?disabled=${!canApply}
|
||||
@click=${props.onApply}
|
||||
>
|
||||
${props.applying ? "Applying…" : "Apply"}
|
||||
${props.applying ? t("common.applying") : t("common.apply")}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn--sm"
|
||||
?disabled=${!canUpdate}
|
||||
@click=${props.onUpdate}
|
||||
>
|
||||
${props.updating ? "Updating…" : "Update"}
|
||||
${props.updating ? t("config.updating") : t("config.update")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -363,7 +360,7 @@ export function renderConfig(props: ConfigProps) {
|
||||
${hasChanges && props.formMode === "form" ? html`
|
||||
<details class="config-diff">
|
||||
<summary class="config-diff__summary">
|
||||
<span>View ${diff.length} pending change${diff.length !== 1 ? "s" : ""}</span>
|
||||
<span>${t("config.viewPending", { count: diff.length })}</span>
|
||||
<svg class="config-diff__chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
@ -404,7 +401,7 @@ export function renderConfig(props: ConfigProps) {
|
||||
class="config-subnav__item ${effectiveSubsection === null ? "active" : ""}"
|
||||
@click=${() => props.onSubsectionChange(ALL_SUBSECTION)}
|
||||
>
|
||||
All
|
||||
${t("common.all")}
|
||||
</button>
|
||||
${subsections.map(
|
||||
(entry) => html`
|
||||
@ -430,7 +427,7 @@ export function renderConfig(props: ConfigProps) {
|
||||
${props.schemaLoading
|
||||
? html`<div class="config-loading">
|
||||
<div class="config-loading__spinner"></div>
|
||||
<span>Loading schema…</span>
|
||||
<span>${t("config.loadingSchema")}</span>
|
||||
</div>`
|
||||
: renderConfigForm({
|
||||
schema: analysis.schema,
|
||||
@ -445,14 +442,13 @@ export function renderConfig(props: ConfigProps) {
|
||||
})}
|
||||
${formUnsafe
|
||||
? html`<div class="callout danger" style="margin-top: 12px;">
|
||||
Form view can't safely edit some fields.
|
||||
Use Raw to avoid losing config entries.
|
||||
${t("config.formUnsafe")}
|
||||
</div>`
|
||||
: nothing}
|
||||
`
|
||||
: html`
|
||||
<label class="field config-raw-field">
|
||||
<span>Raw JSON5</span>
|
||||
<span>${t("config.rawJson5")}</span>
|
||||
<textarea
|
||||
.value=${props.raw}
|
||||
@input=${(e: Event) =>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { html, nothing } from "lit";
|
||||
|
||||
import { t } from "../../i18n";
|
||||
import { formatMs } from "../format";
|
||||
import {
|
||||
formatCronPayload,
|
||||
@ -57,42 +58,42 @@ export function renderCron(props: CronProps) {
|
||||
return html`
|
||||
<section class="grid grid-cols-2">
|
||||
<div class="card">
|
||||
<div class="card-title">Scheduler</div>
|
||||
<div class="card-sub">Gateway-owned cron scheduler status.</div>
|
||||
<div class="card-title">${t("cron.scheduler")}</div>
|
||||
<div class="card-sub">${t("cron.schedulerDesc")}</div>
|
||||
<div class="stat-grid" style="margin-top: 16px;">
|
||||
<div class="stat">
|
||||
<div class="stat-label">Enabled</div>
|
||||
<div class="stat-label">${t("common.enabled")}</div>
|
||||
<div class="stat-value">
|
||||
${props.status
|
||||
? props.status.enabled
|
||||
? "Yes"
|
||||
: "No"
|
||||
: "n/a"}
|
||||
? t("common.yes")
|
||||
: t("common.no")
|
||||
: t("common.na")}
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-label">Jobs</div>
|
||||
<div class="stat-value">${props.status?.jobs ?? "n/a"}</div>
|
||||
<div class="stat-label">${t("cron.jobs")}</div>
|
||||
<div class="stat-value">${props.status?.jobs ?? t("common.na")}</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-label">Next wake</div>
|
||||
<div class="stat-label">${t("cron.status.nextWake")}</div>
|
||||
<div class="stat-value">${formatNextRun(props.status?.nextWakeAtMs ?? null)}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" style="margin-top: 12px;">
|
||||
<button class="btn" ?disabled=${props.loading} @click=${props.onRefresh}>
|
||||
${props.loading ? "Refreshing…" : "Refresh"}
|
||||
${props.loading ? t("common.loading") : t("common.refresh")}
|
||||
</button>
|
||||
${props.error ? html`<span class="muted">${props.error}</span>` : nothing}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">New Job</div>
|
||||
<div class="card-sub">Create a scheduled wakeup or agent run.</div>
|
||||
<div class="card-title">${t("cron.newJob")}</div>
|
||||
<div class="card-sub">${t("cron.newJobDesc")}</div>
|
||||
<div class="form-grid" style="margin-top: 16px;">
|
||||
<label class="field">
|
||||
<span>Name</span>
|
||||
<span>${t("cron.name")}</span>
|
||||
<input
|
||||
.value=${props.form.name}
|
||||
@input=${(e: Event) =>
|
||||
@ -100,7 +101,7 @@ export function renderCron(props: CronProps) {
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Description</span>
|
||||
<span>${t("cron.description")}</span>
|
||||
<input
|
||||
.value=${props.form.description}
|
||||
@input=${(e: Event) =>
|
||||
@ -108,16 +109,16 @@ export function renderCron(props: CronProps) {
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Agent ID</span>
|
||||
<span>${t("cron.agentId")}</span>
|
||||
<input
|
||||
.value=${props.form.agentId}
|
||||
@input=${(e: Event) =>
|
||||
props.onFormChange({ agentId: (e.target as HTMLInputElement).value })}
|
||||
placeholder="default"
|
||||
placeholder=${t("cron.agentIdPlaceholder")}
|
||||
/>
|
||||
</label>
|
||||
<label class="field checkbox">
|
||||
<span>Enabled</span>
|
||||
<span>${t("common.enabled")}</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${props.form.enabled}
|
||||
@ -126,7 +127,7 @@ export function renderCron(props: CronProps) {
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Schedule</span>
|
||||
<span>${t("cron.scheduleKind")}</span>
|
||||
<select
|
||||
.value=${props.form.scheduleKind}
|
||||
@change=${(e: Event) =>
|
||||
@ -134,16 +135,16 @@ export function renderCron(props: CronProps) {
|
||||
scheduleKind: (e.target as HTMLSelectElement).value as CronFormState["scheduleKind"],
|
||||
})}
|
||||
>
|
||||
<option value="every">Every</option>
|
||||
<option value="at">At</option>
|
||||
<option value="cron">Cron</option>
|
||||
<option value="every">${t("cron.everyLabel")}</option>
|
||||
<option value="at">${t("cron.atLabel")}</option>
|
||||
<option value="cron">${t("cron.cronLabel")}</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
${renderScheduleFields(props)}
|
||||
<div class="form-grid" style="margin-top: 12px;">
|
||||
<label class="field">
|
||||
<span>Session</span>
|
||||
<span>${t("cron.session")}</span>
|
||||
<select
|
||||
.value=${props.form.sessionTarget}
|
||||
@change=${(e: Event) =>
|
||||
@ -151,12 +152,12 @@ export function renderCron(props: CronProps) {
|
||||
sessionTarget: (e.target as HTMLSelectElement).value as CronFormState["sessionTarget"],
|
||||
})}
|
||||
>
|
||||
<option value="main">Main</option>
|
||||
<option value="isolated">Isolated</option>
|
||||
<option value="main">${t("cron.main")}</option>
|
||||
<option value="isolated">${t("cron.isolated")}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Wake mode</span>
|
||||
<span>${t("cron.wakeMode")}</span>
|
||||
<select
|
||||
.value=${props.form.wakeMode}
|
||||
@change=${(e: Event) =>
|
||||
@ -164,12 +165,12 @@ export function renderCron(props: CronProps) {
|
||||
wakeMode: (e.target as HTMLSelectElement).value as CronFormState["wakeMode"],
|
||||
})}
|
||||
>
|
||||
<option value="next-heartbeat">Next heartbeat</option>
|
||||
<option value="now">Now</option>
|
||||
<option value="next-heartbeat">${t("cron.nextHeartbeat")}</option>
|
||||
<option value="now">${t("cron.now")}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Payload</span>
|
||||
<span>${t("cron.payload")}</span>
|
||||
<select
|
||||
.value=${props.form.payloadKind}
|
||||
@change=${(e: Event) =>
|
||||
@ -177,13 +178,13 @@ export function renderCron(props: CronProps) {
|
||||
payloadKind: (e.target as HTMLSelectElement).value as CronFormState["payloadKind"],
|
||||
})}
|
||||
>
|
||||
<option value="systemEvent">System event</option>
|
||||
<option value="agentTurn">Agent turn</option>
|
||||
<option value="systemEvent">${t("cron.systemEvent")}</option>
|
||||
<option value="agentTurn">${t("cron.agentTurn")}</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<label class="field" style="margin-top: 12px;">
|
||||
<span>${props.form.payloadKind === "systemEvent" ? "System text" : "Agent message"}</span>
|
||||
<span>${props.form.payloadKind === "systemEvent" ? t("cron.systemText") : t("cron.agentMessage")}</span>
|
||||
<textarea
|
||||
.value=${props.form.payloadText}
|
||||
@input=${(e: Event) =>
|
||||
@ -197,7 +198,7 @@ export function renderCron(props: CronProps) {
|
||||
? html`
|
||||
<div class="form-grid" style="margin-top: 12px;">
|
||||
<label class="field checkbox">
|
||||
<span>Deliver</span>
|
||||
<span>${t("cron.deliver")}</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
.checked=${props.form.deliver}
|
||||
@ -208,7 +209,7 @@ export function renderCron(props: CronProps) {
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Channel</span>
|
||||
<span>${t("cron.form.channel")}</span>
|
||||
<select
|
||||
.value=${props.form.channel || "last"}
|
||||
@change=${(e: Event) =>
|
||||
@ -225,16 +226,16 @@ export function renderCron(props: CronProps) {
|
||||
</select>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>To</span>
|
||||
<span>${t("cron.to")}</span>
|
||||
<input
|
||||
.value=${props.form.to}
|
||||
@input=${(e: Event) =>
|
||||
props.onFormChange({ to: (e.target as HTMLInputElement).value })}
|
||||
placeholder="+1555… or chat id"
|
||||
placeholder=${t("cron.toPlaceholder")}
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Timeout (seconds)</span>
|
||||
<span>${t("cron.timeoutSeconds")}</span>
|
||||
<input
|
||||
.value=${props.form.timeoutSeconds}
|
||||
@input=${(e: Event) =>
|
||||
@ -246,7 +247,7 @@ export function renderCron(props: CronProps) {
|
||||
${props.form.sessionTarget === "isolated"
|
||||
? html`
|
||||
<label class="field">
|
||||
<span>Post to main prefix</span>
|
||||
<span>${t("cron.postToMainPrefix")}</span>
|
||||
<input
|
||||
.value=${props.form.postToMainPrefix}
|
||||
@input=${(e: Event) =>
|
||||
@ -262,17 +263,17 @@ export function renderCron(props: CronProps) {
|
||||
: nothing}
|
||||
<div class="row" style="margin-top: 14px;">
|
||||
<button class="btn primary" ?disabled=${props.busy} @click=${props.onAdd}>
|
||||
${props.busy ? "Saving…" : "Add job"}
|
||||
${props.busy ? t("common.saving") : t("cron.addJob")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card" style="margin-top: 18px;">
|
||||
<div class="card-title">Jobs</div>
|
||||
<div class="card-sub">All scheduled jobs stored in the gateway.</div>
|
||||
<div class="card-title">${t("cron.jobsList")}</div>
|
||||
<div class="card-sub">${t("cron.jobsListDesc")}</div>
|
||||
${props.jobs.length === 0
|
||||
? html`<div class="muted" style="margin-top: 12px;">No jobs yet.</div>`
|
||||
? html`<div class="muted" style="margin-top: 12px;">${t("cron.noJobs")}</div>`
|
||||
: html`
|
||||
<div class="list" style="margin-top: 12px;">
|
||||
${props.jobs.map((job) => renderJob(job, props))}
|
||||
@ -281,16 +282,16 @@ export function renderCron(props: CronProps) {
|
||||
</section>
|
||||
|
||||
<section class="card" style="margin-top: 18px;">
|
||||
<div class="card-title">Run history</div>
|
||||
<div class="card-sub">Latest runs for ${props.runsJobId ?? "(select a job)"}.</div>
|
||||
<div class="card-title">${t("cron.runHistory")}</div>
|
||||
<div class="card-sub">${t("cron.runHistoryDesc")} ${props.runsJobId ?? ""}.</div>
|
||||
${props.runsJobId == null
|
||||
? html`
|
||||
<div class="muted" style="margin-top: 12px;">
|
||||
Select a job to inspect run history.
|
||||
${t("cron.selectJob")}
|
||||
</div>
|
||||
`
|
||||
: props.runs.length === 0
|
||||
? html`<div class="muted" style="margin-top: 12px;">No runs yet.</div>`
|
||||
? html`<div class="muted" style="margin-top: 12px;">${t("cron.noRuns")}</div>`
|
||||
: html`
|
||||
<div class="list" style="margin-top: 12px;">
|
||||
${props.runs.map((entry) => renderRun(entry))}
|
||||
@ -305,7 +306,7 @@ function renderScheduleFields(props: CronProps) {
|
||||
if (form.scheduleKind === "at") {
|
||||
return html`
|
||||
<label class="field" style="margin-top: 12px;">
|
||||
<span>Run at</span>
|
||||
<span>${t("cron.runAt")}</span>
|
||||
<input
|
||||
type="datetime-local"
|
||||
.value=${form.scheduleAt}
|
||||
@ -321,7 +322,7 @@ function renderScheduleFields(props: CronProps) {
|
||||
return html`
|
||||
<div class="form-grid" style="margin-top: 12px;">
|
||||
<label class="field">
|
||||
<span>Every</span>
|
||||
<span>${t("cron.every")}</span>
|
||||
<input
|
||||
.value=${form.everyAmount}
|
||||
@input=${(e: Event) =>
|
||||
@ -331,7 +332,7 @@ function renderScheduleFields(props: CronProps) {
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Unit</span>
|
||||
<span>${t("cron.unit")}</span>
|
||||
<select
|
||||
.value=${form.everyUnit}
|
||||
@change=${(e: Event) =>
|
||||
@ -339,9 +340,9 @@ function renderScheduleFields(props: CronProps) {
|
||||
everyUnit: (e.target as HTMLSelectElement).value as CronFormState["everyUnit"],
|
||||
})}
|
||||
>
|
||||
<option value="minutes">Minutes</option>
|
||||
<option value="hours">Hours</option>
|
||||
<option value="days">Days</option>
|
||||
<option value="minutes">${t("cron.minutes")}</option>
|
||||
<option value="hours">${t("cron.hours")}</option>
|
||||
<option value="days">${t("cron.days")}</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
@ -350,7 +351,7 @@ function renderScheduleFields(props: CronProps) {
|
||||
return html`
|
||||
<div class="form-grid" style="margin-top: 12px;">
|
||||
<label class="field">
|
||||
<span>Expression</span>
|
||||
<span>${t("cron.expression")}</span>
|
||||
<input
|
||||
.value=${form.cronExpr}
|
||||
@input=${(e: Event) =>
|
||||
@ -358,7 +359,7 @@ function renderScheduleFields(props: CronProps) {
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Timezone (optional)</span>
|
||||
<span>${t("cron.timezone")}</span>
|
||||
<input
|
||||
.value=${form.cronTz}
|
||||
@input=${(e: Event) =>
|
||||
@ -396,7 +397,7 @@ function renderJob(job: CronJob, props: CronProps) {
|
||||
props.onToggle(job, !job.enabled);
|
||||
}}
|
||||
>
|
||||
${job.enabled ? "Disable" : "Enable"}
|
||||
${job.enabled ? t("cron.disable") : t("cron.enable")}
|
||||
</button>
|
||||
<button
|
||||
class="btn"
|
||||
@ -406,7 +407,7 @@ function renderJob(job: CronJob, props: CronProps) {
|
||||
props.onRun(job);
|
||||
}}
|
||||
>
|
||||
Run
|
||||
${t("cron.runNow")}
|
||||
</button>
|
||||
<button
|
||||
class="btn"
|
||||
@ -416,7 +417,7 @@ function renderJob(job: CronJob, props: CronProps) {
|
||||
props.onLoadRuns(job.id);
|
||||
}}
|
||||
>
|
||||
Runs
|
||||
${t("cron.runs")}
|
||||
</button>
|
||||
<button
|
||||
class="btn danger"
|
||||
@ -426,7 +427,7 @@ function renderJob(job: CronJob, props: CronProps) {
|
||||
props.onRemove(job);
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
${t("cron.remove")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { html, nothing } from "lit";
|
||||
|
||||
import { t } from "../../i18n";
|
||||
import { formatEventPayload } from "../presenter";
|
||||
import type { EventLogEntry } from "../app-events";
|
||||
|
||||
@ -32,51 +33,51 @@ export function renderDebug(props: DebugProps) {
|
||||
const securityTone = critical > 0 ? "danger" : warn > 0 ? "warn" : "success";
|
||||
const securityLabel =
|
||||
critical > 0
|
||||
? `${critical} critical`
|
||||
? `${critical} ${t("debug.critical")}`
|
||||
: warn > 0
|
||||
? `${warn} warnings`
|
||||
: "No critical issues";
|
||||
? `${warn} ${t("debug.warnings")}`
|
||||
: t("debug.noCritical");
|
||||
|
||||
return html`
|
||||
<section class="grid grid-cols-2">
|
||||
<div class="card">
|
||||
<div class="row" style="justify-content: space-between;">
|
||||
<div>
|
||||
<div class="card-title">Snapshots</div>
|
||||
<div class="card-sub">Status, health, and heartbeat data.</div>
|
||||
<div class="card-title">${t("debug.snapshots")}</div>
|
||||
<div class="card-sub">${t("debug.snapshotsDesc")}</div>
|
||||
</div>
|
||||
<button class="btn" ?disabled=${props.loading} @click=${props.onRefresh}>
|
||||
${props.loading ? "Refreshing…" : "Refresh"}
|
||||
${props.loading ? t("common.loading") : t("common.refresh")}
|
||||
</button>
|
||||
</div>
|
||||
<div class="stack" style="margin-top: 12px;">
|
||||
<div>
|
||||
<div class="muted">Status</div>
|
||||
<div class="muted">${t("debug.status")}</div>
|
||||
${securitySummary
|
||||
? html`<div class="callout ${securityTone}" style="margin-top: 8px;">
|
||||
Security audit: ${securityLabel}${info > 0 ? ` · ${info} info` : ""}. Run
|
||||
<span class="mono">moltbot security audit --deep</span> for details.
|
||||
${t("debug.securityAudit")}: ${securityLabel}${info > 0 ? ` · ${info} ${t("common.info").toLowerCase()}` : ""}. ${t("debug.runAuditCmd")}
|
||||
<span class="mono">moltbot security audit --deep</span>
|
||||
</div>`
|
||||
: nothing}
|
||||
<pre class="code-block">${JSON.stringify(props.status ?? {}, null, 2)}</pre>
|
||||
</div>
|
||||
<div>
|
||||
<div class="muted">Health</div>
|
||||
<div class="muted">${t("debug.health")}</div>
|
||||
<pre class="code-block">${JSON.stringify(props.health ?? {}, null, 2)}</pre>
|
||||
</div>
|
||||
<div>
|
||||
<div class="muted">Last heartbeat</div>
|
||||
<div class="muted">${t("debug.lastHeartbeat")}</div>
|
||||
<pre class="code-block">${JSON.stringify(props.heartbeat ?? {}, null, 2)}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-title">Manual RPC</div>
|
||||
<div class="card-sub">Send a raw gateway method with JSON params.</div>
|
||||
<div class="card-title">${t("debug.manualRpc")}</div>
|
||||
<div class="card-sub">${t("debug.manualRpcDesc")}</div>
|
||||
<div class="form-grid" style="margin-top: 16px;">
|
||||
<label class="field">
|
||||
<span>Method</span>
|
||||
<span>${t("debug.method")}</span>
|
||||
<input
|
||||
.value=${props.callMethod}
|
||||
@input=${(e: Event) =>
|
||||
@ -85,7 +86,7 @@ export function renderDebug(props: DebugProps) {
|
||||
/>
|
||||
</label>
|
||||
<label class="field">
|
||||
<span>Params (JSON)</span>
|
||||
<span>${t("debug.paramsJson")}</span>
|
||||
<textarea
|
||||
.value=${props.callParams}
|
||||
@input=${(e: Event) =>
|
||||
@ -95,7 +96,7 @@ export function renderDebug(props: DebugProps) {
|
||||
</label>
|
||||
</div>
|
||||
<div class="row" style="margin-top: 12px;">
|
||||
<button class="btn primary" @click=${props.onCall}>Call</button>
|
||||
<button class="btn primary" @click=${props.onCall}>${t("debug.call")}</button>
|
||||
</div>
|
||||
${props.callError
|
||||
? html`<div class="callout danger" style="margin-top: 12px;">
|
||||
@ -109,8 +110,8 @@ export function renderDebug(props: DebugProps) {
|
||||
</section>
|
||||
|
||||
<section class="card" style="margin-top: 18px;">
|
||||
<div class="card-title">Models</div>
|
||||
<div class="card-sub">Catalog from models.list.</div>
|
||||
<div class="card-title">${t("debug.models")}</div>
|
||||
<div class="card-sub">${t("debug.modelsDesc")}</div>
|
||||
<pre class="code-block" style="margin-top: 12px;">${JSON.stringify(
|
||||
props.models ?? [],
|
||||
null,
|
||||
@ -119,10 +120,10 @@ export function renderDebug(props: DebugProps) {
|
||||
</section>
|
||||
|
||||
<section class="card" style="margin-top: 18px;">
|
||||
<div class="card-title">Event Log</div>
|
||||
<div class="card-sub">Latest gateway events.</div>
|
||||
<div class="card-title">${t("debug.eventLog")}</div>
|
||||
<div class="card-sub">${t("debug.eventLogDesc")}</div>
|
||||
${props.eventLog.length === 0
|
||||
? html`<div class="muted" style="margin-top: 12px;">No events yet.</div>`
|
||||
? html`<div class="muted" style="margin-top: 12px;">${t("debug.noEvents")}</div>`
|
||||
: html`
|
||||
<div class="list" style="margin-top: 12px;">
|
||||
${props.eventLog.map(
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { html, nothing } from "lit";
|
||||
|
||||
import { t } from "../../i18n";
|
||||
import { clampText } from "../format";
|
||||
import type { SkillStatusEntry, SkillStatusReport } from "../types";
|
||||
import type { SkillMessageMap } from "../controllers/skills";
|
||||
@ -36,25 +37,25 @@ export function renderSkills(props: SkillsProps) {
|
||||
<section class="card">
|
||||
<div class="row" style="justify-content: space-between;">
|
||||
<div>
|
||||
<div class="card-title">Skills</div>
|
||||
<div class="card-sub">Bundled, managed, and workspace skills.</div>
|
||||
<div class="card-title">${t("skills.title")}</div>
|
||||
<div class="card-sub">${t("skills.desc")}</div>
|
||||
</div>
|
||||
<button class="btn" ?disabled=${props.loading} @click=${props.onRefresh}>
|
||||
${props.loading ? "Loading…" : "Refresh"}
|
||||
${props.loading ? t("common.loading") : t("common.refresh")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="filters" style="margin-top: 14px;">
|
||||
<label class="field" style="flex: 1;">
|
||||
<span>Filter</span>
|
||||
<span>${t("common.filter")}</span>
|
||||
<input
|
||||
.value=${props.filter}
|
||||
@input=${(e: Event) =>
|
||||
props.onFilterChange((e.target as HTMLInputElement).value)}
|
||||
placeholder="Search skills"
|
||||
placeholder=${t("skills.filter")}
|
||||
/>
|
||||
</label>
|
||||
<div class="muted">${filtered.length} shown</div>
|
||||
<div class="muted">${filtered.length} ${t("skills.shown")}</div>
|
||||
</div>
|
||||
|
||||
${props.error
|
||||
@ -62,7 +63,7 @@ export function renderSkills(props: SkillsProps) {
|
||||
: nothing}
|
||||
|
||||
${filtered.length === 0
|
||||
? html`<div class="muted" style="margin-top: 16px;">No skills found.</div>`
|
||||
? html`<div class="muted" style="margin-top: 16px;">${t("skills.noSkills")}</div>`
|
||||
: html`
|
||||
<div class="list" style="margin-top: 16px;">
|
||||
${filtered.map((skill) => renderSkill(skill, props))}
|
||||
@ -85,8 +86,8 @@ function renderSkill(skill: SkillStatusEntry, props: SkillsProps) {
|
||||
...skill.missing.os.map((o) => `os:${o}`),
|
||||
];
|
||||
const reasons: string[] = [];
|
||||
if (skill.disabled) reasons.push("disabled");
|
||||
if (skill.blockedByAllowlist) reasons.push("blocked by allowlist");
|
||||
if (skill.disabled) reasons.push(t("skills.disabled").toLowerCase());
|
||||
if (skill.blockedByAllowlist) reasons.push(t("skills.blockedByAllowlist"));
|
||||
return html`
|
||||
<div class="list-item">
|
||||
<div class="list-main">
|
||||
@ -97,21 +98,21 @@ function renderSkill(skill: SkillStatusEntry, props: SkillsProps) {
|
||||
<div class="chip-row" style="margin-top: 6px;">
|
||||
<span class="chip">${skill.source}</span>
|
||||
<span class="chip ${skill.eligible ? "chip-ok" : "chip-warn"}">
|
||||
${skill.eligible ? "eligible" : "blocked"}
|
||||
${skill.eligible ? t("skills.eligible") : t("skills.blocked")}
|
||||
</span>
|
||||
${skill.disabled ? html`<span class="chip chip-warn">disabled</span>` : nothing}
|
||||
${skill.disabled ? html`<span class="chip chip-warn">${t("skills.disabled").toLowerCase()}</span>` : nothing}
|
||||
</div>
|
||||
${missing.length > 0
|
||||
? html`
|
||||
<div class="muted" style="margin-top: 6px;">
|
||||
Missing: ${missing.join(", ")}
|
||||
${t("skills.missing")}: ${missing.join(", ")}
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
${reasons.length > 0
|
||||
? html`
|
||||
<div class="muted" style="margin-top: 6px;">
|
||||
Reason: ${reasons.join(", ")}
|
||||
${t("skills.reason")}: ${reasons.join(", ")}
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
@ -123,7 +124,7 @@ function renderSkill(skill: SkillStatusEntry, props: SkillsProps) {
|
||||
?disabled=${busy}
|
||||
@click=${() => props.onToggle(skill.skillKey, skill.disabled)}
|
||||
>
|
||||
${skill.disabled ? "Enable" : "Disable"}
|
||||
${skill.disabled ? t("cron.enable") : t("cron.disable")}
|
||||
</button>
|
||||
${canInstall
|
||||
? html`<button
|
||||
@ -132,7 +133,7 @@ function renderSkill(skill: SkillStatusEntry, props: SkillsProps) {
|
||||
@click=${() =>
|
||||
props.onInstall(skill.skillKey, skill.name, skill.install[0].id)}
|
||||
>
|
||||
${busy ? "Installing…" : skill.install[0].label}
|
||||
${busy ? t("skills.installing") : skill.install[0].label}
|
||||
</button>`
|
||||
: nothing}
|
||||
</div>
|
||||
@ -151,7 +152,7 @@ function renderSkill(skill: SkillStatusEntry, props: SkillsProps) {
|
||||
${skill.primaryEnv
|
||||
? html`
|
||||
<div class="field" style="margin-top: 10px;">
|
||||
<span>API key</span>
|
||||
<span>${t("skills.apiKey")}</span>
|
||||
<input
|
||||
type="password"
|
||||
.value=${apiKey}
|
||||
@ -165,7 +166,7 @@ function renderSkill(skill: SkillStatusEntry, props: SkillsProps) {
|
||||
?disabled=${busy}
|
||||
@click=${() => props.onSaveKey(skill.skillKey)}
|
||||
>
|
||||
Save key
|
||||
${t("skills.saveKey")}
|
||||
</button>
|
||||
`
|
||||
: nothing}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user