dsh-config-manager 0.1.37 → 0.1.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.d.ts +173 -66
- package/lib/client.js +1643 -1115
- package/lib/client.js.map +1 -1
- package/lib/index.js +175 -1
- package/lib/index.js.map +1 -1
- package/lib/market/git-file-writer.d.ts +86 -0
- package/lib/market/git-file-writer.js +269 -0
- package/lib/market/git-file-writer.js.map +1 -0
- package/lib/market/github-repos.d.ts +122 -0
- package/lib/market/github-repos.js +407 -0
- package/lib/market/github-repos.js.map +1 -0
- package/lib/market/my-repo.d.ts +131 -0
- package/lib/market/my-repo.js +389 -0
- package/lib/market/my-repo.js.map +1 -0
- package/lib/ui/i18n.d.ts +9 -10
- package/lib/ui/i18n.js +19 -21
- package/lib/ui/i18n.js.map +1 -1
- package/package.json +1 -1
- package/src/client/ConfigManagerSection.tsx +2 -2
- package/src/client/client-types.ts +2 -0
- package/src/client/index.ts +4 -2
- package/src/client/market/MarketPanel.tsx +64 -17
- package/src/client/market/MyConfigsView.tsx +745 -0
- package/src/client/market/market-api.test.ts +29 -0
- package/src/client/market/market-api.ts +5 -2
- package/src/client/market/market-locales.ts +118 -117
- package/src/client/market/my-configs-api.ts +163 -0
- package/src/client/market/my-configs-view.test.ts +289 -0
- package/src/client/market/my-configs-view.ts +304 -0
- package/src/client/run-store.ts +13 -0
- package/src/index.ts +163 -1
- package/src/market/git-file-writer.test.ts +249 -0
- package/src/market/git-file-writer.ts +336 -0
- package/src/market/github-repos.test.ts +400 -0
- package/src/market/github-repos.ts +481 -0
- package/src/market/my-repo.test.ts +472 -0
- package/src/market/my-repo.ts +516 -0
- package/src/ui/i18n.ts +19 -21
- package/lib/ui/market-publish.d.ts +0 -129
- package/lib/ui/market-publish.js +0 -168
- package/lib/ui/market-publish.js.map +0 -1
- package/src/client/market/PublishView.tsx +0 -388
- package/src/ui/market-publish.test.ts +0 -219
- package/src/ui/market-publish.ts +0 -256
package/lib/client.js
CHANGED
|
@@ -253,16 +253,15 @@ const uiZh = {
|
|
|
253
253
|
"market.detail.sectionsEmpty": "未声明分区",
|
|
254
254
|
"market.detail.statusValid": "校验通过",
|
|
255
255
|
"market.detail.statusInvalid": "校验未通过",
|
|
256
|
-
"
|
|
257
|
-
"
|
|
258
|
-
"
|
|
259
|
-
"
|
|
260
|
-
"
|
|
261
|
-
"
|
|
262
|
-
"
|
|
263
|
-
"
|
|
264
|
-
"
|
|
265
|
-
"marketPublish.repoUserinfo": "仓库地址不能包含用户名或密码(userinfo);凭据请通过 DSH credentials 提供"
|
|
256
|
+
"myConfigs.autoField": "系统自动",
|
|
257
|
+
"myConfigs.field.id": "条目 ID",
|
|
258
|
+
"myConfigs.field.author": "作者",
|
|
259
|
+
"myConfigs.field.version": "版本",
|
|
260
|
+
"myConfigs.field.updatedAt": "更新时间",
|
|
261
|
+
"myConfigs.status.notListed": "未收录",
|
|
262
|
+
"myConfigs.status.pendingPr": "PR 待审核",
|
|
263
|
+
"myConfigs.status.listed": "已收录",
|
|
264
|
+
"myConfigs.form.errorName": "名称不能为空"
|
|
266
265
|
};
|
|
267
266
|
const uiEn = {
|
|
268
267
|
"commonClose": "Close",
|
|
@@ -498,16 +497,15 @@ const uiEn = {
|
|
|
498
497
|
"market.detail.sectionsEmpty": "No sections declared",
|
|
499
498
|
"market.detail.statusValid": "Verified",
|
|
500
499
|
"market.detail.statusInvalid": "Failed verification",
|
|
501
|
-
"
|
|
502
|
-
"
|
|
503
|
-
"
|
|
504
|
-
"
|
|
505
|
-
"
|
|
506
|
-
"
|
|
507
|
-
"
|
|
508
|
-
"
|
|
509
|
-
"
|
|
510
|
-
"marketPublish.repoUserinfo": "Repository URL must not contain username/password (userinfo); provide credentials via DSH credentials"
|
|
500
|
+
"myConfigs.autoField": "Auto",
|
|
501
|
+
"myConfigs.field.id": "Item ID",
|
|
502
|
+
"myConfigs.field.author": "Author",
|
|
503
|
+
"myConfigs.field.version": "Version",
|
|
504
|
+
"myConfigs.field.updatedAt": "Updated",
|
|
505
|
+
"myConfigs.status.notListed": "Not listed",
|
|
506
|
+
"myConfigs.status.pendingPr": "PR pending review",
|
|
507
|
+
"myConfigs.status.listed": "Listed",
|
|
508
|
+
"myConfigs.form.errorName": "Name is required"
|
|
511
509
|
};
|
|
512
510
|
/** 构造 翻译函数(zh 源 / en 镜像;缺 key 回退)。 */
|
|
513
511
|
function makeUiT(lang) {
|
|
@@ -549,7 +547,7 @@ var ConfigManagerApiError = class extends Error {
|
|
|
549
547
|
}
|
|
550
548
|
};
|
|
551
549
|
/** 解析 JSON 响应;非 2xx 时抛出带路由 error 消息的 ConfigManagerApiError */
|
|
552
|
-
async function readJson$
|
|
550
|
+
async function readJson$3(response, t) {
|
|
553
551
|
const notMountedMessage = t("error.notMounted");
|
|
554
552
|
let body;
|
|
555
553
|
try {
|
|
@@ -601,7 +599,7 @@ var ConfigManagerApi = class {
|
|
|
601
599
|
}
|
|
602
600
|
/** 健康/版本检查(主页横幅:插件版本 / DSH 版本 / 平台) */
|
|
603
601
|
async status() {
|
|
604
|
-
return readJson$
|
|
602
|
+
return readJson$3(await fetch(CONFIG_MANAGER_API.status), this.t);
|
|
605
603
|
}
|
|
606
604
|
/** ExportPort.export:调用 Host 侧导出编排(core Exporter)。加密密码随请求体传输(仅内存)。
|
|
607
605
|
* 带 AbortController 超时:宿主若卡死,客户端得到明确错误而不是永远停在进度条。 */
|
|
@@ -613,7 +611,7 @@ var ConfigManagerApi = class {
|
|
|
613
611
|
const controller = new AbortController();
|
|
614
612
|
const timer = setTimeout(() => controller.abort(), EXPORT_TIMEOUT_MS);
|
|
615
613
|
try {
|
|
616
|
-
return await readJson$
|
|
614
|
+
return await readJson$3(await fetch(CONFIG_MANAGER_API.export, {
|
|
617
615
|
method: "POST",
|
|
618
616
|
headers: { "content-type": "application/json" },
|
|
619
617
|
body: JSON.stringify(body),
|
|
@@ -680,14 +678,14 @@ var ConfigManagerApi = class {
|
|
|
680
678
|
}
|
|
681
679
|
/** 上传用户选择的 ZIP 到 Host 受控临时目录,返回可引用的 zipPath(dsh-ssh 同款原始字节上传) */
|
|
682
680
|
async upload(file) {
|
|
683
|
-
return readJson$
|
|
681
|
+
return readJson$3(await fetch(CONFIG_MANAGER_API.upload + query({ name: file.name }), {
|
|
684
682
|
method: "POST",
|
|
685
683
|
body: file
|
|
686
684
|
}), this.t);
|
|
687
685
|
}
|
|
688
686
|
/** ImportPort.analyzeImport:零写入分析(校验/兼容性/差异/路径/秘密检测) */
|
|
689
687
|
async analyzeImport(zipPath) {
|
|
690
|
-
return readJson$
|
|
688
|
+
return readJson$3(await fetch(CONFIG_MANAGER_API.analyze, {
|
|
691
689
|
method: "POST",
|
|
692
690
|
headers: { "content-type": "application/json" },
|
|
693
691
|
body: JSON.stringify({ zipPath })
|
|
@@ -695,7 +693,7 @@ var ConfigManagerApi = class {
|
|
|
695
693
|
}
|
|
696
694
|
/** ImportPort.createImportPlan:用用户决策(冲突/路径映射/策略)生成最终计划(Dry Run 零写入) */
|
|
697
695
|
async createImportPlan(zipPath, decisions) {
|
|
698
|
-
return readJson$
|
|
696
|
+
return readJson$3(await fetch(CONFIG_MANAGER_API.plan, {
|
|
699
697
|
method: "POST",
|
|
700
698
|
headers: { "content-type": "application/json" },
|
|
701
699
|
body: JSON.stringify({
|
|
@@ -706,7 +704,7 @@ var ConfigManagerApi = class {
|
|
|
706
704
|
}
|
|
707
705
|
/** ImportPort.decryptArchive:解锁整体加密备份容器 → 明文 ZIP 路径 + 解密覆盖的凭据 ref 名 */
|
|
708
706
|
async decryptArchive(zipPath, password) {
|
|
709
|
-
return readJson$
|
|
707
|
+
return readJson$3(await fetch(CONFIG_MANAGER_API.decryptArchive, {
|
|
710
708
|
method: "POST",
|
|
711
709
|
headers: { "content-type": "application/json" },
|
|
712
710
|
body: JSON.stringify({
|
|
@@ -727,7 +725,7 @@ var ConfigManagerApi = class {
|
|
|
727
725
|
decryptPassword: opts.decryptPassword
|
|
728
726
|
}
|
|
729
727
|
};
|
|
730
|
-
return readJson$
|
|
728
|
+
return readJson$3(await fetch(CONFIG_MANAGER_API.execute, {
|
|
731
729
|
method: "POST",
|
|
732
730
|
headers: { "content-type": "application/json" },
|
|
733
731
|
body: JSON.stringify(payload)
|
|
@@ -735,19 +733,19 @@ var ConfigManagerApi = class {
|
|
|
735
733
|
}
|
|
736
734
|
/** m1:查询单个 run 的实时状态(执行中轮询 / 刷新恢复用;404 = 已过保留期或不存在) */
|
|
737
735
|
async progress(runId) {
|
|
738
|
-
return readJson$
|
|
736
|
+
return readJson$3(await fetch(CONFIG_MANAGER_API.progress + query({ runId })), this.t);
|
|
739
737
|
}
|
|
740
738
|
/** m1:列出当前活跃(running)的 run(刷新后重新订阅进行中任务的入口) */
|
|
741
739
|
async runs() {
|
|
742
|
-
return readJson$
|
|
740
|
+
return readJson$3(await fetch(CONFIG_MANAGER_API.runs), this.t);
|
|
743
741
|
}
|
|
744
742
|
/** 列出全部快照元信息(createdAt 倒序;含 status/条目数/宿主文件数/插件数) */
|
|
745
743
|
async snapshots() {
|
|
746
|
-
return (await readJson$
|
|
744
|
+
return (await readJson$3(await fetch(CONFIG_MANAGER_API.snapshots), this.t)).snapshots;
|
|
747
745
|
}
|
|
748
746
|
/** 快照恢复:dryRun=true 只取动作计划(零写入);false 执行并返回诚实报告 */
|
|
749
747
|
async restoreSnapshot(snapshotId, dryRun) {
|
|
750
|
-
return readJson$
|
|
748
|
+
return readJson$3(await fetch(CONFIG_MANAGER_API.restore, {
|
|
751
749
|
method: "POST",
|
|
752
750
|
headers: { "content-type": "application/json" },
|
|
753
751
|
body: JSON.stringify({
|
|
@@ -2221,6 +2219,7 @@ function defaultSyncState() {
|
|
|
2221
2219
|
}
|
|
2222
2220
|
function defaultMarketState() {
|
|
2223
2221
|
return {
|
|
2222
|
+
subView: "browse",
|
|
2224
2223
|
search: "",
|
|
2225
2224
|
category: "",
|
|
2226
2225
|
items: [],
|
|
@@ -2228,7 +2227,9 @@ function defaultMarketState() {
|
|
|
2228
2227
|
approvals: {},
|
|
2229
2228
|
importResult: null,
|
|
2230
2229
|
error: null,
|
|
2231
|
-
loadError: null
|
|
2230
|
+
loadError: null,
|
|
2231
|
+
myItems: null,
|
|
2232
|
+
myItemsError: null
|
|
2232
2233
|
};
|
|
2233
2234
|
}
|
|
2234
2235
|
function defaultSnapshotsState() {
|
|
@@ -2290,6 +2291,7 @@ function toSyncStoreSlice(s) {
|
|
|
2290
2291
|
/** 从市场视图状态提取切片(结构兼容:传入 MarketUiState 亦可)。 */
|
|
2291
2292
|
function toMarketStoreSlice(s) {
|
|
2292
2293
|
return {
|
|
2294
|
+
subView: s.subView,
|
|
2293
2295
|
search: s.search,
|
|
2294
2296
|
category: s.category,
|
|
2295
2297
|
items: s.items,
|
|
@@ -2297,7 +2299,9 @@ function toMarketStoreSlice(s) {
|
|
|
2297
2299
|
approvals: s.approvals,
|
|
2298
2300
|
importResult: s.importResult,
|
|
2299
2301
|
error: s.error,
|
|
2300
|
-
loadError: s.loadError
|
|
2302
|
+
loadError: s.loadError,
|
|
2303
|
+
myItems: s.myItems,
|
|
2304
|
+
myItemsError: s.myItemsError
|
|
2301
2305
|
};
|
|
2302
2306
|
}
|
|
2303
2307
|
/** 从快照面板状态提取切片(结构兼容:传入 PanelState 亦可)。 */
|
|
@@ -2906,111 +2910,111 @@ if (typeof document !== "undefined" && document.querySelector("style[data-tag=\"
|
|
|
2906
2910
|
document.head.appendChild(tag);
|
|
2907
2911
|
}
|
|
2908
2912
|
var config_manager_module_css_default = {
|
|
2909
|
-
"
|
|
2910
|
-
"
|
|
2913
|
+
"warnText": "_367UGW_warnText",
|
|
2914
|
+
"progressPercent": "_367UGW_progressPercent",
|
|
2911
2915
|
"planScroll": "_367UGW_planScroll",
|
|
2912
|
-
"
|
|
2913
|
-
"
|
|
2914
|
-
"
|
|
2915
|
-
"
|
|
2916
|
+
"pullScroll": "_367UGW_pullScroll",
|
|
2917
|
+
"pathIssueKind": "_367UGW_pathIssueKind",
|
|
2918
|
+
"reportScroll": "_367UGW_reportScroll",
|
|
2919
|
+
"primaryButton": "_367UGW_primaryButton",
|
|
2920
|
+
"progressMeta": "_367UGW_progressMeta",
|
|
2921
|
+
"errorLine": "_367UGW_errorLine",
|
|
2922
|
+
"progressBadgeSection": "_367UGW_progressBadgeSection",
|
|
2916
2923
|
"reportView": "_367UGW_reportView",
|
|
2917
|
-
"severityError": "_367UGW_severityError",
|
|
2918
2924
|
"progressBadge": "_367UGW_progressBadge",
|
|
2919
|
-
"
|
|
2920
|
-
"
|
|
2921
|
-
"
|
|
2925
|
+
"snapshotRow": "_367UGW_snapshotRow",
|
|
2926
|
+
"groupList": "_367UGW_groupList",
|
|
2927
|
+
"badgeInfo": "_367UGW_badgeInfo",
|
|
2928
|
+
"statRow": "_367UGW_statRow",
|
|
2929
|
+
"sectionSubtitle": "_367UGW_sectionSubtitle",
|
|
2930
|
+
"select": "_367UGW_select",
|
|
2931
|
+
"errorReason": "_367UGW_errorReason",
|
|
2932
|
+
"badgeError": "_367UGW_badgeError",
|
|
2933
|
+
"checkboxRow": "_367UGW_checkboxRow",
|
|
2934
|
+
"hiddenFile": "_367UGW_hiddenFile",
|
|
2935
|
+
"progressLabel": "_367UGW_progressLabel",
|
|
2936
|
+
"spinner": "_367UGW_spinner",
|
|
2937
|
+
"reportList": "_367UGW_reportList",
|
|
2938
|
+
"warnList": "_367UGW_warnList",
|
|
2939
|
+
"aboutLinkRow": "_367UGW_aboutLinkRow",
|
|
2922
2940
|
"banner": "_367UGW_banner",
|
|
2923
|
-
"
|
|
2924
|
-
"
|
|
2925
|
-
"
|
|
2926
|
-
"pathOld": "_367UGW_pathOld",
|
|
2927
|
-
"section": "_367UGW_section",
|
|
2928
|
-
"sectionTitle": "_367UGW_sectionTitle",
|
|
2929
|
-
"modeTab": "_367UGW_modeTab",
|
|
2930
|
-
"progressMeta": "_367UGW_progressMeta",
|
|
2931
|
-
"categoryDesc": "_367UGW_categoryDesc",
|
|
2941
|
+
"badgeOk": "_367UGW_badgeOk",
|
|
2942
|
+
"conflictItem": "_367UGW_conflictItem",
|
|
2943
|
+
"errorBanner": "_367UGW_errorBanner",
|
|
2932
2944
|
"conflictId": "_367UGW_conflictId",
|
|
2933
|
-
"
|
|
2934
|
-
"
|
|
2935
|
-
"pathRow": "_367UGW_pathRow",
|
|
2936
|
-
"diffScroll": "_367UGW_diffScroll",
|
|
2945
|
+
"secretsList": "_367UGW_secretsList",
|
|
2946
|
+
"sectionTitleBlock": "_367UGW_sectionTitleBlock",
|
|
2937
2947
|
"input": "_367UGW_input",
|
|
2938
|
-
"
|
|
2939
|
-
"
|
|
2940
|
-
"
|
|
2948
|
+
"spinnerLabel": "_367UGW_spinnerLabel",
|
|
2949
|
+
"progressBarDone": "_367UGW_progressBarDone",
|
|
2950
|
+
"categoryDesc": "_367UGW_categoryDesc",
|
|
2951
|
+
"sectionBody": "_367UGW_sectionBody",
|
|
2941
2952
|
"badge": "_367UGW_badge",
|
|
2942
|
-
"
|
|
2943
|
-
"
|
|
2944
|
-
"
|
|
2953
|
+
"groupItems": "_367UGW_groupItems",
|
|
2954
|
+
"progressTrack": "_367UGW_progressTrack",
|
|
2955
|
+
"conflictDetail": "_367UGW_conflictDetail",
|
|
2956
|
+
"errorActionLabel": "_367UGW_errorActionLabel",
|
|
2957
|
+
"kindTag": "_367UGW_kindTag",
|
|
2958
|
+
"progressBlock": "_367UGW_progressBlock",
|
|
2959
|
+
"badgeWarn": "_367UGW_badgeWarn",
|
|
2945
2960
|
"groupNote": "_367UGW_groupNote",
|
|
2946
|
-
"
|
|
2947
|
-
"
|
|
2948
|
-
"
|
|
2949
|
-
"
|
|
2950
|
-
"
|
|
2951
|
-
"
|
|
2952
|
-
"
|
|
2953
|
-
"
|
|
2961
|
+
"groupHeader": "_367UGW_groupHeader",
|
|
2962
|
+
"actionRow": "_367UGW_actionRow",
|
|
2963
|
+
"optionsCard": "_367UGW_optionsCard",
|
|
2964
|
+
"pathOld": "_367UGW_pathOld",
|
|
2965
|
+
"errorItem": "_367UGW_errorItem",
|
|
2966
|
+
"snapshotList": "_367UGW_snapshotList",
|
|
2967
|
+
"pathNew": "_367UGW_pathNew",
|
|
2968
|
+
"confirmScroll": "_367UGW_confirmScroll",
|
|
2969
|
+
"conflictList": "_367UGW_conflictList",
|
|
2970
|
+
"categoryName": "_367UGW_categoryName",
|
|
2971
|
+
"groupLabel": "_367UGW_groupLabel",
|
|
2972
|
+
"errorList": "_367UGW_errorList",
|
|
2954
2973
|
"card": "_367UGW_card",
|
|
2955
|
-
"
|
|
2956
|
-
"
|
|
2957
|
-
"
|
|
2958
|
-
"
|
|
2959
|
-
"select": "_367UGW_select",
|
|
2960
|
-
"secretsList": "_367UGW_secretsList",
|
|
2974
|
+
"sectionTitle": "_367UGW_sectionTitle",
|
|
2975
|
+
"optionsHeader": "_367UGW_optionsHeader",
|
|
2976
|
+
"aboutAuthor": "_367UGW_aboutAuthor",
|
|
2977
|
+
"progressBadgeCount": "_367UGW_progressBadgeCount",
|
|
2961
2978
|
"fieldLabel": "_367UGW_fieldLabel",
|
|
2979
|
+
"reportText": "_367UGW_reportText",
|
|
2980
|
+
"severityError": "_367UGW_severityError",
|
|
2962
2981
|
"formError": "_367UGW_formError",
|
|
2982
|
+
"pathMappingList": "_367UGW_pathMappingList",
|
|
2983
|
+
"empty": "_367UGW_empty",
|
|
2963
2984
|
"spinnerWrap": "_367UGW_spinnerWrap",
|
|
2964
|
-
"
|
|
2965
|
-
"
|
|
2966
|
-
"
|
|
2967
|
-
"snapshotRowHeader": "_367UGW_snapshotRowHeader",
|
|
2985
|
+
"field": "_367UGW_field",
|
|
2986
|
+
"secretFields": "_367UGW_secretFields",
|
|
2987
|
+
"rollbackBox": "_367UGW_rollbackBox",
|
|
2968
2988
|
"rowActions": "_367UGW_rowActions",
|
|
2969
|
-
"
|
|
2970
|
-
"
|
|
2971
|
-
"
|
|
2972
|
-
"progressPercent": "_367UGW_progressPercent",
|
|
2973
|
-
"conflictDetail": "_367UGW_conflictDetail",
|
|
2974
|
-
"progressLabel": "_367UGW_progressLabel",
|
|
2975
|
-
"badgeInfo": "_367UGW_badgeInfo",
|
|
2976
|
-
"statRow": "_367UGW_statRow",
|
|
2977
|
-
"modeTabs": "_367UGW_modeTabs",
|
|
2989
|
+
"dshCmIndeterminate": "_367UGW_dshCmIndeterminate",
|
|
2990
|
+
"ghostButton": "_367UGW_ghostButton",
|
|
2991
|
+
"diffScroll": "_367UGW_diffScroll",
|
|
2978
2992
|
"viewTab": "_367UGW_viewTab",
|
|
2979
|
-
"
|
|
2993
|
+
"modeTab": "_367UGW_modeTab",
|
|
2994
|
+
"radioLabel": "_367UGW_radioLabel",
|
|
2995
|
+
"groupCard": "_367UGW_groupCard",
|
|
2996
|
+
"reportFooter": "_367UGW_reportFooter",
|
|
2980
2997
|
"sectionHeader": "_367UGW_sectionHeader",
|
|
2981
|
-
"
|
|
2982
|
-
"optionsHeader": "_367UGW_optionsHeader",
|
|
2983
|
-
"groupList": "_367UGW_groupList",
|
|
2984
|
-
"conflictList": "_367UGW_conflictList",
|
|
2985
|
-
"groupHeader": "_367UGW_groupHeader",
|
|
2986
|
-
"errorLine": "_367UGW_errorLine",
|
|
2987
|
-
"conflictItem": "_367UGW_conflictItem",
|
|
2988
|
-
"snapshotRow": "_367UGW_snapshotRow",
|
|
2989
|
-
"errorBanner": "_367UGW_errorBanner",
|
|
2990
|
-
"hiddenFile": "_367UGW_hiddenFile",
|
|
2998
|
+
"pathValue": "_367UGW_pathValue",
|
|
2991
2999
|
"progressIndeterminate": "_367UGW_progressIndeterminate",
|
|
2992
|
-
"
|
|
2993
|
-
"confirmScroll": "_367UGW_confirmScroll",
|
|
2994
|
-
"actionRow": "_367UGW_actionRow",
|
|
2995
|
-
"progressBar": "_367UGW_progressBar",
|
|
3000
|
+
"dangerButton": "_367UGW_dangerButton",
|
|
2996
3001
|
"hint": "_367UGW_hint",
|
|
2997
|
-
"errorList": "_367UGW_errorList",
|
|
2998
|
-
"reportList": "_367UGW_reportList",
|
|
2999
|
-
"ghostButton": "_367UGW_ghostButton",
|
|
3000
|
-
"groupLabel": "_367UGW_groupLabel",
|
|
3001
|
-
"badgeWarn": "_367UGW_badgeWarn",
|
|
3002
|
-
"checkboxRow": "_367UGW_checkboxRow",
|
|
3003
|
-
"progressBadgeCount": "_367UGW_progressBadgeCount",
|
|
3004
|
-
"errorReason": "_367UGW_errorReason",
|
|
3005
|
-
"groupCard": "_367UGW_groupCard",
|
|
3006
|
-
"categoryItem": "_367UGW_categoryItem",
|
|
3007
3002
|
"modeHint": "_367UGW_modeHint",
|
|
3008
|
-
"
|
|
3009
|
-
"
|
|
3010
|
-
"
|
|
3011
|
-
"
|
|
3012
|
-
"
|
|
3013
|
-
"
|
|
3003
|
+
"categoryItem": "_367UGW_categoryItem",
|
|
3004
|
+
"errorFooter": "_367UGW_errorFooter",
|
|
3005
|
+
"pathRow": "_367UGW_pathRow",
|
|
3006
|
+
"progressDetail": "_367UGW_progressDetail",
|
|
3007
|
+
"progressBar": "_367UGW_progressBar",
|
|
3008
|
+
"snapshotRowHeader": "_367UGW_snapshotRowHeader",
|
|
3009
|
+
"viewBody": "_367UGW_viewBody",
|
|
3010
|
+
"modeTabs": "_367UGW_modeTabs",
|
|
3011
|
+
"errorTitle": "_367UGW_errorTitle",
|
|
3012
|
+
"errorAction": "_367UGW_errorAction",
|
|
3013
|
+
"conflictHead": "_367UGW_conflictHead",
|
|
3014
|
+
"section": "_367UGW_section",
|
|
3015
|
+
"viewTabs": "_367UGW_viewTabs",
|
|
3016
|
+
"conflictOptions": "_367UGW_conflictOptions",
|
|
3017
|
+
"dshCmSpin": "_367UGW_dshCmSpin"
|
|
3014
3018
|
};
|
|
3015
3019
|
//#endregion
|
|
3016
3020
|
//#region src/client/common/ui.tsx
|
|
@@ -5030,7 +5034,7 @@ const SYNC_API = {
|
|
|
5030
5034
|
/** 同步请求超时(ms):与 Host 半 ROUTE_TIMEOUT_MS 对齐(git 网络操作可能较慢) */
|
|
5031
5035
|
const SYNC_TIMEOUT_MS = 300 * 1e3;
|
|
5032
5036
|
/** 解析 JSON 响应;非 2xx 时抛出带路由 error 消息的 ConfigManagerApiError(与 api.ts 同款) */
|
|
5033
|
-
async function readJson$
|
|
5037
|
+
async function readJson$2(response, t) {
|
|
5034
5038
|
const notMountedMessage = t("error.notMounted");
|
|
5035
5039
|
let body;
|
|
5036
5040
|
try {
|
|
@@ -5043,11 +5047,11 @@ async function readJson$1(response, t) {
|
|
|
5043
5047
|
return body;
|
|
5044
5048
|
}
|
|
5045
5049
|
/** POST JSON 请求(带超时:宿主卡死时 UI 拿到明确错误而不是永远转圈) */
|
|
5046
|
-
async function postJson$
|
|
5050
|
+
async function postJson$2(path, body, t) {
|
|
5047
5051
|
const controller = new AbortController();
|
|
5048
5052
|
const timer = setTimeout(() => controller.abort(), SYNC_TIMEOUT_MS);
|
|
5049
5053
|
try {
|
|
5050
|
-
return await readJson$
|
|
5054
|
+
return await readJson$2(await fetch(path, {
|
|
5051
5055
|
method: "POST",
|
|
5052
5056
|
headers: { "content-type": "application/json" },
|
|
5053
5057
|
body: JSON.stringify(body),
|
|
@@ -5068,51 +5072,51 @@ var SyncApi = class {
|
|
|
5068
5072
|
}
|
|
5069
5073
|
/** 读取同步状态(配置 / 凭据 / 上次同步时间 / 分区数) */
|
|
5070
5074
|
async status() {
|
|
5071
|
-
return readJson$
|
|
5075
|
+
return readJson$2(await fetch(SYNC_API.status), this.t);
|
|
5072
5076
|
}
|
|
5073
5077
|
/** 推送:导出 portable 分区 → 提交到私有 Git 仓库 → 更新 sync-state */
|
|
5074
5078
|
async push(payload) {
|
|
5075
|
-
return postJson$
|
|
5079
|
+
return postJson$2(SYNC_API.push, payload, this.t);
|
|
5076
5080
|
}
|
|
5077
5081
|
/** 拉取差异预览:拉取远端最新快照 → 只读分析(绝不执行导入) */
|
|
5078
5082
|
async pull(payload) {
|
|
5079
|
-
return postJson$
|
|
5083
|
+
return postJson$2(SYNC_API.pull, payload, this.t);
|
|
5080
5084
|
}
|
|
5081
5085
|
/** GitHub OAuth device flow:发起登录,返回一次性用户码 + 授权页 URL + flowId */
|
|
5082
5086
|
async githubStart() {
|
|
5083
|
-
return postJson$
|
|
5087
|
+
return postJson$2(SYNC_API.githubStart, {}, this.t);
|
|
5084
5088
|
}
|
|
5085
5089
|
/** GitHub OAuth device flow:凭 flowId 轮询授权结果(成功时 token 已由 Host 写入 credentials) */
|
|
5086
5090
|
async githubPoll(flowId) {
|
|
5087
|
-
return postJson$
|
|
5091
|
+
return postJson$2(SYNC_API.githubPoll, { flowId }, this.t);
|
|
5088
5092
|
}
|
|
5089
5093
|
/** GitHub OAuth device flow:取消(丢弃宿主侧登记,零副作用) */
|
|
5090
5094
|
async githubCancel(flowId) {
|
|
5091
|
-
return postJson$
|
|
5095
|
+
return postJson$2(SYNC_API.githubCancel, { flowId }, this.t);
|
|
5092
5096
|
}
|
|
5093
5097
|
/** 同步历史:列出本地祖先快照 + 自动同步执行记录(按 createdAt 倒序合并)。 */
|
|
5094
5098
|
async history() {
|
|
5095
|
-
return readJson$
|
|
5099
|
+
return readJson$2(await fetch(SYNC_API.history), this.t);
|
|
5096
5100
|
}
|
|
5097
5101
|
/** 远端历史快照列表(供「选择历史快照」下拉)。 */
|
|
5098
5102
|
async snapshotsList(payload) {
|
|
5099
|
-
return postJson$
|
|
5103
|
+
return postJson$2(SYNC_API.snapshotsList, payload, this.t);
|
|
5100
5104
|
}
|
|
5101
5105
|
/** 一键同步第一步:拉取 → 差异确认会话(items 逐项确认,暂不导入)。 */
|
|
5102
5106
|
async sync(payload) {
|
|
5103
|
-
return postJson$
|
|
5107
|
+
return postJson$2(SYNC_API.sync, payload, this.t);
|
|
5104
5108
|
}
|
|
5105
5109
|
/** 一键同步第二步:按用户对差异项的逐项决策执行导入。 */
|
|
5106
5110
|
async applyItems(payload) {
|
|
5107
|
-
return postJson$
|
|
5111
|
+
return postJson$2(SYNC_API.applyItems, payload, this.t);
|
|
5108
5112
|
}
|
|
5109
5113
|
/** 取消/清理差异确认会话(丢弃临时 ZIP,零副作用)。 */
|
|
5110
5114
|
async cancel(syncSessionId) {
|
|
5111
|
-
return postJson$
|
|
5115
|
+
return postJson$2(SYNC_API.cancel, { syncSessionId }, this.t);
|
|
5112
5116
|
}
|
|
5113
5117
|
/** 自动同步状态(GET /sync/autosync 返回全部通道的 { git, webdav },各自独立)。 */
|
|
5114
5118
|
async autosyncStatusAll() {
|
|
5115
|
-
return readJson$
|
|
5119
|
+
return readJson$2(await fetch(SYNC_API.autosync), this.t);
|
|
5116
5120
|
}
|
|
5117
5121
|
/** 自动同步状态(指定通道;从全部通道状态中取)。 */
|
|
5118
5122
|
async autosyncStatus(transport = "git") {
|
|
@@ -5120,26 +5124,26 @@ var SyncApi = class {
|
|
|
5120
5124
|
}
|
|
5121
5125
|
/** 自动同步配置更新(POST /sync/autosync;payload.transport 指定目标通道)。 */
|
|
5122
5126
|
async autosyncUpdate(payload) {
|
|
5123
|
-
return postJson$
|
|
5127
|
+
return postJson$2(SYNC_API.autosync, payload, this.t);
|
|
5124
5128
|
}
|
|
5125
5129
|
/** 保存同步分区选择(POST /sync/selection):模式 + 勾选分区持久化到 Host。
|
|
5126
5130
|
* 自动同步调度器与手动 push 共用此配置(刷新/重启后仍然生效)。 */
|
|
5127
5131
|
async saveSelection(payload) {
|
|
5128
|
-
return postJson$
|
|
5132
|
+
return postJson$2(SYNC_API.selection, payload, this.t);
|
|
5129
5133
|
}
|
|
5130
5134
|
/** 保存同步通道配置(POST /sync/config):url/username/password(git: repoUrl/token)持久化。
|
|
5131
5135
|
* password/token 经 Host 写入 DSH credentials(值永不回传);返回凭据布尔供 UI 刷新徽章。 */
|
|
5132
5136
|
async saveConfig(payload) {
|
|
5133
|
-
return postJson$
|
|
5137
|
+
return postJson$2(SYNC_API.config, payload, this.t);
|
|
5134
5138
|
}
|
|
5135
5139
|
/** 保存插件 UI 偏好(POST /sync/ui-prefs):当前为上次选择的同步通道(ui-prefs.json,
|
|
5136
5140
|
* 随 self 分区进导出备份)。纯偏好无 secret;失败由调用方静默降级(localStorage 兜底)。 */
|
|
5137
5141
|
async saveUiPrefs(payload) {
|
|
5138
|
-
return postJson$
|
|
5142
|
+
return postJson$2(SYNC_API.uiPrefs, payload, this.t);
|
|
5139
5143
|
}
|
|
5140
5144
|
/** 一键回滚:按 restoreId 调用 backup→rollback */
|
|
5141
5145
|
async rollback(payload) {
|
|
5142
|
-
return postJson$
|
|
5146
|
+
return postJson$2(SYNC_API.rollback, payload, this.t);
|
|
5143
5147
|
}
|
|
5144
5148
|
};
|
|
5145
5149
|
//#endregion
|
|
@@ -7188,904 +7192,1318 @@ function isOfficialMarket(url) {
|
|
|
7188
7192
|
return url.trim() === DEFAULT_MARKET_URL;
|
|
7189
7193
|
}
|
|
7190
7194
|
//#endregion
|
|
7191
|
-
//#region src/market/
|
|
7195
|
+
//#region src/market/github-repos.ts
|
|
7192
7196
|
/**
|
|
7193
|
-
*
|
|
7194
|
-
*
|
|
7197
|
+
* 官方收录目标仓库(产品决策 2026-08-20,docs/design/2026-08-20-my-configs-design.md §2.4):
|
|
7198
|
+
* 收录 / PR 相关目标**固定**为 xiajiajun516/dsh-config-market,写死常量、界面不提供任何修改入口。
|
|
7199
|
+
* 与 src/market/builtin.ts 的内置市场为同一仓库(内置市场 URL 可经 env 覆盖仅用于浏览,
|
|
7200
|
+
* 收录目标不受 env 影响,恒为官方仓库)。
|
|
7195
7201
|
*/
|
|
7196
|
-
const
|
|
7202
|
+
const MARKET_UPSTREAM_OWNER = "xiajiajun516";
|
|
7203
|
+
/** 官方收录目标仓库名(见 MARKET_UPSTREAM_OWNER) */
|
|
7204
|
+
const MARKET_UPSTREAM_REPO = "dsh-config-market";
|
|
7197
7205
|
//#endregion
|
|
7198
|
-
//#region src/
|
|
7206
|
+
//#region src/client/market/my-configs-view.ts
|
|
7199
7207
|
/**
|
|
7200
|
-
*
|
|
7208
|
+
* 「我的配置」区块的客户端纯渲染装配层(m-my-configs-view,node 可测)。
|
|
7201
7209
|
*
|
|
7202
|
-
* 设计纪律(docs/design/2026-08-
|
|
7203
|
-
* - 全部无副作用纯函数,React 壳(
|
|
7204
|
-
* -
|
|
7205
|
-
* -
|
|
7210
|
+
* 设计纪律(docs/design/2026-08-20-my-configs-design.md §4.5 / §4.6):
|
|
7211
|
+
* - 全部无副作用纯函数,React 壳(MyConfigsView)只装配,不承载业务逻辑;
|
|
7212
|
+
* - 输入数据一律由调用方(组件 / api 层)提供 —— 本文件不发起任何请求,不持有凭据;
|
|
7213
|
+
* - 录入最小化(§1.2):表单仅 name(预填 zip 文件名,可改)/ description(可选)/
|
|
7214
|
+
* categories(可选);id / author / version / updatedAt 均为系统自动生成,
|
|
7215
|
+
* 界面以「系统自动」徽章展示(MY_CONFIG_AUTO_FIELDS);
|
|
7216
|
+
* - 收录状态三态(§4.5):未收录(本地独有)| PR 待审核(带 PR 链接)| 已收录
|
|
7217
|
+
* (官方 dsh-config-market/index.json 含该 id);已收录 > PR 待审核 > 未收录
|
|
7218
|
+
* (官方已收录时即使存在 open PR 也以收录为准——PR 已合并后不应再显示待审核);
|
|
7219
|
+
* - open PR 匹配按固定分支 `dsh-market-sync/<itemId>`(§2.4 自动 PR 分支约定);
|
|
7220
|
+
* - 文案走 src/ui/i18n.ts 的 UiT('myConfigs.*' 键,zh 源 / en 镜像),不硬编码中文。
|
|
7206
7221
|
*
|
|
7207
|
-
*
|
|
7208
|
-
*
|
|
7209
|
-
* - repoUrl 可选但若填必须为 http(s) 且无 userinfo(轻量等价实现 sync validateRepoUrl 的
|
|
7210
|
-
* 语义,不 import node 模块、浏览器/node 双端可跑;凭据永不拼入 URL);
|
|
7211
|
-
* - 本层只生成 git 命令模板与 index.json 收录片段**文本**,不做任何 git 写操作、
|
|
7212
|
-
* 不持有凭据(与 About 页「Star 由用户在 GitHub 完成」同款产品纪律);
|
|
7213
|
-
* - 内容校验(zip 加固解包 / containsSecrets / SHA-256)在 Host 侧 prepare 端点,本层不管。
|
|
7222
|
+
* 安全硬约束:本层只做展示推导,不接触 token / 凭据;PR URL 由调用方提供,
|
|
7223
|
+
* 渲染前仍过 redact() 兜底(与全站一致)。
|
|
7214
7224
|
*/
|
|
7215
|
-
/**
|
|
7216
|
-
|
|
7217
|
-
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
"
|
|
7221
|
-
"
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
+
/**
|
|
7226
|
+
* 登录状态推导(纯函数):loading 优先;authError 非空(401 等)→ token 失效;
|
|
7227
|
+
* 否则按 status.loggedIn 区分已登录 / 未登录。输入由调用方提供。
|
|
7228
|
+
*/
|
|
7229
|
+
function deriveLoginState(input) {
|
|
7230
|
+
if (input.loading) return { kind: "loading" };
|
|
7231
|
+
if (input.authFailed) return { kind: "token-invalid" };
|
|
7232
|
+
const s = input.status;
|
|
7233
|
+
if (s !== null && s.loggedIn) return {
|
|
7234
|
+
kind: "logged-in",
|
|
7235
|
+
login: s.login ?? "",
|
|
7236
|
+
repoUrl: s.repoUrl ?? "",
|
|
7237
|
+
repoExists: s.repoExists ?? false
|
|
7238
|
+
};
|
|
7239
|
+
return { kind: "logged-out" };
|
|
7240
|
+
}
|
|
7241
|
+
/** 空表单初值(React 壳 useState 初始 + 测试用)。 */
|
|
7242
|
+
const EMPTY_MY_CONFIG_FORM = {
|
|
7243
|
+
name: "",
|
|
7244
|
+
description: "",
|
|
7245
|
+
categories: ""
|
|
7246
|
+
};
|
|
7247
|
+
/** 表单校验:名称必填(trim 后非空)。描述/类别可选。 */
|
|
7248
|
+
function validateMyConfigForm(form, t = zhUiT) {
|
|
7249
|
+
return { name: form.name.trim() === "" ? t("myConfigs.form.errorName") : null };
|
|
7250
|
+
}
|
|
7251
|
+
/** 表单是否全部合法(上传按钮可用性)。 */
|
|
7252
|
+
function myConfigFormValid(errors) {
|
|
7253
|
+
return errors.name === null;
|
|
7254
|
+
}
|
|
7255
|
+
/** 逗号分隔类别文本 → 去空白数组(空结果 = 无类别;与 PublishView 相同语义)。 */
|
|
7256
|
+
function parseCategories(csv) {
|
|
7257
|
+
return csv.split(",").map((c) => c.trim()).filter((c) => c !== "");
|
|
7258
|
+
}
|
|
7259
|
+
/** 系统自动字段徽章列表(id/作者/版本/更新时间 → 「系统自动」徽章)。 */
|
|
7260
|
+
function autoFieldBadges(t = zhUiT) {
|
|
7261
|
+
const autoText = t("myConfigs.autoField");
|
|
7225
7262
|
return [
|
|
7226
7263
|
{
|
|
7227
|
-
|
|
7228
|
-
|
|
7229
|
-
},
|
|
7230
|
-
{
|
|
7231
|
-
id: "validate",
|
|
7232
|
-
title: t("marketPublish.stepValidate")
|
|
7264
|
+
field: "id",
|
|
7265
|
+
label: t("myConfigs.field.id")
|
|
7233
7266
|
},
|
|
7234
7267
|
{
|
|
7235
|
-
|
|
7236
|
-
|
|
7268
|
+
field: "author",
|
|
7269
|
+
label: t("myConfigs.field.author")
|
|
7237
7270
|
},
|
|
7238
7271
|
{
|
|
7239
|
-
|
|
7240
|
-
|
|
7272
|
+
field: "version",
|
|
7273
|
+
label: t("myConfigs.field.version")
|
|
7241
7274
|
},
|
|
7242
7275
|
{
|
|
7243
|
-
|
|
7244
|
-
|
|
7276
|
+
field: "updatedAt",
|
|
7277
|
+
label: t("myConfigs.field.updatedAt")
|
|
7245
7278
|
}
|
|
7246
|
-
]
|
|
7279
|
+
].map(({ field, label }) => ({
|
|
7280
|
+
field,
|
|
7281
|
+
label,
|
|
7282
|
+
autoText
|
|
7283
|
+
}));
|
|
7247
7284
|
}
|
|
7248
|
-
/** 空表单初值(React 壳 useState 初始 + 测试用)。 */
|
|
7249
|
-
const EMPTY_PUBLISH_FORM = {
|
|
7250
|
-
itemId: "",
|
|
7251
|
-
name: "",
|
|
7252
|
-
version: "",
|
|
7253
|
-
description: "",
|
|
7254
|
-
author: "",
|
|
7255
|
-
categories: "",
|
|
7256
|
-
repoUrl: ""
|
|
7257
|
-
};
|
|
7258
7285
|
/**
|
|
7259
|
-
*
|
|
7260
|
-
*
|
|
7261
|
-
*
|
|
7262
|
-
*
|
|
7263
|
-
* - authority 段不含 userinfo(`user[:password]@host`),凭据必须走 DSH credentials。
|
|
7264
|
-
* 语义与 sync 的 validateRepoUrl 对齐(拒绝 userinfo / 空白),另按市场设计补强「非 http(s) 拒绝」。
|
|
7286
|
+
* Host 侧收录状态桥(§4.5 在 Host 判定)→ 客户端判别联合 ItemStatus:
|
|
7287
|
+
* my-repo.ts 的 MyItemStatus = 'not-listed' | 'pr-pending' | 'listed',
|
|
7288
|
+
* prUrl 在 pr-pending 时由 Host 提供;prNumber 可选(Host 未提供时缺省)。
|
|
7289
|
+
* 纯映射,无副作用;MyConfigsView 消费 /me/items 条目时经此桥转徽章模型。
|
|
7265
7290
|
*/
|
|
7266
|
-
function
|
|
7267
|
-
|
|
7268
|
-
|
|
7269
|
-
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7291
|
+
function itemStatusFromHost(entry) {
|
|
7292
|
+
switch (entry.status) {
|
|
7293
|
+
case "listed": return { kind: "listed" };
|
|
7294
|
+
case "pr-pending": return {
|
|
7295
|
+
kind: "pending-pr",
|
|
7296
|
+
prUrl: entry.prUrl ?? "",
|
|
7297
|
+
prNumber: entry.prNumber
|
|
7298
|
+
};
|
|
7299
|
+
default: return { kind: "not-listed" };
|
|
7300
|
+
}
|
|
7301
|
+
}
|
|
7302
|
+
/** 收录状态 → 徽章模板(ok=已收录 / warn=PR 待审核 / info=未收录)。 */
|
|
7303
|
+
function itemStatusBadge(status, t = zhUiT) {
|
|
7304
|
+
switch (status.kind) {
|
|
7305
|
+
case "listed": return {
|
|
7306
|
+
kind: "ok",
|
|
7307
|
+
text: t("myConfigs.status.listed")
|
|
7308
|
+
};
|
|
7309
|
+
case "pending-pr": return {
|
|
7310
|
+
kind: "warn",
|
|
7311
|
+
text: t("myConfigs.status.pendingPr"),
|
|
7312
|
+
prUrl: status.prUrl,
|
|
7313
|
+
prNumber: status.prNumber
|
|
7314
|
+
};
|
|
7315
|
+
case "not-listed": return {
|
|
7316
|
+
kind: "info",
|
|
7317
|
+
text: t("myConfigs.status.notListed")
|
|
7318
|
+
};
|
|
7319
|
+
}
|
|
7273
7320
|
}
|
|
7274
|
-
/**
|
|
7275
|
-
function
|
|
7276
|
-
|
|
7277
|
-
|
|
7278
|
-
name:
|
|
7279
|
-
|
|
7321
|
+
/** 条目投影:缺失字段给默认值(name 缺省回退 id;其余空串/空数组)。 */
|
|
7322
|
+
function toMyItemView(item, status, t = zhUiT) {
|
|
7323
|
+
return {
|
|
7324
|
+
id: item.id,
|
|
7325
|
+
name: item.name !== void 0 && item.name.trim() !== "" ? item.name : item.id,
|
|
7326
|
+
version: item.version ?? "",
|
|
7327
|
+
updatedAt: item.updatedAt ?? "",
|
|
7328
|
+
categories: item.categories ?? [],
|
|
7329
|
+
author: item.author ?? "",
|
|
7330
|
+
status,
|
|
7331
|
+
badge: itemStatusBadge(status, t)
|
|
7280
7332
|
};
|
|
7281
|
-
if (!SAFE_ITEM_ID_RE.test(form.itemId)) errors.itemId = t("marketPublish.errorItemId");
|
|
7282
|
-
if (form.name.trim() === "") errors.name = t("marketPublish.errorName");
|
|
7283
|
-
const repoErr = validatePublishRepoUrl(form.repoUrl, t);
|
|
7284
|
-
if (repoErr !== null) errors.repoUrl = repoErr;
|
|
7285
|
-
return errors;
|
|
7286
7333
|
}
|
|
7287
|
-
/**
|
|
7288
|
-
function
|
|
7289
|
-
|
|
7334
|
+
/** 列表摘要统计(按 status 分类计数)。 */
|
|
7335
|
+
function summarizeMyItems(views) {
|
|
7336
|
+
let listed = 0;
|
|
7337
|
+
let pendingPr = 0;
|
|
7338
|
+
let notListed = 0;
|
|
7339
|
+
for (const v of views) if (v.status.kind === "listed") listed += 1;
|
|
7340
|
+
else if (v.status.kind === "pending-pr") pendingPr += 1;
|
|
7341
|
+
else notListed += 1;
|
|
7342
|
+
return {
|
|
7343
|
+
total: views.length,
|
|
7344
|
+
listed,
|
|
7345
|
+
pendingPr,
|
|
7346
|
+
notListed
|
|
7347
|
+
};
|
|
7290
7348
|
}
|
|
7291
|
-
|
|
7292
|
-
|
|
7293
|
-
|
|
7294
|
-
|
|
7295
|
-
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
*/
|
|
7299
|
-
function buildGitPushCommands(input) {
|
|
7300
|
-
const { repoUrl, itemId, dir } = input;
|
|
7301
|
-
return [
|
|
7302
|
-
`git clone ${repoUrl} ${dir}`,
|
|
7303
|
-
`cd ${dir}`,
|
|
7304
|
-
`mkdir -p items/${itemId}`,
|
|
7305
|
-
`cp -r ../dist/items/${itemId}/. items/${itemId}/`,
|
|
7306
|
-
`git add items/${itemId}`,
|
|
7307
|
-
`git commit -m "publish ${itemId}"`,
|
|
7308
|
-
`git push origin HEAD`
|
|
7309
|
-
];
|
|
7349
|
+
//#endregion
|
|
7350
|
+
//#region src/market/view.ts
|
|
7351
|
+
/** 市场状态行文案(status 区块顶部一行)。count<=0 → 未添加提示 */
|
|
7352
|
+
function marketStatusText(config, t) {
|
|
7353
|
+
const count = Array.isArray(config) ? config.length : config.count;
|
|
7354
|
+
if (count <= 0) return t("market.statusUnconfigured");
|
|
7355
|
+
return t("market.statusConfigured", { count });
|
|
7310
7356
|
}
|
|
7311
|
-
/**
|
|
7312
|
-
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
|
|
7357
|
+
/** 列表摘要:总条目数 + 缓存徽章计数(供 UI 顶部小结)。无副作用。 */
|
|
7358
|
+
function marketListSummary(items, _t) {
|
|
7359
|
+
const fresh = items.filter((i) => i.cacheState === "fresh").length;
|
|
7360
|
+
const cached = items.filter((i) => i.cacheState === "cached").length;
|
|
7361
|
+
const none = items.filter((i) => i.cacheState === "none").length;
|
|
7362
|
+
return {
|
|
7363
|
+
total: items.length,
|
|
7364
|
+
fresh,
|
|
7365
|
+
cached,
|
|
7366
|
+
none
|
|
7320
7367
|
};
|
|
7321
|
-
if (item.description !== void 0 && item.description.trim() !== "") entry.description = item.description.trim();
|
|
7322
|
-
if (item.author !== void 0 && item.author.trim() !== "") entry.author = item.author.trim();
|
|
7323
|
-
if (item.version !== void 0 && item.version.trim() !== "") entry.version = item.version.trim();
|
|
7324
|
-
if (item.updatedAt !== void 0 && item.updatedAt.trim() !== "") entry.updatedAt = item.updatedAt.trim();
|
|
7325
|
-
if (item.categories !== void 0 && item.categories.length > 0) entry.categories = item.categories;
|
|
7326
|
-
entry.repo = item.repo;
|
|
7327
|
-
return JSON.stringify(entry, null, 2);
|
|
7328
7368
|
}
|
|
7329
|
-
/**
|
|
7330
|
-
|
|
7331
|
-
|
|
7332
|
-
|
|
7333
|
-
|
|
7334
|
-
|
|
7335
|
-
|
|
7336
|
-
*/
|
|
7337
|
-
function canProceed(state) {
|
|
7338
|
-
switch (state.step) {
|
|
7339
|
-
case "select": return state.zipSelected;
|
|
7340
|
-
case "validate": return state.validated;
|
|
7341
|
-
case "prepare": return state.prepared;
|
|
7342
|
-
case "push": return true;
|
|
7343
|
-
case "submit": return false;
|
|
7344
|
-
}
|
|
7369
|
+
/** 条目校验徽章:status 文案 + sections 清单文案。返回 { statusText, sectionsText, valid } */
|
|
7370
|
+
function computeItemBadge(detail, t) {
|
|
7371
|
+
return {
|
|
7372
|
+
statusText: detail.status === "valid" ? t("market.detail.statusValid") : t("market.detail.statusInvalid"),
|
|
7373
|
+
sectionsText: detail.sections.length > 0 ? t("market.detail.sections", { sections: detail.sections.join(", ") }) : t("market.detail.sectionsEmpty"),
|
|
7374
|
+
valid: detail.status === "valid"
|
|
7375
|
+
};
|
|
7345
7376
|
}
|
|
7346
|
-
//#endregion
|
|
7347
|
-
//#region src/client/market/PublishView.tsx
|
|
7348
7377
|
/**
|
|
7349
|
-
*
|
|
7350
|
-
*
|
|
7351
|
-
* 5 步流程(全部渲染/校验模型来自 src/ui/market-publish.ts 纯函数,node 已测;本组件只装配):
|
|
7352
|
-
* ①选择配置包 —— 复用导出 zip(本地文件选择 → upload 受控临时区,零新增导入逻辑);
|
|
7353
|
-
* ②本地校验 —— analyzeImport dry-run(零写入):内容合法且不含密钥(secretCount===0);
|
|
7354
|
-
* ③生成条目包 —— 表单(name/description/author/categories/id/repoUrl 可选)+ POST /market/prepare
|
|
7355
|
-
* → 展示 manifest 文本 / SHA-256 / sections(可复制);
|
|
7356
|
-
* ④推送作者仓库 —— t4 控制器生成的 git 命令模板(可复制);插件不做任何 git 写操作、不持有凭据;
|
|
7357
|
-
* ⑤提交收录申请 —— index.json 追加片段(可复制)+ fork 官方仓库提 PR 指引。
|
|
7358
|
-
*
|
|
7359
|
-
* 状态策略:组件内自持(useState,不进 sessionStorage —— 发布为一次性低频率流程,同 MarketPanel 的低频面板策略)。
|
|
7360
|
-
* 安全:市场通道永不携带秘密(步骤 2 拒绝含密钥 zip,prepare 亦双保险拒绝 containsSecrets);
|
|
7361
|
-
* 展示文本渲染前过 redact() 兜底;repoUrl 已由 validatePublishRepoUrl 拒绝 userinfo,凭据绝不拼入命令/片段。
|
|
7362
|
-
*/
|
|
7363
|
-
/**
|
|
7364
|
-
* 发布向导(React 壳,只装配):
|
|
7365
|
-
* - 步骤门控用 t4 控制器 canProceed(zipSelected / validated / prepared);
|
|
7366
|
-
* - 表单校验用 validatePublishForm / publishFormValid(行内 formError);
|
|
7367
|
-
* - git 命令 / index 片段文本来自 buildGitPushCommands / buildIndexEntrySnippet。
|
|
7378
|
+
* 恒生成供应链警示行(硬不变式:任何市场下载条目确认导入前都可见)。
|
|
7379
|
+
* 传入 L2 manifest + 市场 URL + 下载时间;不依赖任何“来源可信”判定。
|
|
7368
7380
|
*/
|
|
7369
|
-
function
|
|
7370
|
-
const
|
|
7371
|
-
|
|
7372
|
-
|
|
7373
|
-
|
|
7374
|
-
|
|
7375
|
-
|
|
7376
|
-
|
|
7377
|
-
|
|
7378
|
-
|
|
7379
|
-
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
|
|
7383
|
-
|
|
7384
|
-
|
|
7385
|
-
|
|
7386
|
-
|
|
7387
|
-
|
|
7388
|
-
|
|
7389
|
-
|
|
7390
|
-
|
|
7391
|
-
|
|
7392
|
-
|
|
7393
|
-
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
|
|
7397
|
-
|
|
7398
|
-
|
|
7381
|
+
function marketItemWarnings(manifest, url, downloadedAt, t) {
|
|
7382
|
+
const warnings = [];
|
|
7383
|
+
warnings.push(t("market.supplyUnofficial"));
|
|
7384
|
+
if (url !== "") warnings.push(t("market.supplySource", { url }));
|
|
7385
|
+
if (downloadedAt !== "") warnings.push(t("market.supplyDownloadedAt", { time: downloadedAt }));
|
|
7386
|
+
if (manifest?.author) warnings.push(t("market.supplyAuthor", { author: manifest.author }));
|
|
7387
|
+
if (manifest?.provenance?.source) warnings.push(t("market.supplyProvenanceSource", { source: manifest.provenance.source }));
|
|
7388
|
+
if (manifest?.provenance?.note) warnings.push(t("market.supplyProvenanceNote", { note: manifest.provenance.note }));
|
|
7389
|
+
return warnings;
|
|
7390
|
+
}
|
|
7391
|
+
//#endregion
|
|
7392
|
+
//#region src/client/market/market-view.ts
|
|
7393
|
+
/**
|
|
7394
|
+
* 配置市场区块的客户端渲染装配层(m-market-ui,node 可测)。
|
|
7395
|
+
*
|
|
7396
|
+
* 设计纪律(docs/design/marketplace.md §7.2 / §9 contract 表):
|
|
7397
|
+
* - **共享渲染模型唯一权威 = Host 侧 `src/market/view.ts`**(marketStatusText /
|
|
7398
|
+
* marketListSummary / computeItemBadge / marketItemWarnings / needsReview / toMarketListItem)。
|
|
7399
|
+
* 本文件原样 **re-export** 这些函数(单一来源,消重,避免与后端漂移);
|
|
7400
|
+
* - 本文件只保留**客户端专属**的 UI 装配函数(搜索/类别过滤、详情聚合、时间格式化、
|
|
7401
|
+
* 供应链警示的 warn/info 着色行、条目来源徽章)——这些不属共享模型,属前端薄层。
|
|
7402
|
+
*
|
|
7403
|
+
* 安全硬约束(§1 / §7.2):供应链警示恒生成、needsReview 恒 true(re-export 自后端权威)。
|
|
7404
|
+
*/
|
|
7405
|
+
/**
|
|
7406
|
+
* 条目来源徽章 kind(阶段 1:条目级来源仓库,docs/design/2026-08-19-market-publish-design.md §3.3):
|
|
7407
|
+
* - `'ok'`(官方):`item.repo` 缺省(条目与市场仓库同仓)或 `item.repo` 为官方默认地址;
|
|
7408
|
+
* - `'warn'`(第三方):`item.repo` 存在且非官方默认地址(条目由作者自托管仓库发布)。
|
|
7409
|
+
* 判定基准复用 `builtin.ts` 的 `isOfficialMarket`(固定官方地址比较):env 覆盖为预览仓库时,
|
|
7410
|
+
* 无 repo 条目随市场头部一并显示第三方徽章,语义自洽。纯函数、node 可测;MarketPanel 只装配。
|
|
7411
|
+
*/
|
|
7412
|
+
function sourceBadgeKind(item, builtinUrl) {
|
|
7413
|
+
return isOfficialMarket(item.repo ?? builtinUrl) ? "ok" : "warn";
|
|
7414
|
+
}
|
|
7415
|
+
/** 类别过滤:从条目收集全部出现过的类别(用于「全部类别」下拉)。 */
|
|
7416
|
+
function collectCategories(items) {
|
|
7417
|
+
const set = /* @__PURE__ */ new Set();
|
|
7418
|
+
for (const it of items) for (const c of it.categories ?? []) set.add(c);
|
|
7419
|
+
return [...set];
|
|
7420
|
+
}
|
|
7421
|
+
/**
|
|
7422
|
+
* 搜索 + 类别过滤(纯函数,客户端专属)。
|
|
7423
|
+
* - query:对 name / author / description 做大小写不敏感子串匹配(空白 query 不过滤);
|
|
7424
|
+
* - category:空串表示不限类别;否则要求 categories 含该值。
|
|
7425
|
+
* 返回筛选后的条目(保持原始顺序)。
|
|
7426
|
+
*/
|
|
7427
|
+
function filterMarketItems(items, query, category) {
|
|
7428
|
+
const q = query.trim().toLowerCase();
|
|
7429
|
+
return items.filter((it) => {
|
|
7430
|
+
if (category !== "" && !(it.categories ?? []).includes(category)) return false;
|
|
7431
|
+
if (q === "") return true;
|
|
7432
|
+
if (it.name.toLowerCase().includes(q)) return true;
|
|
7433
|
+
if ((it.author ?? "").toLowerCase().includes(q)) return true;
|
|
7434
|
+
if ((it.description ?? "").toLowerCase().includes(q)) return true;
|
|
7435
|
+
return false;
|
|
7399
7436
|
});
|
|
7400
|
-
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
-
|
|
7437
|
+
}
|
|
7438
|
+
/**
|
|
7439
|
+
* 供应链警示 → warn/info 着色行(客户端专属)。**逻辑委托** Host 权威 `marketItemWarnings`
|
|
7440
|
+
* (恒生成非官方审核 + 来源 URL + 下载时间 + 作者/来源自述),本层只负责给「非官方审核」标 warn、
|
|
7441
|
+
* 其余标 info,供 UI 列表着色。硬不变式:返回恒非空(至少一条),确认导入前必经。
|
|
7442
|
+
*/
|
|
7443
|
+
function marketWarningsLines(detail, url, t = zhUiT) {
|
|
7444
|
+
return marketItemWarnings(detail.provenance !== void 0 || detail.author !== void 0 ? {
|
|
7445
|
+
name: detail.name,
|
|
7446
|
+
author: detail.author,
|
|
7447
|
+
provenance: detail.provenance
|
|
7448
|
+
} : void 0, url, detail.downloadedAt, t).map((text, i) => ({
|
|
7449
|
+
kind: i === 0 ? "warn" : "info",
|
|
7450
|
+
text
|
|
7451
|
+
}));
|
|
7452
|
+
}
|
|
7453
|
+
/**
|
|
7454
|
+
* 聚合详情视图(客户端专属):徽章(委托共享 computeItemBadge)+ 供应链警示着色行
|
|
7455
|
+
* (委托共享 marketItemWarnings)+ 校验错误 + 可否导入。
|
|
7456
|
+
*/
|
|
7457
|
+
function marketDetailView(detail, url, showBack, t = zhUiT) {
|
|
7458
|
+
const badge = computeItemBadge(detail, t);
|
|
7459
|
+
return {
|
|
7460
|
+
badge: {
|
|
7461
|
+
statusKind: badge.valid ? "ok" : "error",
|
|
7462
|
+
statusText: badge.statusText,
|
|
7463
|
+
sectionsText: badge.sectionsText,
|
|
7464
|
+
valid: badge.valid
|
|
7465
|
+
},
|
|
7466
|
+
warnings: marketWarningsLines(detail, url, t),
|
|
7467
|
+
errors: detail.errors ?? [],
|
|
7468
|
+
canImport: detail.status === "valid" && detail.sections.length > 0,
|
|
7469
|
+
showBack
|
|
7470
|
+
};
|
|
7471
|
+
}
|
|
7472
|
+
/**
|
|
7473
|
+
* 高风险分区(默认不导入,须逐项显式批准):
|
|
7474
|
+
* - pluginFiles :把远端文件写进 $DSH_HOME(可覆盖本机插件配置文件)
|
|
7475
|
+
* - agentInstructions :AGENTS.md 注入每个会话的全局指令(LLM 言行边界)
|
|
7476
|
+
* - agentPresets :agent 预设(会话 persona / 行为模板)
|
|
7477
|
+
* - sessions :session 文件(可带历史/敏感上下文)
|
|
7478
|
+
* - mcp :注册 MCP 服务器(可接入外部工具/执行能力)
|
|
7479
|
+
* - plugins :安装/更新插件(ExecutePlugin / Install 项,供应链最高风险)
|
|
7480
|
+
* 其余(settings/ui/providers/prompts/skills/workspaces/credentialsStatus…)默认勾选。
|
|
7481
|
+
*/
|
|
7482
|
+
const HIGH_RISK_ADAPTERS = /* @__PURE__ */ new Set([
|
|
7483
|
+
"pluginFiles",
|
|
7484
|
+
"agentInstructions",
|
|
7485
|
+
"agentPresets",
|
|
7486
|
+
"sessions",
|
|
7487
|
+
"mcp",
|
|
7488
|
+
"plugins"
|
|
7489
|
+
]);
|
|
7490
|
+
/** 是否为高风险分区(需逐项显式批准,默认不导入)。 */
|
|
7491
|
+
function isHighRiskAdapter(adapter) {
|
|
7492
|
+
return HIGH_RISK_ADAPTERS.has(adapter);
|
|
7493
|
+
}
|
|
7494
|
+
/** PlanItemKind 是否需要重启 DSH 生效(Install 及插件级变更)。 */
|
|
7495
|
+
function itemNeedsRestart(adapter, kind) {
|
|
7496
|
+
if (kind === "Install") return true;
|
|
7497
|
+
if (adapter === "plugins" || adapter === "mcp" || adapter === "agentPresets" || adapter === "agentInstructions") return true;
|
|
7498
|
+
return false;
|
|
7499
|
+
}
|
|
7500
|
+
/** 收集计划中出现过的分区(按 APPLY_ORDER 语义排序不重要,去重即可)。 */
|
|
7501
|
+
function planAdapters(plan) {
|
|
7502
|
+
const set = /* @__PURE__ */ new Set();
|
|
7503
|
+
for (const item of plan.items) set.add(item.adapter);
|
|
7504
|
+
return [...set];
|
|
7505
|
+
}
|
|
7506
|
+
/**
|
|
7507
|
+
* 默认批准表:低风险分区默认勾选(true);高风险分区默认不勾选(false,须逐项显式批准)。
|
|
7508
|
+
* 这是严格分层信任的默认 —— 不提供「自动信任高风险来源」的默认。
|
|
7509
|
+
*/
|
|
7510
|
+
function defaultApprovals(plan) {
|
|
7511
|
+
const out = {};
|
|
7512
|
+
for (const adapter of planAdapters(plan)) out[adapter] = !isHighRiskAdapter(adapter);
|
|
7513
|
+
return out;
|
|
7514
|
+
}
|
|
7515
|
+
/**
|
|
7516
|
+
* 按批准表过滤计划 → 仅保留已批准分区的项(供 executeImportPlan 执行的子计划,subPlan)。
|
|
7517
|
+
* - items:仅保留 approved[adapter]===true 的项;
|
|
7518
|
+
* - needsRestart:按已批准项里是否有需重启者重算(不再沿用整份计划的 needsRestart);
|
|
7519
|
+
* - estimatedActions:仅保留已批准分区的计数;
|
|
7520
|
+
* - globalStrategy / missingSecrets / pathMappings:透传(导入的可选分区子集不改变这些)。
|
|
7521
|
+
* 返回与入参同形状的 ImportPlan,可直接交 executeImportPlan(analytic 只执行 plan.items)。
|
|
7522
|
+
*/
|
|
7523
|
+
function buildApprovedPlan(plan, approvals) {
|
|
7524
|
+
const items = plan.items.filter((it) => approvals[it.adapter] === true);
|
|
7525
|
+
let needsRestart = false;
|
|
7526
|
+
const estimatedActions = {};
|
|
7527
|
+
for (const it of items) {
|
|
7528
|
+
if (itemNeedsRestart(it.adapter, it.kind)) needsRestart = true;
|
|
7529
|
+
estimatedActions[it.adapter] = (estimatedActions[it.adapter] ?? 0) + 1;
|
|
7530
|
+
}
|
|
7531
|
+
return {
|
|
7532
|
+
items,
|
|
7533
|
+
globalStrategy: plan.globalStrategy,
|
|
7534
|
+
pathMappings: plan.pathMappings,
|
|
7535
|
+
missingSecrets: plan.missingSecrets,
|
|
7536
|
+
needsRestart,
|
|
7537
|
+
estimatedActions
|
|
7538
|
+
};
|
|
7539
|
+
}
|
|
7540
|
+
/** 批准表摘要(确认按钮可用性 + 提示徽章数据源,纯函数)。 */
|
|
7541
|
+
function approvedAdapterSummary(plan, approvals) {
|
|
7542
|
+
const adapters = planAdapters(plan);
|
|
7543
|
+
let selected = 0;
|
|
7544
|
+
let highRiskSelected = 0;
|
|
7545
|
+
let highRiskTotal = 0;
|
|
7546
|
+
for (const a of adapters) {
|
|
7547
|
+
if (approvals[a] === true) selected += 1;
|
|
7548
|
+
if (isHighRiskAdapter(a)) {
|
|
7549
|
+
highRiskTotal += 1;
|
|
7550
|
+
if (approvals[a] === true) highRiskSelected += 1;
|
|
7551
|
+
}
|
|
7552
|
+
}
|
|
7553
|
+
const hasItems = plan.items.some((it) => approvals[it.adapter] === true);
|
|
7554
|
+
return {
|
|
7555
|
+
total: adapters.length,
|
|
7556
|
+
selected,
|
|
7557
|
+
canImport: hasItems,
|
|
7558
|
+
highRiskSelected,
|
|
7559
|
+
highRiskTotal
|
|
7560
|
+
};
|
|
7561
|
+
}
|
|
7562
|
+
/** 计划 → 分区批准列表(供详情视图逐项勾选渲染;纯函数)。 */
|
|
7563
|
+
function approvalRows(plan, approvals) {
|
|
7564
|
+
const byAdapter = /* @__PURE__ */ new Map();
|
|
7565
|
+
for (const item of plan.items) {
|
|
7566
|
+
const list = byAdapter.get(item.adapter) ?? [];
|
|
7567
|
+
list.push(item);
|
|
7568
|
+
byAdapter.set(item.adapter, list);
|
|
7569
|
+
}
|
|
7570
|
+
const rows = [];
|
|
7571
|
+
for (const adapter of planAdapters(plan)) {
|
|
7572
|
+
const items = byAdapter.get(adapter) ?? [];
|
|
7573
|
+
const label = items[0]?.description ?? adapter;
|
|
7574
|
+
rows.push({
|
|
7575
|
+
adapter,
|
|
7576
|
+
itemCount: items.length,
|
|
7577
|
+
highRisk: isHighRiskAdapter(adapter),
|
|
7578
|
+
approved: approvals[adapter] === true,
|
|
7579
|
+
label
|
|
7580
|
+
});
|
|
7581
|
+
}
|
|
7582
|
+
return rows;
|
|
7583
|
+
}
|
|
7584
|
+
//#endregion
|
|
7585
|
+
//#region src/client/market/MyConfigsView.tsx
|
|
7586
|
+
/**
|
|
7587
|
+
* 「我的配置」视图(设计文档 docs/design/2026-08-20-my-configs-design.md §4.6)。
|
|
7588
|
+
*
|
|
7589
|
+
* 组装「一键上传 / 查看已上传 / 一键更新 / 装回本地」:
|
|
7590
|
+
* - **登录卡**:未登录 → GitHub device flow 登录(复用 SyncApi.githubStart/Poll/Cancel,
|
|
7591
|
+
* 交互与 SyncSettingsView 一致:一次性用户码 + 授权页链接 + 轮询 + 取消,定时器卸载清理);
|
|
7592
|
+
* 已登录 → @login + 固定目标仓库(xiajiajun516/dsh-config-market,只读展示,无编辑入口);
|
|
7593
|
+
* token 失效(/me/status 401)→ 引导重新登录;
|
|
7594
|
+
* - **上传向导**:选 zip(复用 ConfigManagerApi.upload 受控临时区)→ analyzeImport 校验
|
|
7595
|
+
* (内容合法 + 无密钥,零写入)→ 精简表单(仅 name/description/categories;
|
|
7596
|
+
* id/author/version/updatedAt 显示「系统自动」徽章)→ meUpload 一键上传 → 结果卡
|
|
7597
|
+
* (PR 链接 / 仓库链接 / sha256 / 分区);
|
|
7598
|
+
* - **已上传列表**:条目卡片(字段 + 收录状态徽章:未收录 / PR 待审核[带 PR 链接] / 已收录,
|
|
7599
|
+
* 状态由 Host 侧判定经 itemStatusFromHost 桥接)+ 行操作:更新(预填信息进向导)/
|
|
7600
|
+
* 装回本地(复用市场下载 + 逐分区批准 + executeImportPlan 安全管道)/ 打开仓库。
|
|
7601
|
+
*
|
|
7602
|
+
* 渲染/校验模型全部来自 my-configs-view.ts + market-view.ts 纯函数(node 已测),本组件只装配;
|
|
7603
|
+
* 状态组件内自持(useState),非敏感切片(已上传列表 myItems + 错误)为**受控 props**:
|
|
7604
|
+
* 经 MarketPanel 的 commit/patch 统一镜像进模块级 runStore(market.myItems / myItemsError),
|
|
7605
|
+
* 切 tab 不丢;刷新后免重拉(与 MarketPanel 浏览态同单店镜像策略)。
|
|
7606
|
+
* 安全:token 只存宿主凭据槽;密码/表单无敏感字段;所有展示文本渲染前过 redact() 兜底;
|
|
7607
|
+
* 本文件不 import 任何 node 模块(纯浏览器 bundle)。
|
|
7608
|
+
*/
|
|
7609
|
+
const initialGithubFlow = {
|
|
7610
|
+
phase: "idle",
|
|
7611
|
+
flowId: "",
|
|
7612
|
+
userCode: "",
|
|
7613
|
+
verificationUri: "",
|
|
7614
|
+
interval: 5,
|
|
7615
|
+
error: null
|
|
7616
|
+
};
|
|
7617
|
+
function initialWizard(mode) {
|
|
7618
|
+
return {
|
|
7619
|
+
mode,
|
|
7620
|
+
step: "select",
|
|
7621
|
+
zipPath: null,
|
|
7622
|
+
fileName: null,
|
|
7623
|
+
validating: false,
|
|
7624
|
+
validated: false,
|
|
7625
|
+
validationError: null,
|
|
7626
|
+
form: { ...EMPTY_MY_CONFIG_FORM },
|
|
7627
|
+
formErrors: { name: null },
|
|
7628
|
+
running: false,
|
|
7629
|
+
result: null,
|
|
7630
|
+
error: null
|
|
7631
|
+
};
|
|
7632
|
+
}
|
|
7633
|
+
function MyConfigsView({ meApi, api, importApi, syncApi, t, myItems, myItemsError, onMyItemsChange, onBack }) {
|
|
7634
|
+
const uiT = meApi.t;
|
|
7635
|
+
const [status, setStatus] = (0, react.useState)(null);
|
|
7636
|
+
const [statusLoading, setStatusLoading] = (0, react.useState)(true);
|
|
7637
|
+
const [statusFailed, setStatusFailed] = (0, react.useState)(false);
|
|
7638
|
+
const [github, setGithub] = (0, react.useState)(initialGithubFlow);
|
|
7639
|
+
const githubPollTimer = (0, react.useRef)(null);
|
|
7640
|
+
const [listLoading, setListLoading] = (0, react.useState)(false);
|
|
7641
|
+
const [wizard, setWizard] = (0, react.useState)(() => initialWizard("upload"));
|
|
7642
|
+
const [install, setInstall] = (0, react.useState)(null);
|
|
7643
|
+
const fileInput = (0, react.useRef)(null);
|
|
7644
|
+
/** 读取登录态(挂载 / 登录成功 / 状态刷新),返回 status 供后续判断 */
|
|
7645
|
+
const loadStatus = async () => {
|
|
7646
|
+
setStatusLoading(true);
|
|
7647
|
+
setStatusFailed(false);
|
|
7409
7648
|
try {
|
|
7410
|
-
const
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
} catch
|
|
7414
|
-
|
|
7649
|
+
const s = await meApi.meStatus();
|
|
7650
|
+
setStatus(s);
|
|
7651
|
+
return s;
|
|
7652
|
+
} catch {
|
|
7653
|
+
setStatusFailed(true);
|
|
7654
|
+
return null;
|
|
7415
7655
|
} finally {
|
|
7416
|
-
|
|
7656
|
+
setStatusLoading(false);
|
|
7417
7657
|
}
|
|
7418
7658
|
};
|
|
7419
|
-
/**
|
|
7420
|
-
const
|
|
7421
|
-
if (
|
|
7422
|
-
setValidating(true);
|
|
7423
|
-
setValidationError(null);
|
|
7659
|
+
/** 加载已上传列表(状态上抛 MarketPanel 镜像 runStore:切 tab 不丢 / 刷新免重拉) */
|
|
7660
|
+
const loadItems = async (opts = {}) => {
|
|
7661
|
+
if (!opts.silent) setListLoading(true);
|
|
7424
7662
|
try {
|
|
7425
|
-
|
|
7426
|
-
if (analysis.secretCount > 0) {
|
|
7427
|
-
setValidated(false);
|
|
7428
|
-
setValidationError(t("publish.validate.secrets"));
|
|
7429
|
-
} else if (!analysis.valid) {
|
|
7430
|
-
setValidated(false);
|
|
7431
|
-
setValidationError(t("publish.validate.invalid"));
|
|
7432
|
-
} else setValidated(true);
|
|
7663
|
+
onMyItemsChange((await meApi.meItems()).items, null);
|
|
7433
7664
|
} catch (err) {
|
|
7434
|
-
|
|
7435
|
-
setValidationError(err instanceof Error ? err.message : String(err));
|
|
7665
|
+
onMyItemsChange(myItems, err instanceof Error ? err.message : String(err));
|
|
7436
7666
|
} finally {
|
|
7437
|
-
|
|
7667
|
+
if (!opts.silent) setListLoading(false);
|
|
7438
7668
|
}
|
|
7439
7669
|
};
|
|
7440
|
-
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
7444
|
-
|
|
7670
|
+
(0, react.useEffect)(() => {
|
|
7671
|
+
let cancelled = false;
|
|
7672
|
+
(async () => {
|
|
7673
|
+
const s = await loadStatus();
|
|
7674
|
+
if (!cancelled && s !== null && s.loggedIn) loadItems({ silent: true });
|
|
7675
|
+
})();
|
|
7676
|
+
return () => {
|
|
7677
|
+
cancelled = true;
|
|
7678
|
+
if (githubPollTimer.current !== null) clearTimeout(githubPollTimer.current);
|
|
7445
7679
|
};
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
|
|
7450
|
-
|
|
7451
|
-
|
|
7452
|
-
|
|
7453
|
-
setFormErrors(errs);
|
|
7454
|
-
if (!publishFormValid(errs)) return;
|
|
7455
|
-
const categories = parseCategories();
|
|
7456
|
-
setGenerating(true);
|
|
7457
|
-
setPrepareError(null);
|
|
7680
|
+
}, []);
|
|
7681
|
+
const runGithubStart = async () => {
|
|
7682
|
+
setGithub((g) => ({
|
|
7683
|
+
...g,
|
|
7684
|
+
phase: "starting",
|
|
7685
|
+
error: null
|
|
7686
|
+
}));
|
|
7458
7687
|
try {
|
|
7459
|
-
const
|
|
7460
|
-
|
|
7461
|
-
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
7465
|
-
|
|
7466
|
-
|
|
7467
|
-
...categories.length > 0 ? { categories } : {}
|
|
7688
|
+
const info = await syncApi.githubStart();
|
|
7689
|
+
setGithub({
|
|
7690
|
+
phase: "waiting",
|
|
7691
|
+
flowId: info.flowId,
|
|
7692
|
+
userCode: info.userCode,
|
|
7693
|
+
verificationUri: info.verificationUri,
|
|
7694
|
+
interval: info.interval,
|
|
7695
|
+
error: null
|
|
7468
7696
|
});
|
|
7469
|
-
|
|
7697
|
+
scheduleGithubPoll(info.flowId, Math.max(info.interval, 1) * 1e3);
|
|
7470
7698
|
} catch (err) {
|
|
7471
|
-
|
|
7472
|
-
|
|
7473
|
-
|
|
7699
|
+
setGithub((g) => ({
|
|
7700
|
+
...g,
|
|
7701
|
+
phase: "error",
|
|
7702
|
+
error: err instanceof Error ? err.message : String(err)
|
|
7703
|
+
}));
|
|
7474
7704
|
}
|
|
7475
7705
|
};
|
|
7476
|
-
|
|
7477
|
-
|
|
7706
|
+
const scheduleGithubPoll = (flowId, delayMs) => {
|
|
7707
|
+
if (githubPollTimer.current !== null) clearTimeout(githubPollTimer.current);
|
|
7708
|
+
githubPollTimer.current = setTimeout(() => {
|
|
7709
|
+
runGithubPoll(flowId);
|
|
7710
|
+
}, delayMs);
|
|
7711
|
+
};
|
|
7712
|
+
const runGithubPoll = async (flowId) => {
|
|
7713
|
+
setGithub((g) => ({
|
|
7714
|
+
...g,
|
|
7715
|
+
phase: "polling"
|
|
7716
|
+
}));
|
|
7478
7717
|
try {
|
|
7479
|
-
await
|
|
7480
|
-
|
|
7481
|
-
|
|
7482
|
-
|
|
7718
|
+
const poll = await syncApi.githubPoll(flowId);
|
|
7719
|
+
if (poll.status === "pending") {
|
|
7720
|
+
setGithub((g) => ({
|
|
7721
|
+
...g,
|
|
7722
|
+
phase: "waiting"
|
|
7723
|
+
}));
|
|
7724
|
+
scheduleGithubPoll(flowId, poll.pollDelayMs ?? Math.max(github.interval, 1) * 1e3);
|
|
7725
|
+
return;
|
|
7726
|
+
}
|
|
7727
|
+
const message = githubPollMessage(poll, uiT);
|
|
7728
|
+
if (poll.status === "success") {
|
|
7729
|
+
setGithub(initialGithubFlow);
|
|
7730
|
+
const s = await loadStatus();
|
|
7731
|
+
if (s !== null && s.loggedIn) loadItems({ silent: true });
|
|
7732
|
+
} else setGithub((g) => ({
|
|
7733
|
+
...g,
|
|
7734
|
+
phase: "error",
|
|
7735
|
+
error: message
|
|
7736
|
+
}));
|
|
7737
|
+
} catch (err) {
|
|
7738
|
+
setGithub((g) => ({
|
|
7739
|
+
...g,
|
|
7740
|
+
phase: "error",
|
|
7741
|
+
error: err instanceof Error ? err.message : String(err)
|
|
7742
|
+
}));
|
|
7483
7743
|
}
|
|
7484
7744
|
};
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
|
|
7745
|
+
const runGithubCancel = async () => {
|
|
7746
|
+
if (githubPollTimer.current !== null) {
|
|
7747
|
+
clearTimeout(githubPollTimer.current);
|
|
7748
|
+
githubPollTimer.current = null;
|
|
7749
|
+
}
|
|
7750
|
+
const flowId = github.flowId;
|
|
7751
|
+
setGithub(initialGithubFlow);
|
|
7752
|
+
if (flowId !== "") try {
|
|
7753
|
+
await syncApi.githubCancel(flowId);
|
|
7754
|
+
} catch {}
|
|
7755
|
+
};
|
|
7756
|
+
/** 步骤 1:选 zip → upload 受控临时区;upload 模式预填 name 为 zip 文件名(可改) */
|
|
7757
|
+
const onPickFile = async (file) => {
|
|
7758
|
+
if (file === void 0) return;
|
|
7759
|
+
setWizard((w) => ({
|
|
7760
|
+
...w,
|
|
7761
|
+
fileName: file.name,
|
|
7762
|
+
error: null
|
|
7763
|
+
}));
|
|
7764
|
+
try {
|
|
7765
|
+
const uploaded = await importApi.upload(file);
|
|
7766
|
+
setWizard((w) => ({
|
|
7767
|
+
...w,
|
|
7768
|
+
zipPath: uploaded.zipPath,
|
|
7769
|
+
step: "validate",
|
|
7770
|
+
form: w.mode === "upload" ? {
|
|
7771
|
+
...w.form,
|
|
7772
|
+
name: file.name.replace(/\.zip$/i, "")
|
|
7773
|
+
} : w.form
|
|
7774
|
+
}));
|
|
7775
|
+
} catch (err) {
|
|
7776
|
+
setWizard((w) => ({
|
|
7777
|
+
...w,
|
|
7778
|
+
error: err instanceof Error ? err.message : String(err)
|
|
7779
|
+
}));
|
|
7780
|
+
}
|
|
7781
|
+
};
|
|
7782
|
+
/** 步骤 2:analyzeImport dry-run(零写入)—— 无密钥 + 内容合法才放行;通过后进表单 */
|
|
7783
|
+
const runValidate = async () => {
|
|
7784
|
+
if (wizard.zipPath === null) return;
|
|
7785
|
+
setWizard((w) => ({
|
|
7786
|
+
...w,
|
|
7787
|
+
validating: true,
|
|
7788
|
+
validationError: null
|
|
7789
|
+
}));
|
|
7790
|
+
try {
|
|
7791
|
+
const analysis = await importApi.analyzeImport(wizard.zipPath);
|
|
7792
|
+
if (analysis.secretCount > 0) setWizard((w) => ({
|
|
7793
|
+
...w,
|
|
7794
|
+
validating: false,
|
|
7795
|
+
validated: false,
|
|
7796
|
+
validationError: t("myconfigs.upload.validateSecrets")
|
|
7797
|
+
}));
|
|
7798
|
+
else if (!analysis.valid) setWizard((w) => ({
|
|
7799
|
+
...w,
|
|
7800
|
+
validating: false,
|
|
7801
|
+
validated: false,
|
|
7802
|
+
validationError: t("myconfigs.upload.validateInvalid")
|
|
7803
|
+
}));
|
|
7804
|
+
else setWizard((w) => ({
|
|
7805
|
+
...w,
|
|
7806
|
+
validating: false,
|
|
7807
|
+
validated: true,
|
|
7808
|
+
step: "form"
|
|
7809
|
+
}));
|
|
7810
|
+
} catch (err) {
|
|
7811
|
+
setWizard((w) => ({
|
|
7812
|
+
...w,
|
|
7813
|
+
validating: false,
|
|
7814
|
+
validated: false,
|
|
7815
|
+
validationError: err instanceof Error ? err.message : String(err)
|
|
7816
|
+
}));
|
|
7817
|
+
}
|
|
7818
|
+
};
|
|
7819
|
+
/** 表单字段更新 + 实时校验(pure 模型) */
|
|
7820
|
+
const onFormField = (field, value) => {
|
|
7821
|
+
const next = {
|
|
7822
|
+
...wizard.form,
|
|
7823
|
+
[field]: value
|
|
7824
|
+
};
|
|
7825
|
+
setWizard((w) => ({
|
|
7826
|
+
...w,
|
|
7827
|
+
form: next,
|
|
7828
|
+
formErrors: validateMyConfigForm(next, uiT)
|
|
7829
|
+
}));
|
|
7830
|
+
};
|
|
7831
|
+
/** 「一键上传 / 一键更新」→ meUpload / meUpdate */
|
|
7832
|
+
const runUpload = async () => {
|
|
7833
|
+
if (wizard.zipPath === null) return;
|
|
7834
|
+
const errs = validateMyConfigForm(wizard.form, uiT);
|
|
7835
|
+
setWizard((w) => ({
|
|
7836
|
+
...w,
|
|
7837
|
+
formErrors: errs
|
|
7838
|
+
}));
|
|
7839
|
+
if (!myConfigFormValid(errs)) return;
|
|
7840
|
+
const categories = parseCategories(wizard.form.categories);
|
|
7841
|
+
const form = {
|
|
7842
|
+
name: wizard.form.name.trim(),
|
|
7843
|
+
...wizard.form.description.trim() !== "" ? { description: wizard.form.description.trim() } : {},
|
|
7844
|
+
...categories.length > 0 ? { categories } : {}
|
|
7845
|
+
};
|
|
7846
|
+
setWizard((w) => ({
|
|
7847
|
+
...w,
|
|
7848
|
+
running: true,
|
|
7849
|
+
error: null,
|
|
7850
|
+
result: null
|
|
7851
|
+
}));
|
|
7852
|
+
try {
|
|
7853
|
+
const result = wizard.mode === "update" ? await meApi.meUpdate({
|
|
7854
|
+
zipPath: wizard.zipPath,
|
|
7855
|
+
form
|
|
7856
|
+
}) : await meApi.meUpload({
|
|
7857
|
+
zipPath: wizard.zipPath,
|
|
7858
|
+
form
|
|
7859
|
+
});
|
|
7860
|
+
setWizard((w) => ({
|
|
7861
|
+
...w,
|
|
7862
|
+
running: false,
|
|
7863
|
+
result
|
|
7864
|
+
}));
|
|
7865
|
+
loadItems({ silent: true });
|
|
7866
|
+
} catch (err) {
|
|
7867
|
+
setWizard((w) => ({
|
|
7868
|
+
...w,
|
|
7869
|
+
running: false,
|
|
7870
|
+
error: err instanceof Error ? err.message : String(err)
|
|
7871
|
+
}));
|
|
7872
|
+
}
|
|
7873
|
+
};
|
|
7874
|
+
/** 行操作:更新 → 打开向导,预填条目信息(name/description/categories) */
|
|
7875
|
+
const startUpdate = (entry) => {
|
|
7876
|
+
setWizard({
|
|
7877
|
+
...initialWizard("update"),
|
|
7878
|
+
form: {
|
|
7879
|
+
name: entry.name,
|
|
7880
|
+
description: entry.description ?? "",
|
|
7881
|
+
categories: (entry.categories ?? []).join(", ")
|
|
7507
7882
|
}
|
|
7508
|
-
}),
|
|
7509
|
-
fileName !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7510
|
-
className: config_manager_module_css_default.statRow,
|
|
7511
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
7512
|
-
kind: "info",
|
|
7513
|
-
children: t("publish.select.file", { name: fileName })
|
|
7514
|
-
})
|
|
7515
|
-
}),
|
|
7516
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7517
|
-
className: config_manager_module_css_default.actionRow,
|
|
7518
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
7519
|
-
variant: "primary",
|
|
7520
|
-
disabled: uploading,
|
|
7521
|
-
onClick: () => {
|
|
7522
|
-
fileInput.current?.click();
|
|
7523
|
-
},
|
|
7524
|
-
children: uploading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("publish.select.uploading") }) : t(fileName !== null ? "publish.select.reselect" : "publish.select.browse")
|
|
7525
|
-
})
|
|
7526
|
-
})
|
|
7527
|
-
] });
|
|
7528
|
-
const renderValidate = () => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
|
|
7529
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7530
|
-
className: config_manager_module_css_default.hint,
|
|
7531
|
-
children: t("publish.validate.hint")
|
|
7532
|
-
}),
|
|
7533
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7534
|
-
className: config_manager_module_css_default.actionRow,
|
|
7535
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
7536
|
-
variant: "primary",
|
|
7537
|
-
disabled: zipPath === null || validating,
|
|
7538
|
-
onClick: () => {
|
|
7539
|
-
runValidate();
|
|
7540
|
-
},
|
|
7541
|
-
children: validating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("publish.validate.running") }) : validated ? t("publish.validate.retry") : t("publish.validate.run")
|
|
7542
|
-
})
|
|
7543
|
-
}),
|
|
7544
|
-
validated && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
7545
|
-
kind: "ok",
|
|
7546
|
-
children: t("publish.validate.ok")
|
|
7547
|
-
}),
|
|
7548
|
-
validationError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
|
|
7549
|
-
kind: "error",
|
|
7550
|
-
children: redact(validationError)
|
|
7551
|
-
})
|
|
7552
|
-
] });
|
|
7553
|
-
const renderForm = () => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
|
|
7554
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7555
|
-
className: config_manager_module_css_default.groupLabel,
|
|
7556
|
-
children: t("publish.form.title")
|
|
7557
|
-
}),
|
|
7558
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Field, {
|
|
7559
|
-
label: t("publish.form.id"),
|
|
7560
|
-
hint: t("publish.form.idHint"),
|
|
7561
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
7562
|
-
className: config_manager_module_css_default.input,
|
|
7563
|
-
value: form.itemId,
|
|
7564
|
-
onChange: (e) => {
|
|
7565
|
-
onFormField("itemId", e.target.value);
|
|
7566
|
-
}
|
|
7567
|
-
}), formErrors.itemId !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7568
|
-
className: config_manager_module_css_default.formError,
|
|
7569
|
-
children: redact(formErrors.itemId)
|
|
7570
|
-
})]
|
|
7571
|
-
}),
|
|
7572
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Field, {
|
|
7573
|
-
label: t("publish.form.name"),
|
|
7574
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
7575
|
-
className: config_manager_module_css_default.input,
|
|
7576
|
-
value: form.name,
|
|
7577
|
-
onChange: (e) => {
|
|
7578
|
-
onFormField("name", e.target.value);
|
|
7579
|
-
}
|
|
7580
|
-
}), formErrors.name !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7581
|
-
className: config_manager_module_css_default.formError,
|
|
7582
|
-
children: redact(formErrors.name)
|
|
7583
|
-
})]
|
|
7584
|
-
}),
|
|
7585
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
7586
|
-
label: t("publish.form.description"),
|
|
7587
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
7588
|
-
className: config_manager_module_css_default.input,
|
|
7589
|
-
value: form.description,
|
|
7590
|
-
onChange: (e) => {
|
|
7591
|
-
onFormField("description", e.target.value);
|
|
7592
|
-
}
|
|
7593
|
-
})
|
|
7594
|
-
}),
|
|
7595
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
7596
|
-
label: t("publish.form.author"),
|
|
7597
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
7598
|
-
className: config_manager_module_css_default.input,
|
|
7599
|
-
value: form.author,
|
|
7600
|
-
onChange: (e) => {
|
|
7601
|
-
onFormField("author", e.target.value);
|
|
7602
|
-
}
|
|
7603
|
-
})
|
|
7604
|
-
}),
|
|
7605
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
7606
|
-
label: t("publish.form.version"),
|
|
7607
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
7608
|
-
className: config_manager_module_css_default.input,
|
|
7609
|
-
value: form.version,
|
|
7610
|
-
onChange: (e) => {
|
|
7611
|
-
onFormField("version", e.target.value);
|
|
7612
|
-
}
|
|
7613
|
-
})
|
|
7614
|
-
}),
|
|
7615
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
7616
|
-
label: t("publish.form.categories"),
|
|
7617
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
7618
|
-
className: config_manager_module_css_default.input,
|
|
7619
|
-
value: form.categories,
|
|
7620
|
-
onChange: (e) => {
|
|
7621
|
-
onFormField("categories", e.target.value);
|
|
7622
|
-
}
|
|
7623
|
-
})
|
|
7624
|
-
}),
|
|
7625
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Field, {
|
|
7626
|
-
label: t("publish.form.repoUrl"),
|
|
7627
|
-
hint: t("publish.form.repoUrlHint"),
|
|
7628
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
7629
|
-
className: config_manager_module_css_default.input,
|
|
7630
|
-
value: form.repoUrl,
|
|
7631
|
-
onChange: (e) => {
|
|
7632
|
-
onFormField("repoUrl", e.target.value);
|
|
7633
|
-
}
|
|
7634
|
-
}), formErrors.repoUrl !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7635
|
-
className: config_manager_module_css_default.formError,
|
|
7636
|
-
children: redact(formErrors.repoUrl)
|
|
7637
|
-
})]
|
|
7638
|
-
}),
|
|
7639
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7640
|
-
className: config_manager_module_css_default.actionRow,
|
|
7641
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
7642
|
-
variant: "primary",
|
|
7643
|
-
disabled: generating || !publishFormValid(formErrors),
|
|
7644
|
-
onClick: () => {
|
|
7645
|
-
runGenerate();
|
|
7646
|
-
},
|
|
7647
|
-
children: generating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("publish.form.generating") }) : t("publish.form.generate")
|
|
7648
|
-
})
|
|
7649
|
-
}),
|
|
7650
|
-
prepareError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
|
|
7651
|
-
kind: "error",
|
|
7652
|
-
children: redact(prepareError)
|
|
7653
|
-
}),
|
|
7654
|
-
prepareResult !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
7655
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7656
|
-
className: config_manager_module_css_default.groupLabel,
|
|
7657
|
-
children: t("publish.prepare.title")
|
|
7658
|
-
}),
|
|
7659
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
|
|
7660
|
-
kind: "warn",
|
|
7661
|
-
children: t("publish.prepare.warnings")
|
|
7662
|
-
}),
|
|
7663
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7664
|
-
className: config_manager_module_css_default.statRow,
|
|
7665
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
7666
|
-
kind: "info",
|
|
7667
|
-
children: t("publish.prepare.sha256", { hash: prepareResult.sha256 })
|
|
7668
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
7669
|
-
kind: "info",
|
|
7670
|
-
children: t("publish.prepare.sections", { sections: prepareResult.sections.join(", ") })
|
|
7671
|
-
})]
|
|
7672
|
-
}),
|
|
7673
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7674
|
-
className: config_manager_module_css_default.fieldLabel,
|
|
7675
|
-
children: t("publish.prepare.manifest")
|
|
7676
|
-
}),
|
|
7677
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7678
|
-
className: config_manager_module_css_default.actionRow,
|
|
7679
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
7680
|
-
onClick: () => {
|
|
7681
|
-
copyText("manifest", prepareResult.manifestText);
|
|
7682
|
-
},
|
|
7683
|
-
children: t("publish.prepare.copyManifest")
|
|
7684
|
-
}), copyBadge("manifest")]
|
|
7685
|
-
}),
|
|
7686
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
7687
|
-
className: config_manager_module_css_default.reportText,
|
|
7688
|
-
children: redact(prepareResult.manifestText)
|
|
7689
|
-
}),
|
|
7690
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7691
|
-
className: config_manager_module_css_default.hint,
|
|
7692
|
-
children: t("publish.prepare.dirHint", { dir: prepareResult.dir })
|
|
7693
|
-
})
|
|
7694
|
-
] })
|
|
7695
|
-
] });
|
|
7696
|
-
const renderPush = () => {
|
|
7697
|
-
const repoUrl = form.repoUrl.trim();
|
|
7698
|
-
const zipPath = prepareResult?.zipPath;
|
|
7699
|
-
const downloadBtn = zipPath !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
7700
|
-
onClick: () => {
|
|
7701
|
-
window.open(api.downloadPublishUrl(zipPath), "_blank");
|
|
7702
|
-
},
|
|
7703
|
-
children: t("publish.push.download")
|
|
7704
7883
|
});
|
|
7705
|
-
|
|
7706
|
-
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
|
|
7710
|
-
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
dir: "market-items"
|
|
7884
|
+
};
|
|
7885
|
+
/** 装回本地(复用市场下载 + 逐分区批准链路;条目内容在用户自己的公开仓库,必须带 repo 来源) */
|
|
7886
|
+
const runDownload = async (entry) => {
|
|
7887
|
+
setInstall({
|
|
7888
|
+
itemId: entry.id,
|
|
7889
|
+
detail: null,
|
|
7890
|
+
approvals: {},
|
|
7891
|
+
importing: false,
|
|
7892
|
+
importResult: null,
|
|
7893
|
+
error: null
|
|
7716
7894
|
});
|
|
7717
|
-
|
|
7718
|
-
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
|
-
|
|
7895
|
+
try {
|
|
7896
|
+
const detail = await api.download(entry.id, entry.repoUrl);
|
|
7897
|
+
setInstall((i) => i !== null ? {
|
|
7898
|
+
...i,
|
|
7899
|
+
detail,
|
|
7900
|
+
approvals: defaultApprovals(detail.plan)
|
|
7901
|
+
} : i);
|
|
7902
|
+
} catch (err) {
|
|
7903
|
+
setInstall((i) => i !== null ? {
|
|
7904
|
+
...i,
|
|
7905
|
+
error: err instanceof Error ? err.message : String(err)
|
|
7906
|
+
} : i);
|
|
7907
|
+
}
|
|
7908
|
+
};
|
|
7909
|
+
const runImport = async () => {
|
|
7910
|
+
if (install === null || install.detail === null) return;
|
|
7911
|
+
const approvedPlan = buildApprovedPlan(install.detail.plan, install.approvals);
|
|
7912
|
+
if (approvedPlan.items.length === 0) {
|
|
7913
|
+
setInstall((i) => i !== null ? {
|
|
7914
|
+
...i,
|
|
7915
|
+
error: t("detail.noApproval")
|
|
7916
|
+
} : i);
|
|
7917
|
+
return;
|
|
7918
|
+
}
|
|
7919
|
+
setInstall((i) => i !== null ? {
|
|
7920
|
+
...i,
|
|
7921
|
+
importing: true,
|
|
7922
|
+
error: null
|
|
7923
|
+
} : i);
|
|
7924
|
+
try {
|
|
7925
|
+
const executed = await importApi.executeImportPlan(install.detail.zipPath, approvedPlan, {
|
|
7926
|
+
confirm: true,
|
|
7927
|
+
rollbackOnError: true
|
|
7928
|
+
});
|
|
7929
|
+
setInstall((i) => i !== null ? {
|
|
7930
|
+
...i,
|
|
7931
|
+
importing: false,
|
|
7932
|
+
importResult: executed
|
|
7933
|
+
} : i);
|
|
7934
|
+
} catch (err) {
|
|
7935
|
+
setInstall((i) => i !== null ? {
|
|
7936
|
+
...i,
|
|
7937
|
+
importing: false,
|
|
7938
|
+
error: err instanceof Error ? err.message : String(err)
|
|
7939
|
+
} : i);
|
|
7940
|
+
}
|
|
7941
|
+
};
|
|
7942
|
+
/** 登录视图(loading / logged-out / logged-in / token-invalid) */
|
|
7943
|
+
const loginView = deriveLoginState({
|
|
7944
|
+
loading: statusLoading,
|
|
7945
|
+
status,
|
|
7946
|
+
authFailed: statusFailed
|
|
7947
|
+
});
|
|
7948
|
+
/** GitHub 登录卡渲染模型(复用 sync-view 纯函数:状态行 / 按钮态 / 展示设备码) */
|
|
7949
|
+
const githubView = computeGithubLoginView(github.phase, github.userCode, github.verificationUri, github.error, uiT);
|
|
7950
|
+
/** 已上传条目投影(Host 状态 → 徽章模型) */
|
|
7951
|
+
const itemViews = (myItems ?? []).map((entry) => {
|
|
7952
|
+
const view = toMyItemView(entry, itemStatusFromHost(entry), uiT);
|
|
7953
|
+
return {
|
|
7954
|
+
entry,
|
|
7955
|
+
view,
|
|
7956
|
+
badge: view.badge
|
|
7957
|
+
};
|
|
7958
|
+
});
|
|
7959
|
+
const summary = summarizeMyItems(itemViews.map((v) => v.view));
|
|
7960
|
+
const autoBadges = autoFieldBadges(uiT);
|
|
7961
|
+
const targetRepo = `${MARKET_UPSTREAM_OWNER}/${MARKET_UPSTREAM_REPO}`;
|
|
7962
|
+
/** 设备码 + 授权页链接展示(waiting/polling 时) */
|
|
7963
|
+
const renderDeviceCode = () => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7964
|
+
className: config_manager_module_css_default.statRow,
|
|
7965
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
7966
|
+
kind: "info",
|
|
7967
|
+
children: t("myconfigs.login.userCode", { code: githubView.userCode })
|
|
7968
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
7969
|
+
className: config_manager_module_css_default.ghostButton,
|
|
7970
|
+
href: githubView.verificationUri,
|
|
7971
|
+
target: "_blank",
|
|
7972
|
+
rel: "noreferrer",
|
|
7973
|
+
style: { textDecoration: "none" },
|
|
7974
|
+
children: t("myconfigs.login.openAuth")
|
|
7975
|
+
})]
|
|
7976
|
+
});
|
|
7977
|
+
/** 登录卡(未登录 / token 失效 → device flow;已登录 → @login + 固定目标仓库只读展示) */
|
|
7978
|
+
const renderLoginCard = () => {
|
|
7979
|
+
if (loginView.kind === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7980
|
+
className: config_manager_module_css_default.statRow,
|
|
7981
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7982
|
+
className: config_manager_module_css_default.groupLabel,
|
|
7983
|
+
children: t("myconfigs.login.title")
|
|
7984
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("myconfigs.login.checking") })]
|
|
7985
|
+
}) });
|
|
7986
|
+
if (loginView.kind === "logged-out" || loginView.kind === "token-invalid") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
|
|
7722
7987
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7723
7988
|
className: config_manager_module_css_default.actionRow,
|
|
7724
7989
|
children: [
|
|
7725
|
-
|
|
7990
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7991
|
+
className: config_manager_module_css_default.groupLabel,
|
|
7992
|
+
children: t("myconfigs.login.title")
|
|
7993
|
+
}),
|
|
7726
7994
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
7995
|
+
variant: "primary",
|
|
7996
|
+
disabled: !githubView.canStart,
|
|
7727
7997
|
onClick: () => {
|
|
7728
|
-
|
|
7998
|
+
runGithubStart();
|
|
7729
7999
|
},
|
|
7730
|
-
children:
|
|
8000
|
+
children: githubView.startLabel
|
|
7731
8001
|
}),
|
|
7732
|
-
|
|
8002
|
+
githubView.canCancel && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
8003
|
+
disabled: github.phase === "starting",
|
|
8004
|
+
onClick: () => {
|
|
8005
|
+
runGithubCancel();
|
|
8006
|
+
},
|
|
8007
|
+
children: t("myconfigs.login.cancel")
|
|
8008
|
+
})
|
|
7733
8009
|
]
|
|
7734
8010
|
}),
|
|
7735
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
7736
|
-
className: config_manager_module_css_default.reportText,
|
|
7737
|
-
children: redact(commands.join("\n"))
|
|
7738
|
-
})
|
|
7739
|
-
] });
|
|
7740
|
-
};
|
|
7741
|
-
const renderSubmit = () => {
|
|
7742
|
-
const repoUrl = form.repoUrl.trim();
|
|
7743
|
-
if (repoUrl === "") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Card, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
|
|
7744
|
-
kind: "warn",
|
|
7745
|
-
children: t("publish.submit.repoMissing")
|
|
7746
|
-
}) });
|
|
7747
|
-
const categories = parseCategories();
|
|
7748
|
-
const snippet = buildIndexEntrySnippet({
|
|
7749
|
-
id: form.itemId,
|
|
7750
|
-
name: form.name,
|
|
7751
|
-
...form.description !== "" ? { description: form.description } : {},
|
|
7752
|
-
...form.author !== "" ? { author: form.author } : {},
|
|
7753
|
-
...form.version !== "" ? { version: form.version } : {},
|
|
7754
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7755
|
-
...categories.length > 0 ? { categories } : {},
|
|
7756
|
-
repo: repoUrl
|
|
7757
|
-
});
|
|
7758
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
|
|
7759
8011
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7760
8012
|
className: config_manager_module_css_default.hint,
|
|
7761
|
-
children: t("
|
|
8013
|
+
children: t("myconfigs.login.hint")
|
|
8014
|
+
}),
|
|
8015
|
+
loginView.kind === "token-invalid" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
|
|
8016
|
+
kind: "warn",
|
|
8017
|
+
children: t("myconfigs.error.loadStatus")
|
|
7762
8018
|
}),
|
|
8019
|
+
githubView.showCode && renderDeviceCode(),
|
|
8020
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8021
|
+
className: config_manager_module_css_default.statRow,
|
|
8022
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
8023
|
+
kind: githubView.phase === "error" ? "error" : "warn",
|
|
8024
|
+
children: githubView.statusText
|
|
8025
|
+
})
|
|
8026
|
+
}),
|
|
8027
|
+
github.error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
|
|
8028
|
+
kind: "error",
|
|
8029
|
+
children: redact(github.error)
|
|
8030
|
+
})
|
|
8031
|
+
] });
|
|
8032
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
|
|
7763
8033
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7764
8034
|
className: config_manager_module_css_default.actionRow,
|
|
7765
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7773
|
-
className: config_manager_module_css_default.fieldLabel,
|
|
7774
|
-
children: t("publish.submit.snippet")
|
|
7775
|
-
}),
|
|
7776
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
|
|
7777
|
-
className: config_manager_module_css_default.reportText,
|
|
7778
|
-
children: redact(snippet)
|
|
7779
|
-
}),
|
|
7780
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7781
|
-
className: config_manager_module_css_default.hint,
|
|
7782
|
-
children: t("publish.submit.fork", { url: "https://github.com/xiajiajun516/dsh-config-market" })
|
|
7783
|
-
}),
|
|
7784
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7785
|
-
className: config_manager_module_css_default.hint,
|
|
7786
|
-
children: t("publish.submit.manual")
|
|
7787
|
-
})
|
|
7788
|
-
] });
|
|
7789
|
-
};
|
|
7790
|
-
const renderStep = () => {
|
|
7791
|
-
switch (currentStep) {
|
|
7792
|
-
case "select": return renderSelect();
|
|
7793
|
-
case "validate": return renderValidate();
|
|
7794
|
-
case "prepare": return renderForm();
|
|
7795
|
-
case "push": return renderPush();
|
|
7796
|
-
case "submit": return renderSubmit();
|
|
7797
|
-
}
|
|
7798
|
-
};
|
|
7799
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7800
|
-
className: config_manager_module_css_default.viewBody,
|
|
7801
|
-
children: [
|
|
7802
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SectionTitle, {
|
|
7803
|
-
title: t("publish.title"),
|
|
7804
|
-
subtitle: t("publish.subtitle")
|
|
7805
|
-
}),
|
|
7806
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7807
|
-
className: config_manager_module_css_default.actionRow,
|
|
7808
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
7809
|
-
onClick: onBack,
|
|
7810
|
-
children: t("publish.back")
|
|
7811
|
-
})
|
|
8035
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8036
|
+
className: config_manager_module_css_default.groupLabel,
|
|
8037
|
+
children: t("myconfigs.login.title")
|
|
8038
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
8039
|
+
kind: "ok",
|
|
8040
|
+
children: t("myconfigs.login.loggedInAs", { login: loginView.login })
|
|
8041
|
+
})]
|
|
7812
8042
|
}),
|
|
7813
8043
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7814
8044
|
className: config_manager_module_css_default.statRow,
|
|
7815
|
-
children:
|
|
7816
|
-
kind:
|
|
7817
|
-
children:
|
|
7818
|
-
}
|
|
8045
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
8046
|
+
kind: "info",
|
|
8047
|
+
children: t("myconfigs.login.targetRepo", { repo: targetRepo })
|
|
8048
|
+
})
|
|
7819
8049
|
}),
|
|
7820
8050
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7821
8051
|
className: config_manager_module_css_default.statRow,
|
|
7822
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
8052
|
+
children: loginView.repoExists ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
8053
|
+
kind: "ok",
|
|
8054
|
+
children: t("myconfigs.login.repoReady", { repo: loginView.repoUrl })
|
|
8055
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
8056
|
+
kind: "warn",
|
|
8057
|
+
children: t("myconfigs.login.repoMissing")
|
|
8058
|
+
})
|
|
8059
|
+
})
|
|
8060
|
+
] });
|
|
8061
|
+
};
|
|
8062
|
+
/** 上传 / 更新向导卡片(选 zip → 校验 → 表单 → 结果) */
|
|
8063
|
+
const renderWizard = () => {
|
|
8064
|
+
const reset = () => setWizard(initialWizard(wizard.mode));
|
|
8065
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
|
|
8066
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8067
|
+
className: config_manager_module_css_default.actionRow,
|
|
8068
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8069
|
+
className: config_manager_module_css_default.groupLabel,
|
|
8070
|
+
children: wizard.mode === "update" ? t("myconfigs.update.title") : t("myconfigs.upload.title")
|
|
8071
|
+
}), wizard.mode === "update" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
7823
8072
|
kind: "info",
|
|
7824
|
-
children: t("
|
|
7825
|
-
|
|
7826
|
-
|
|
7827
|
-
|
|
8073
|
+
children: t("myconfigs.update.hint")
|
|
8074
|
+
})]
|
|
8075
|
+
}),
|
|
8076
|
+
wizard.step === "select" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
8077
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8078
|
+
className: config_manager_module_css_default.hint,
|
|
8079
|
+
children: t("myconfigs.upload.selectHint")
|
|
8080
|
+
}),
|
|
8081
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
8082
|
+
ref: fileInput,
|
|
8083
|
+
type: "file",
|
|
8084
|
+
accept: ".zip,application/zip",
|
|
8085
|
+
className: config_manager_module_css_default.hiddenFile,
|
|
8086
|
+
onChange: (e) => {
|
|
8087
|
+
const picked = e.target.files?.[0];
|
|
8088
|
+
e.target.value = "";
|
|
8089
|
+
onPickFile(picked);
|
|
8090
|
+
}
|
|
8091
|
+
}),
|
|
8092
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8093
|
+
className: config_manager_module_css_default.actionRow,
|
|
8094
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
8095
|
+
variant: "primary",
|
|
8096
|
+
disabled: wizard.running,
|
|
8097
|
+
onClick: () => {
|
|
8098
|
+
fileInput.current?.click();
|
|
8099
|
+
},
|
|
8100
|
+
children: t("myconfigs.upload.select")
|
|
7828
8101
|
})
|
|
7829
8102
|
})
|
|
7830
|
-
}),
|
|
7831
|
-
|
|
8103
|
+
] }),
|
|
8104
|
+
wizard.step === "validate" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
8105
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8106
|
+
className: config_manager_module_css_default.hint,
|
|
8107
|
+
children: t("myconfigs.upload.selectHint")
|
|
8108
|
+
}),
|
|
8109
|
+
wizard.fileName !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8110
|
+
className: config_manager_module_css_default.statRow,
|
|
8111
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
8112
|
+
kind: "info",
|
|
8113
|
+
children: t("myconfigs.upload.selected", { name: wizard.fileName })
|
|
8114
|
+
})
|
|
8115
|
+
}),
|
|
8116
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8117
|
+
className: config_manager_module_css_default.actionRow,
|
|
8118
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
8119
|
+
variant: "primary",
|
|
8120
|
+
disabled: wizard.zipPath === null || wizard.validating,
|
|
8121
|
+
onClick: () => {
|
|
8122
|
+
runValidate();
|
|
8123
|
+
},
|
|
8124
|
+
children: wizard.validating ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("myconfigs.upload.validating") }) : t("myconfigs.upload.validate")
|
|
8125
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
8126
|
+
disabled: wizard.validating,
|
|
8127
|
+
onClick: reset,
|
|
8128
|
+
children: t("myconfigs.upload.reselect")
|
|
8129
|
+
})]
|
|
8130
|
+
}),
|
|
8131
|
+
wizard.validationError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
|
|
8132
|
+
kind: "error",
|
|
8133
|
+
children: redact(wizard.validationError)
|
|
8134
|
+
})
|
|
8135
|
+
] }),
|
|
8136
|
+
wizard.step === "form" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
8137
|
+
wizard.validated && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8138
|
+
className: config_manager_module_css_default.statRow,
|
|
8139
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
8140
|
+
kind: "ok",
|
|
8141
|
+
children: t("myconfigs.upload.validateOk")
|
|
8142
|
+
})
|
|
8143
|
+
}),
|
|
8144
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Field, {
|
|
8145
|
+
label: t("myconfigs.upload.form.name"),
|
|
8146
|
+
hint: t("myconfigs.upload.form.nameHint"),
|
|
8147
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
8148
|
+
className: config_manager_module_css_default.input,
|
|
8149
|
+
value: wizard.form.name,
|
|
8150
|
+
onChange: (e) => {
|
|
8151
|
+
onFormField("name", e.target.value);
|
|
8152
|
+
}
|
|
8153
|
+
}), wizard.formErrors.name !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8154
|
+
className: config_manager_module_css_default.formError,
|
|
8155
|
+
children: redact(wizard.formErrors.name)
|
|
8156
|
+
})]
|
|
8157
|
+
}),
|
|
8158
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
8159
|
+
label: t("myconfigs.upload.form.description"),
|
|
8160
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
8161
|
+
className: config_manager_module_css_default.input,
|
|
8162
|
+
value: wizard.form.description,
|
|
8163
|
+
onChange: (e) => {
|
|
8164
|
+
onFormField("description", e.target.value);
|
|
8165
|
+
}
|
|
8166
|
+
})
|
|
8167
|
+
}),
|
|
8168
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Field, {
|
|
8169
|
+
label: t("myconfigs.upload.form.categories"),
|
|
8170
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
8171
|
+
className: config_manager_module_css_default.input,
|
|
8172
|
+
value: wizard.form.categories,
|
|
8173
|
+
onChange: (e) => {
|
|
8174
|
+
onFormField("categories", e.target.value);
|
|
8175
|
+
}
|
|
8176
|
+
})
|
|
8177
|
+
}),
|
|
8178
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8179
|
+
className: config_manager_module_css_default.hint,
|
|
8180
|
+
children: t("myconfigs.upload.form.autoHint")
|
|
8181
|
+
}),
|
|
8182
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8183
|
+
className: config_manager_module_css_default.statRow,
|
|
8184
|
+
children: autoBadges.map((b) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Badge, {
|
|
8185
|
+
kind: "info",
|
|
8186
|
+
children: [
|
|
8187
|
+
b.label,
|
|
8188
|
+
":",
|
|
8189
|
+
b.autoText
|
|
8190
|
+
]
|
|
8191
|
+
}, b.field))
|
|
8192
|
+
}),
|
|
8193
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8194
|
+
className: config_manager_module_css_default.actionRow,
|
|
8195
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
8196
|
+
variant: "primary",
|
|
8197
|
+
disabled: wizard.validated !== true || wizard.running || !myConfigFormValid(wizard.formErrors),
|
|
8198
|
+
onClick: () => {
|
|
8199
|
+
runUpload();
|
|
8200
|
+
},
|
|
8201
|
+
children: wizard.running ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: wizard.mode === "update" ? t("myconfigs.update.running") : t("myconfigs.upload.running") }) : wizard.mode === "update" ? t("myconfigs.update.run") : t("myconfigs.upload.run")
|
|
8202
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
8203
|
+
disabled: wizard.running,
|
|
8204
|
+
onClick: reset,
|
|
8205
|
+
children: t("myconfigs.upload.reselect")
|
|
8206
|
+
})]
|
|
8207
|
+
})
|
|
8208
|
+
] }),
|
|
8209
|
+
wizard.error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
|
|
7832
8210
|
kind: "error",
|
|
7833
|
-
children: redact(error)
|
|
8211
|
+
children: redact(wizard.error)
|
|
7834
8212
|
}),
|
|
7835
|
-
|
|
8213
|
+
wizard.result !== null && (wizard.result.ok ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
8214
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8215
|
+
className: config_manager_module_css_default.groupLabel,
|
|
8216
|
+
children: t("myconfigs.result.title")
|
|
8217
|
+
}),
|
|
8218
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8219
|
+
className: config_manager_module_css_default.statRow,
|
|
8220
|
+
children: [
|
|
8221
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
8222
|
+
kind: "ok",
|
|
8223
|
+
children: t("myconfigs.result.version", { version: wizard.result.version })
|
|
8224
|
+
}),
|
|
8225
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
8226
|
+
kind: "info",
|
|
8227
|
+
children: t("myconfigs.result.sha256", { hash: wizard.result.sha256 })
|
|
8228
|
+
}),
|
|
8229
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
8230
|
+
kind: "info",
|
|
8231
|
+
children: t("myconfigs.result.sections", { sections: wizard.result.sections.join(", ") })
|
|
8232
|
+
})
|
|
8233
|
+
]
|
|
8234
|
+
}),
|
|
8235
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8236
|
+
className: config_manager_module_css_default.actionRow,
|
|
8237
|
+
children: [
|
|
8238
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
8239
|
+
kind: "info",
|
|
8240
|
+
children: t("myconfigs.result.repo")
|
|
8241
|
+
}),
|
|
8242
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
8243
|
+
href: wizard.result.repoUrl,
|
|
8244
|
+
children: t("myconfigs.result.openRepo")
|
|
8245
|
+
}),
|
|
8246
|
+
wizard.result.prUrl !== null && wizard.result.prUrl !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
8247
|
+
href: wizard.result.prUrl,
|
|
8248
|
+
children: wizard.result.prNumber !== null ? t("myconfigs.result.pr", { number: String(wizard.result.prNumber) }) : t("myconfigs.result.openPr")
|
|
8249
|
+
})
|
|
8250
|
+
]
|
|
8251
|
+
})
|
|
8252
|
+
] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
|
|
8253
|
+
kind: "error",
|
|
8254
|
+
children: redact(wizard.result.error ?? ((wizard.result.warnings ?? []).join(" · ") || t("common.unknownError")))
|
|
8255
|
+
}))
|
|
8256
|
+
] });
|
|
8257
|
+
};
|
|
8258
|
+
/** 已上传列表(条目卡片 + 状态徽章 + 行操作) */
|
|
8259
|
+
const renderList = () => {
|
|
8260
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
|
|
7836
8261
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7837
8262
|
className: config_manager_module_css_default.actionRow,
|
|
7838
|
-
children: [
|
|
7839
|
-
|
|
7840
|
-
|
|
7841
|
-
|
|
7842
|
-
|
|
7843
|
-
|
|
7844
|
-
|
|
7845
|
-
|
|
7846
|
-
|
|
7847
|
-
|
|
7848
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
|
|
7852
|
-
|
|
7853
|
-
|
|
7854
|
-
|
|
7855
|
-
|
|
7856
|
-
|
|
7857
|
-
|
|
7858
|
-
|
|
7859
|
-
|
|
7860
|
-
|
|
7861
|
-
|
|
7862
|
-
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
7867
|
-
|
|
7868
|
-
|
|
7869
|
-
|
|
7870
|
-
|
|
7871
|
-
|
|
7872
|
-
|
|
7873
|
-
|
|
7874
|
-
}
|
|
7875
|
-
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
|
|
7879
|
-
|
|
7880
|
-
|
|
7881
|
-
|
|
7882
|
-
|
|
7883
|
-
|
|
7884
|
-
|
|
7885
|
-
|
|
7886
|
-
|
|
7887
|
-
|
|
7888
|
-
|
|
7889
|
-
|
|
7890
|
-
|
|
7891
|
-
|
|
7892
|
-
|
|
7893
|
-
|
|
7894
|
-
|
|
7895
|
-
|
|
7896
|
-
|
|
7897
|
-
|
|
7898
|
-
|
|
7899
|
-
|
|
7900
|
-
|
|
7901
|
-
|
|
7902
|
-
|
|
7903
|
-
|
|
7904
|
-
|
|
7905
|
-
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
|
|
7909
|
-
|
|
7910
|
-
*/
|
|
7911
|
-
|
|
7912
|
-
|
|
7913
|
-
|
|
7914
|
-
|
|
7915
|
-
|
|
7916
|
-
|
|
7917
|
-
|
|
7918
|
-
|
|
7919
|
-
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
|
|
7923
|
-
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
|
|
7930
|
-
|
|
7931
|
-
|
|
7932
|
-
|
|
7933
|
-
|
|
7934
|
-
|
|
7935
|
-
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
if ((it.author ?? "").toLowerCase().includes(q)) return true;
|
|
7940
|
-
if ((it.description ?? "").toLowerCase().includes(q)) return true;
|
|
7941
|
-
return false;
|
|
7942
|
-
});
|
|
7943
|
-
}
|
|
7944
|
-
/**
|
|
7945
|
-
* 供应链警示 → warn/info 着色行(客户端专属)。**逻辑委托** Host 权威 `marketItemWarnings`
|
|
7946
|
-
* (恒生成非官方审核 + 来源 URL + 下载时间 + 作者/来源自述),本层只负责给「非官方审核」标 warn、
|
|
7947
|
-
* 其余标 info,供 UI 列表着色。硬不变式:返回恒非空(至少一条),确认导入前必经。
|
|
7948
|
-
*/
|
|
7949
|
-
function marketWarningsLines(detail, url, t = zhUiT) {
|
|
7950
|
-
return marketItemWarnings(detail.provenance !== void 0 || detail.author !== void 0 ? {
|
|
7951
|
-
name: detail.name,
|
|
7952
|
-
author: detail.author,
|
|
7953
|
-
provenance: detail.provenance
|
|
7954
|
-
} : void 0, url, detail.downloadedAt, t).map((text, i) => ({
|
|
7955
|
-
kind: i === 0 ? "warn" : "info",
|
|
7956
|
-
text
|
|
7957
|
-
}));
|
|
7958
|
-
}
|
|
7959
|
-
/**
|
|
7960
|
-
* 聚合详情视图(客户端专属):徽章(委托共享 computeItemBadge)+ 供应链警示着色行
|
|
7961
|
-
* (委托共享 marketItemWarnings)+ 校验错误 + 可否导入。
|
|
7962
|
-
*/
|
|
7963
|
-
function marketDetailView(detail, url, showBack, t = zhUiT) {
|
|
7964
|
-
const badge = computeItemBadge(detail, t);
|
|
7965
|
-
return {
|
|
7966
|
-
badge: {
|
|
7967
|
-
statusKind: badge.valid ? "ok" : "error",
|
|
7968
|
-
statusText: badge.statusText,
|
|
7969
|
-
sectionsText: badge.sectionsText,
|
|
7970
|
-
valid: badge.valid
|
|
7971
|
-
},
|
|
7972
|
-
warnings: marketWarningsLines(detail, url, t),
|
|
7973
|
-
errors: detail.errors ?? [],
|
|
7974
|
-
canImport: detail.status === "valid" && detail.sections.length > 0,
|
|
7975
|
-
showBack
|
|
7976
|
-
};
|
|
7977
|
-
}
|
|
7978
|
-
/**
|
|
7979
|
-
* 高风险分区(默认不导入,须逐项显式批准):
|
|
7980
|
-
* - pluginFiles :把远端文件写进 $DSH_HOME(可覆盖本机插件配置文件)
|
|
7981
|
-
* - agentInstructions :AGENTS.md 注入每个会话的全局指令(LLM 言行边界)
|
|
7982
|
-
* - agentPresets :agent 预设(会话 persona / 行为模板)
|
|
7983
|
-
* - sessions :session 文件(可带历史/敏感上下文)
|
|
7984
|
-
* - mcp :注册 MCP 服务器(可接入外部工具/执行能力)
|
|
7985
|
-
* - plugins :安装/更新插件(ExecutePlugin / Install 项,供应链最高风险)
|
|
7986
|
-
* 其余(settings/ui/providers/prompts/skills/workspaces/credentialsStatus…)默认勾选。
|
|
7987
|
-
*/
|
|
7988
|
-
const HIGH_RISK_ADAPTERS = /* @__PURE__ */ new Set([
|
|
7989
|
-
"pluginFiles",
|
|
7990
|
-
"agentInstructions",
|
|
7991
|
-
"agentPresets",
|
|
7992
|
-
"sessions",
|
|
7993
|
-
"mcp",
|
|
7994
|
-
"plugins"
|
|
7995
|
-
]);
|
|
7996
|
-
/** 是否为高风险分区(需逐项显式批准,默认不导入)。 */
|
|
7997
|
-
function isHighRiskAdapter(adapter) {
|
|
7998
|
-
return HIGH_RISK_ADAPTERS.has(adapter);
|
|
7999
|
-
}
|
|
8000
|
-
/** PlanItemKind 是否需要重启 DSH 生效(Install 及插件级变更)。 */
|
|
8001
|
-
function itemNeedsRestart(adapter, kind) {
|
|
8002
|
-
if (kind === "Install") return true;
|
|
8003
|
-
if (adapter === "plugins" || adapter === "mcp" || adapter === "agentPresets" || adapter === "agentInstructions") return true;
|
|
8004
|
-
return false;
|
|
8005
|
-
}
|
|
8006
|
-
/** 收集计划中出现过的分区(按 APPLY_ORDER 语义排序不重要,去重即可)。 */
|
|
8007
|
-
function planAdapters(plan) {
|
|
8008
|
-
const set = /* @__PURE__ */ new Set();
|
|
8009
|
-
for (const item of plan.items) set.add(item.adapter);
|
|
8010
|
-
return [...set];
|
|
8011
|
-
}
|
|
8012
|
-
/**
|
|
8013
|
-
* 默认批准表:低风险分区默认勾选(true);高风险分区默认不勾选(false,须逐项显式批准)。
|
|
8014
|
-
* 这是严格分层信任的默认 —— 不提供「自动信任高风险来源」的默认。
|
|
8015
|
-
*/
|
|
8016
|
-
function defaultApprovals(plan) {
|
|
8017
|
-
const out = {};
|
|
8018
|
-
for (const adapter of planAdapters(plan)) out[adapter] = !isHighRiskAdapter(adapter);
|
|
8019
|
-
return out;
|
|
8020
|
-
}
|
|
8021
|
-
/**
|
|
8022
|
-
* 按批准表过滤计划 → 仅保留已批准分区的项(供 executeImportPlan 执行的子计划,subPlan)。
|
|
8023
|
-
* - items:仅保留 approved[adapter]===true 的项;
|
|
8024
|
-
* - needsRestart:按已批准项里是否有需重启者重算(不再沿用整份计划的 needsRestart);
|
|
8025
|
-
* - estimatedActions:仅保留已批准分区的计数;
|
|
8026
|
-
* - globalStrategy / missingSecrets / pathMappings:透传(导入的可选分区子集不改变这些)。
|
|
8027
|
-
* 返回与入参同形状的 ImportPlan,可直接交 executeImportPlan(analytic 只执行 plan.items)。
|
|
8028
|
-
*/
|
|
8029
|
-
function buildApprovedPlan(plan, approvals) {
|
|
8030
|
-
const items = plan.items.filter((it) => approvals[it.adapter] === true);
|
|
8031
|
-
let needsRestart = false;
|
|
8032
|
-
const estimatedActions = {};
|
|
8033
|
-
for (const it of items) {
|
|
8034
|
-
if (itemNeedsRestart(it.adapter, it.kind)) needsRestart = true;
|
|
8035
|
-
estimatedActions[it.adapter] = (estimatedActions[it.adapter] ?? 0) + 1;
|
|
8036
|
-
}
|
|
8037
|
-
return {
|
|
8038
|
-
items,
|
|
8039
|
-
globalStrategy: plan.globalStrategy,
|
|
8040
|
-
pathMappings: plan.pathMappings,
|
|
8041
|
-
missingSecrets: plan.missingSecrets,
|
|
8042
|
-
needsRestart,
|
|
8043
|
-
estimatedActions
|
|
8263
|
+
children: [
|
|
8264
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8265
|
+
className: config_manager_module_css_default.groupLabel,
|
|
8266
|
+
children: t("myconfigs.list.title")
|
|
8267
|
+
}),
|
|
8268
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
8269
|
+
disabled: listLoading,
|
|
8270
|
+
onClick: () => {
|
|
8271
|
+
loadItems();
|
|
8272
|
+
},
|
|
8273
|
+
children: listLoading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("myconfigs.list.loading") }) : t("myconfigs.list.refresh")
|
|
8274
|
+
}),
|
|
8275
|
+
myItems !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
8276
|
+
kind: "info",
|
|
8277
|
+
children: t("myconfigs.list.summary", {
|
|
8278
|
+
total: String(summary.total),
|
|
8279
|
+
listed: String(summary.listed),
|
|
8280
|
+
pending: String(summary.pendingPr),
|
|
8281
|
+
none: String(summary.notListed)
|
|
8282
|
+
})
|
|
8283
|
+
})
|
|
8284
|
+
]
|
|
8285
|
+
}),
|
|
8286
|
+
myItemsError !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
|
|
8287
|
+
kind: "error",
|
|
8288
|
+
children: redact(myItemsError)
|
|
8289
|
+
}),
|
|
8290
|
+
listLoading && myItems === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8291
|
+
className: config_manager_module_css_default.statRow,
|
|
8292
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("myconfigs.list.loading") })
|
|
8293
|
+
}),
|
|
8294
|
+
!listLoading && myItems !== null && myItems.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Empty, { children: t("myconfigs.list.empty") }),
|
|
8295
|
+
!listLoading && itemViews.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8296
|
+
className: config_manager_module_css_default.snapshotList,
|
|
8297
|
+
children: itemViews.map(({ entry, view, badge }) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8298
|
+
className: config_manager_module_css_default.statRow,
|
|
8299
|
+
style: { paddingTop: 4 },
|
|
8300
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8301
|
+
style: {
|
|
8302
|
+
flex: 1,
|
|
8303
|
+
minWidth: 0
|
|
8304
|
+
},
|
|
8305
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8306
|
+
className: config_manager_module_css_default.conflictHead,
|
|
8307
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8308
|
+
className: config_manager_module_css_default.conflictId,
|
|
8309
|
+
children: view.name
|
|
8310
|
+
}), view.version !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
8311
|
+
kind: "info",
|
|
8312
|
+
children: view.version
|
|
8313
|
+
})]
|
|
8314
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8315
|
+
className: config_manager_module_css_default.statRow,
|
|
8316
|
+
children: [
|
|
8317
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
8318
|
+
kind: badge.kind,
|
|
8319
|
+
children: badge.text
|
|
8320
|
+
}),
|
|
8321
|
+
view.author !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
8322
|
+
kind: "info",
|
|
8323
|
+
children: view.author
|
|
8324
|
+
}),
|
|
8325
|
+
view.updatedAt !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
8326
|
+
kind: "info",
|
|
8327
|
+
children: view.updatedAt
|
|
8328
|
+
}),
|
|
8329
|
+
view.categories.map((c) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
8330
|
+
kind: "info",
|
|
8331
|
+
children: c
|
|
8332
|
+
}, c))
|
|
8333
|
+
]
|
|
8334
|
+
})]
|
|
8335
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8336
|
+
className: config_manager_module_css_default.rowActions,
|
|
8337
|
+
children: [
|
|
8338
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
8339
|
+
onClick: () => {
|
|
8340
|
+
startUpdate(entry);
|
|
8341
|
+
},
|
|
8342
|
+
children: t("myconfigs.item.update")
|
|
8343
|
+
}),
|
|
8344
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
8345
|
+
disabled: install !== null && install.itemId === view.id && install.detail === null,
|
|
8346
|
+
onClick: () => {
|
|
8347
|
+
runDownload(entry);
|
|
8348
|
+
},
|
|
8349
|
+
children: t("myconfigs.item.install")
|
|
8350
|
+
}),
|
|
8351
|
+
entry.repoUrl !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
8352
|
+
href: entry.repoUrl,
|
|
8353
|
+
children: t("myconfigs.list.openRepo")
|
|
8354
|
+
}),
|
|
8355
|
+
badge.kind === "warn" && badge.prUrl !== void 0 && badge.prUrl !== "" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
8356
|
+
href: badge.prUrl,
|
|
8357
|
+
children: t("myconfigs.item.openPr")
|
|
8358
|
+
})
|
|
8359
|
+
]
|
|
8360
|
+
})]
|
|
8361
|
+
}, view.id))
|
|
8362
|
+
})
|
|
8363
|
+
] });
|
|
8044
8364
|
};
|
|
8045
|
-
|
|
8046
|
-
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8065
|
-
|
|
8365
|
+
/** 装回本地:下载 + 逐分区批准 + 执行导入(复用市场安全管道 + market-view 纯模型) */
|
|
8366
|
+
const renderInstall = () => {
|
|
8367
|
+
if (install === null) return null;
|
|
8368
|
+
const { detail } = install;
|
|
8369
|
+
const approvalList = detail !== null ? approvalRows(detail.plan, install.approvals) : [];
|
|
8370
|
+
const approvalSummary = detail !== null ? approvedAdapterSummary(detail.plan, install.approvals) : null;
|
|
8371
|
+
const detailView = detail !== null ? marketDetailView(detail, detail.repo ?? entryRepoUrl(install.itemId), true, uiT) : null;
|
|
8372
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Card, { children: [
|
|
8373
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8374
|
+
className: config_manager_module_css_default.actionRow,
|
|
8375
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
8376
|
+
className: config_manager_module_css_default.groupLabel,
|
|
8377
|
+
children: [
|
|
8378
|
+
t("detail.title"),
|
|
8379
|
+
":",
|
|
8380
|
+
install.itemId
|
|
8381
|
+
]
|
|
8382
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
8383
|
+
onClick: () => {
|
|
8384
|
+
setInstall(null);
|
|
8385
|
+
},
|
|
8386
|
+
children: t("detail.back")
|
|
8387
|
+
})]
|
|
8388
|
+
}),
|
|
8389
|
+
install.error !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
|
|
8390
|
+
kind: "error",
|
|
8391
|
+
children: redact(install.error)
|
|
8392
|
+
}),
|
|
8393
|
+
detail === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8394
|
+
className: config_manager_module_css_default.statRow,
|
|
8395
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("list.loading") })
|
|
8396
|
+
}),
|
|
8397
|
+
detail !== null && detailView !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
8398
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
|
|
8399
|
+
kind: "warn",
|
|
8400
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("detail.needReview") })
|
|
8401
|
+
}),
|
|
8402
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8403
|
+
className: config_manager_module_css_default.statRow,
|
|
8404
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
8405
|
+
kind: detailView.badge.valid ? "ok" : "error",
|
|
8406
|
+
children: detailView.badge.statusText
|
|
8407
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
8408
|
+
kind: "info",
|
|
8409
|
+
children: detailView.badge.sectionsText
|
|
8410
|
+
})]
|
|
8411
|
+
}),
|
|
8412
|
+
approvalList.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
|
|
8413
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8414
|
+
className: config_manager_module_css_default.groupLabel,
|
|
8415
|
+
children: t("detail.approval.title")
|
|
8416
|
+
}),
|
|
8417
|
+
approvalSummary !== null && approvalSummary.highRiskTotal > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Banner, {
|
|
8418
|
+
kind: "warn",
|
|
8419
|
+
children: t("detail.approval.highRiskHint")
|
|
8420
|
+
}),
|
|
8421
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8422
|
+
className: config_manager_module_css_default.conflictList,
|
|
8423
|
+
children: approvalList.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Checkbox, {
|
|
8424
|
+
checked: row.approved,
|
|
8425
|
+
onChange: (checked) => {
|
|
8426
|
+
setInstall((i) => i !== null ? {
|
|
8427
|
+
...i,
|
|
8428
|
+
approvals: {
|
|
8429
|
+
...i.approvals,
|
|
8430
|
+
[row.adapter]: checked
|
|
8431
|
+
}
|
|
8432
|
+
} : i);
|
|
8433
|
+
},
|
|
8434
|
+
label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
8435
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8436
|
+
className: config_manager_module_css_default.conflictId,
|
|
8437
|
+
children: row.adapter
|
|
8438
|
+
}),
|
|
8439
|
+
" ",
|
|
8440
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
8441
|
+
kind: row.highRisk ? "warn" : "info",
|
|
8442
|
+
children: row.highRisk ? t("detail.approval.requiresApproval") : t("detail.approval.safe")
|
|
8443
|
+
}),
|
|
8444
|
+
" ",
|
|
8445
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
8446
|
+
kind: "info",
|
|
8447
|
+
children: row.label
|
|
8448
|
+
})
|
|
8449
|
+
] })
|
|
8450
|
+
}, row.adapter))
|
|
8451
|
+
}),
|
|
8452
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8453
|
+
className: config_manager_module_css_default.statRow,
|
|
8454
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Badge, {
|
|
8455
|
+
kind: approvalSummary !== null && approvalSummary.canImport ? "ok" : "warn",
|
|
8456
|
+
children: approvalSummary !== null ? t("detail.approval.count", {
|
|
8457
|
+
selected: String(approvalSummary.selected),
|
|
8458
|
+
total: String(approvalSummary.total)
|
|
8459
|
+
}) : ""
|
|
8460
|
+
})
|
|
8461
|
+
})
|
|
8462
|
+
] }),
|
|
8463
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8464
|
+
className: config_manager_module_css_default.actionRow,
|
|
8465
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
8466
|
+
variant: "primary",
|
|
8467
|
+
disabled: install.importing || approvalSummary !== null && !approvalSummary.canImport,
|
|
8468
|
+
onClick: () => {
|
|
8469
|
+
runImport();
|
|
8470
|
+
},
|
|
8471
|
+
children: install.importing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("common.loading") }) : t("detail.import")
|
|
8472
|
+
})
|
|
8473
|
+
})
|
|
8474
|
+
] }),
|
|
8475
|
+
install.importResult !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Banner, {
|
|
8476
|
+
kind: install.importResult.ok ? "ok" : "error",
|
|
8477
|
+
children: [install.importResult.ok ? `导入完成:${install.importResult.executed.filter((e) => e.status === "ok").length} 项写入` : `导入失败(${install.importResult.executed.filter((e) => e.status === "failed").length} 项失败)`, install.importResult.needsRestart && " · 部分改动需重启 DSH 后生效"]
|
|
8478
|
+
})
|
|
8479
|
+
] });
|
|
8066
8480
|
};
|
|
8067
|
-
|
|
8068
|
-
|
|
8069
|
-
|
|
8070
|
-
const byAdapter = /* @__PURE__ */ new Map();
|
|
8071
|
-
for (const item of plan.items) {
|
|
8072
|
-
const list = byAdapter.get(item.adapter) ?? [];
|
|
8073
|
-
list.push(item);
|
|
8074
|
-
byAdapter.set(item.adapter, list);
|
|
8075
|
-
}
|
|
8076
|
-
const rows = [];
|
|
8077
|
-
for (const adapter of planAdapters(plan)) {
|
|
8078
|
-
const items = byAdapter.get(adapter) ?? [];
|
|
8079
|
-
const label = items[0]?.description ?? adapter;
|
|
8080
|
-
rows.push({
|
|
8081
|
-
adapter,
|
|
8082
|
-
itemCount: items.length,
|
|
8083
|
-
highRisk: isHighRiskAdapter(adapter),
|
|
8084
|
-
approved: approvals[adapter] === true,
|
|
8085
|
-
label
|
|
8086
|
-
});
|
|
8481
|
+
/** 装回本地详情展示用的仓库 URL(供应链警示来源行;取条目 repoUrl 兜底固定目标仓库) */
|
|
8482
|
+
function entryRepoUrl(itemId) {
|
|
8483
|
+
return (myItems ?? []).find((e) => e.id === itemId)?.repoUrl ?? `https://github.com/xiajiajun516/dsh-config-market`;
|
|
8087
8484
|
}
|
|
8088
|
-
return
|
|
8485
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8486
|
+
className: config_manager_module_css_default.viewBody,
|
|
8487
|
+
children: [
|
|
8488
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8489
|
+
className: config_manager_module_css_default.actionRow,
|
|
8490
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
8491
|
+
onClick: onBack,
|
|
8492
|
+
children: t("myconfigs.back")
|
|
8493
|
+
})
|
|
8494
|
+
}),
|
|
8495
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SectionTitle, {
|
|
8496
|
+
title: t("myconfigs.tab.myconfigs"),
|
|
8497
|
+
subtitle: t("myconfigs.login.hint")
|
|
8498
|
+
}),
|
|
8499
|
+
renderLoginCard(),
|
|
8500
|
+
loginView.kind === "logged-in" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
8501
|
+
renderWizard(),
|
|
8502
|
+
renderList(),
|
|
8503
|
+
renderInstall()
|
|
8504
|
+
] })
|
|
8505
|
+
]
|
|
8506
|
+
});
|
|
8089
8507
|
}
|
|
8090
8508
|
//#endregion
|
|
8091
8509
|
//#region src/client/market/MarketPanel.tsx
|
|
@@ -8110,6 +8528,9 @@ function approvalRows(plan, approvals) {
|
|
|
8110
8528
|
* 内容一律视为不可信,确认导入前 supply-chain 警示可见 & needsReview 恒 true(不允许默认信任)。
|
|
8111
8529
|
*/
|
|
8112
8530
|
const initial$1 = {
|
|
8531
|
+
subView: "browse",
|
|
8532
|
+
myItems: null,
|
|
8533
|
+
myItemsError: null,
|
|
8113
8534
|
loading: true,
|
|
8114
8535
|
loadError: null,
|
|
8115
8536
|
market: null,
|
|
@@ -8134,6 +8555,9 @@ function initFromStore() {
|
|
|
8134
8555
|
const s = runStore.getSnapshot().market;
|
|
8135
8556
|
return {
|
|
8136
8557
|
...initial$1,
|
|
8558
|
+
subView: s.subView,
|
|
8559
|
+
myItems: s.myItems,
|
|
8560
|
+
myItemsError: s.myItemsError,
|
|
8137
8561
|
search: s.search,
|
|
8138
8562
|
category: s.category,
|
|
8139
8563
|
items: s.items,
|
|
@@ -8144,10 +8568,8 @@ function initFromStore() {
|
|
|
8144
8568
|
loadError: s.loadError
|
|
8145
8569
|
};
|
|
8146
8570
|
}
|
|
8147
|
-
function MarketPanel({ api, importApi, t }) {
|
|
8571
|
+
function MarketPanel({ api, myConfigsApi, importApi, syncApi, t }) {
|
|
8148
8572
|
const uiT = api.t;
|
|
8149
|
-
/** 发布向导开关(组件内状态,不进 sessionStorage —— 发布为一次性低频率流程) */
|
|
8150
|
-
const [publishOpen, setPublishOpen] = (0, react.useState)(false);
|
|
8151
8573
|
const [state, setState] = (0, react.useState)(initFromStore);
|
|
8152
8574
|
/** 最新 state 镜像(commit/卸载 flush 读取,避免闭包过期值) */
|
|
8153
8575
|
const stateRef = (0, react.useRef)(state);
|
|
@@ -8243,14 +8665,14 @@ function MarketPanel({ api, importApi, t }) {
|
|
|
8243
8665
|
});
|
|
8244
8666
|
}
|
|
8245
8667
|
};
|
|
8246
|
-
/** 下载 + 校验单条目 → dry-run
|
|
8247
|
-
const runDownload = async (
|
|
8668
|
+
/** 下载 + 校验单条目 → dry-run 详情预览(零写入)。自托管条目(带 repo)必须携带来源仓库。 */
|
|
8669
|
+
const runDownload = async (item) => {
|
|
8248
8670
|
patch({
|
|
8249
|
-
downloadingId:
|
|
8671
|
+
downloadingId: item.id,
|
|
8250
8672
|
error: null
|
|
8251
8673
|
});
|
|
8252
8674
|
try {
|
|
8253
|
-
const detail = await api.download(
|
|
8675
|
+
const detail = await api.download(item.id, item.repo);
|
|
8254
8676
|
patch({
|
|
8255
8677
|
downloadingId: null,
|
|
8256
8678
|
detail,
|
|
@@ -8296,7 +8718,7 @@ function MarketPanel({ api, importApi, t }) {
|
|
|
8296
8718
|
const filtered = filterMarketItems(state.items, state.search, state.category);
|
|
8297
8719
|
const summary = marketListSummary(state.items, uiT);
|
|
8298
8720
|
const categories = collectCategories(state.items);
|
|
8299
|
-
const detailView = state.detail !== null ? marketDetailView(state.detail, marketUrl, state.items.length > 0 || state.detail.status !== "valid", uiT) : null;
|
|
8721
|
+
const detailView = state.detail !== null ? marketDetailView(state.detail, state.detail.repo ?? marketUrl, state.items.length > 0 || state.detail.status !== "valid", uiT) : null;
|
|
8300
8722
|
const approvalList = state.detail !== null ? approvalRows(state.detail.plan, state.approvals) : [];
|
|
8301
8723
|
const approvalSummary = state.detail !== null ? approvedAdapterSummary(state.detail.plan, state.approvals) : null;
|
|
8302
8724
|
const cacheLabel = (cacheState) => {
|
|
@@ -8304,14 +8726,48 @@ function MarketPanel({ api, importApi, t }) {
|
|
|
8304
8726
|
if (cacheState === "fresh") return t("list.cacheFresh");
|
|
8305
8727
|
return t("list.cacheNone");
|
|
8306
8728
|
};
|
|
8307
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.
|
|
8729
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8308
8730
|
className: config_manager_module_css_default.viewBody,
|
|
8309
|
-
children:
|
|
8731
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8732
|
+
className: config_manager_module_css_default.modeTabs,
|
|
8733
|
+
role: "tablist",
|
|
8734
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
8735
|
+
type: "button",
|
|
8736
|
+
role: "tab",
|
|
8737
|
+
"aria-selected": state.subView === "browse",
|
|
8738
|
+
"data-active": state.subView === "browse" ? "" : void 0,
|
|
8739
|
+
className: config_manager_module_css_default.modeTab,
|
|
8740
|
+
onClick: () => {
|
|
8741
|
+
patch({ subView: "browse" });
|
|
8742
|
+
},
|
|
8743
|
+
children: t("myconfigs.tab.browse")
|
|
8744
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
8745
|
+
type: "button",
|
|
8746
|
+
role: "tab",
|
|
8747
|
+
"aria-selected": state.subView === "myconfigs",
|
|
8748
|
+
"data-active": state.subView === "myconfigs" ? "" : void 0,
|
|
8749
|
+
className: config_manager_module_css_default.modeTab,
|
|
8750
|
+
onClick: () => {
|
|
8751
|
+
patch({ subView: "myconfigs" });
|
|
8752
|
+
},
|
|
8753
|
+
children: t("myconfigs.tab.myconfigs")
|
|
8754
|
+
})]
|
|
8755
|
+
}), state.subView === "myconfigs" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MyConfigsView, {
|
|
8756
|
+
meApi: myConfigsApi,
|
|
8310
8757
|
api,
|
|
8311
8758
|
importApi,
|
|
8759
|
+
syncApi,
|
|
8312
8760
|
t,
|
|
8761
|
+
myItems: state.myItems,
|
|
8762
|
+
myItemsError: state.myItemsError,
|
|
8763
|
+
onMyItemsChange: (items, error) => {
|
|
8764
|
+
patch({
|
|
8765
|
+
myItems: items,
|
|
8766
|
+
myItemsError: error
|
|
8767
|
+
});
|
|
8768
|
+
},
|
|
8313
8769
|
onBack: () => {
|
|
8314
|
-
|
|
8770
|
+
patch({ subView: "browse" });
|
|
8315
8771
|
}
|
|
8316
8772
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
8317
8773
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SectionTitle, {
|
|
@@ -8357,29 +8813,20 @@ function MarketPanel({ api, importApi, t }) {
|
|
|
8357
8813
|
}),
|
|
8358
8814
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8359
8815
|
className: config_manager_module_css_default.actionRow,
|
|
8360
|
-
children: [
|
|
8361
|
-
|
|
8362
|
-
|
|
8363
|
-
|
|
8364
|
-
|
|
8365
|
-
|
|
8366
|
-
|
|
8367
|
-
|
|
8368
|
-
|
|
8369
|
-
|
|
8370
|
-
|
|
8371
|
-
|
|
8372
|
-
|
|
8373
|
-
|
|
8374
|
-
children: state.browsing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("list.loading") }) : t("list.browse")
|
|
8375
|
-
}),
|
|
8376
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
8377
|
-
onClick: () => {
|
|
8378
|
-
setPublishOpen(true);
|
|
8379
|
-
},
|
|
8380
|
-
children: t("publish.title")
|
|
8381
|
-
})
|
|
8382
|
-
]
|
|
8816
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
8817
|
+
variant: "primary",
|
|
8818
|
+
disabled: state.refreshing || state.importing,
|
|
8819
|
+
onClick: () => {
|
|
8820
|
+
runRefresh();
|
|
8821
|
+
},
|
|
8822
|
+
children: state.refreshing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("config.refreshing") }) : t("config.refresh")
|
|
8823
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
8824
|
+
disabled: state.browsing,
|
|
8825
|
+
onClick: () => {
|
|
8826
|
+
runBrowse();
|
|
8827
|
+
},
|
|
8828
|
+
children: state.browsing ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("list.loading") }) : t("list.browse")
|
|
8829
|
+
})]
|
|
8383
8830
|
}),
|
|
8384
8831
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8385
8832
|
className: config_manager_module_css_default.statRow,
|
|
@@ -8613,7 +9060,7 @@ function MarketPanel({ api, importApi, t }) {
|
|
|
8613
9060
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
8614
9061
|
disabled: state.downloadingId === it.id,
|
|
8615
9062
|
onClick: () => {
|
|
8616
|
-
runDownload(it
|
|
9063
|
+
runDownload(it);
|
|
8617
9064
|
},
|
|
8618
9065
|
children: state.downloadingId === it.id ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spinner, { label: t("common.loading") }) : t("list.download")
|
|
8619
9066
|
})]
|
|
@@ -8621,7 +9068,7 @@ function MarketPanel({ api, importApi, t }) {
|
|
|
8621
9068
|
})
|
|
8622
9069
|
})
|
|
8623
9070
|
] })
|
|
8624
|
-
] })
|
|
9071
|
+
] })]
|
|
8625
9072
|
});
|
|
8626
9073
|
}
|
|
8627
9074
|
//#endregion
|
|
@@ -8836,7 +9283,7 @@ function AboutPanel({ api, t }) {
|
|
|
8836
9283
|
* 所有 tab(主视图 view + 低频面板 panel)状态都在模块级 store(切 tab/刷新不丢);
|
|
8837
9284
|
* 面板内部状态由各视图镜像进 store(Sync/Market/Snapshots),敏感字段白名单剔除。
|
|
8838
9285
|
*/
|
|
8839
|
-
function ConfigManagerSection({ api, syncApi, syncT, marketApi, marketT, t }) {
|
|
9286
|
+
function ConfigManagerSection({ api, syncApi, syncT, marketApi, myConfigsApi, marketT, t }) {
|
|
8840
9287
|
const state = (0, react.useSyncExternalStore)(runStore.subscribe, runStore.getSnapshot);
|
|
8841
9288
|
const view = state.view;
|
|
8842
9289
|
const panel = state.panel;
|
|
@@ -8941,6 +9388,8 @@ function ConfigManagerSection({ api, syncApi, syncT, marketApi, marketT, t }) {
|
|
|
8941
9388
|
t
|
|
8942
9389
|
}) : panel === "market" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MarketPanel, {
|
|
8943
9390
|
api: marketApi,
|
|
9391
|
+
myConfigsApi,
|
|
9392
|
+
syncApi,
|
|
8944
9393
|
importApi: api,
|
|
8945
9394
|
t: marketT
|
|
8946
9395
|
}) : panel === "sync" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SyncSettingsView, {
|
|
@@ -9595,7 +10044,7 @@ const MARKET_API = {
|
|
|
9595
10044
|
/** 市场请求超时(ms):git 拉取可能较慢,与 Host 半 ROUTE_TIMEOUT_MS 对齐量级 */
|
|
9596
10045
|
const MARKET_TIMEOUT_MS = 300 * 1e3;
|
|
9597
10046
|
/** 解析 JSON 响应;非 2xx 时抛出带路由 error 消息的 ConfigManagerApiError(与 api.ts 同款) */
|
|
9598
|
-
async function readJson(response, t) {
|
|
10047
|
+
async function readJson$1(response, t) {
|
|
9599
10048
|
const notMountedMessage = t("error.notMounted");
|
|
9600
10049
|
let body;
|
|
9601
10050
|
try {
|
|
@@ -9608,11 +10057,11 @@ async function readJson(response, t) {
|
|
|
9608
10057
|
return body;
|
|
9609
10058
|
}
|
|
9610
10059
|
/** POST JSON 请求(带超时:宿主卡死时 UI 拿到明确错误而不是永远转圈) */
|
|
9611
|
-
async function postJson(path, body, t) {
|
|
10060
|
+
async function postJson$1(path, body, t) {
|
|
9612
10061
|
const controller = new AbortController();
|
|
9613
10062
|
const timer = setTimeout(() => controller.abort(), MARKET_TIMEOUT_MS);
|
|
9614
10063
|
try {
|
|
9615
|
-
return await readJson(await fetch(path, {
|
|
10064
|
+
return await readJson$1(await fetch(path, {
|
|
9616
10065
|
method: "POST",
|
|
9617
10066
|
headers: { "content-type": "application/json" },
|
|
9618
10067
|
body: JSON.stringify(body),
|
|
@@ -9633,25 +10082,31 @@ var MarketApi = class {
|
|
|
9633
10082
|
}
|
|
9634
10083
|
/** 读取内置市场摘要(条目数 / 最近拉取时间;无任何凭据) */
|
|
9635
10084
|
async status() {
|
|
9636
|
-
return readJson(await fetch(MARKET_API.status), this.t);
|
|
10085
|
+
return readJson$1(await fetch(MARKET_API.status), this.t);
|
|
9637
10086
|
}
|
|
9638
10087
|
/** 拉取市场最新 index.json(内置单市场;返回目录条目 + 市场缓存摘要) */
|
|
9639
10088
|
async refresh() {
|
|
9640
|
-
return postJson(MARKET_API.refresh, {}, this.t);
|
|
10089
|
+
return postJson$1(MARKET_API.refresh, {}, this.t);
|
|
9641
10090
|
}
|
|
9642
10091
|
/** 浏览内置市场(合并 index + 本地缓存状态 → 条目列表带 cacheState) */
|
|
9643
10092
|
async browse() {
|
|
9644
|
-
return postJson(MARKET_API.browse, {}, this.t);
|
|
10093
|
+
return postJson$1(MARKET_API.browse, {}, this.t);
|
|
9645
10094
|
}
|
|
9646
10095
|
/** 下载 + 校验单条目(dry-run 预览:拉取 → §6 校验 → analyzeImport → createImportPlan)。
|
|
10096
|
+
* repo 可选:条目来源仓库(作者自托管)。自托管条目(官方 index 带 repo 引用、或「我的配置」
|
|
10097
|
+
* 未收录条目)内容文件在作者自己的公开仓库,必须显式传 repo 才能从正确来源拉取;
|
|
10098
|
+
* 缺省 = 市场仓库(官方同仓条目)。
|
|
9647
10099
|
* 真正落盘由用户对预览确认后走现有 executeImportPlan(confirm:true 安全阀 + 回滚)。 */
|
|
9648
|
-
async download(itemId) {
|
|
9649
|
-
return postJson(MARKET_API.download, {
|
|
10100
|
+
async download(itemId, repo) {
|
|
10101
|
+
return postJson$1(MARKET_API.download, {
|
|
10102
|
+
itemId,
|
|
10103
|
+
...repo !== void 0 && repo !== "" ? { repo } : {}
|
|
10104
|
+
}, this.t);
|
|
9650
10105
|
}
|
|
9651
10106
|
/** 发布向导:由「上传 zip + 用户填写元数据」生成市场条目包(L2 manifest + SHA-256 + sections),
|
|
9652
10107
|
* 供 UI 展示/复制与引导推送。零写入配置(发布目录在受控临时区);不含任何凭据字段。 */
|
|
9653
10108
|
async prepare(payload) {
|
|
9654
|
-
return postJson(MARKET_API.prepare, payload, this.t);
|
|
10109
|
+
return postJson$1(MARKET_API.prepare, payload, this.t);
|
|
9655
10110
|
}
|
|
9656
10111
|
/** 发布包下载 URL(受控临时区 zip;经宿主 /download 端点,无凭据、无写操作) */
|
|
9657
10112
|
downloadPublishUrl(zipPath) {
|
|
@@ -9659,6 +10114,75 @@ var MarketApi = class {
|
|
|
9659
10114
|
}
|
|
9660
10115
|
};
|
|
9661
10116
|
//#endregion
|
|
10117
|
+
//#region src/client/market/my-configs-api.ts
|
|
10118
|
+
/** 「我的配置」端点常量(与 Host 半 API 常量保持一致) */
|
|
10119
|
+
const MY_CONFIGS_API = {
|
|
10120
|
+
base: "/api/dsh-config-manager/me",
|
|
10121
|
+
status: "/api/dsh-config-manager/me/status",
|
|
10122
|
+
upload: "/api/dsh-config-manager/me/upload",
|
|
10123
|
+
items: "/api/dsh-config-manager/me/items",
|
|
10124
|
+
update: "/api/dsh-config-manager/me/update"
|
|
10125
|
+
};
|
|
10126
|
+
/** 「我的配置」请求超时(ms):上传/更新含 git clone/push/fork/PR,与 market/sync 对齐量级 */
|
|
10127
|
+
const MY_CONFIGS_TIMEOUT_MS = 300 * 1e3;
|
|
10128
|
+
/** 解析 JSON 响应;非 2xx 时抛出带路由 error 消息的 ConfigManagerApiError(与 api.ts 同款) */
|
|
10129
|
+
async function readJson(response, t) {
|
|
10130
|
+
const notMountedMessage = t("error.notMounted");
|
|
10131
|
+
let body;
|
|
10132
|
+
try {
|
|
10133
|
+
body = await response.json();
|
|
10134
|
+
} catch {
|
|
10135
|
+
if (response.status === 404) throw new ConfigManagerApiError(notMountedMessage);
|
|
10136
|
+
throw new ConfigManagerApiError(t("error.httpInvalidJson", { status: String(response.status) }));
|
|
10137
|
+
}
|
|
10138
|
+
if (!response.ok) throw new ConfigManagerApiError(typeof body === "object" && body !== null && typeof body.error === "string" ? body.error : response.status === 404 ? notMountedMessage : `HTTP ${response.status}`);
|
|
10139
|
+
return body;
|
|
10140
|
+
}
|
|
10141
|
+
/** POST JSON 请求(带超时:宿主卡死时 UI 拿到明确错误而不是永远转圈) */
|
|
10142
|
+
async function postJson(path, body, t) {
|
|
10143
|
+
const controller = new AbortController();
|
|
10144
|
+
const timer = setTimeout(() => controller.abort(), MY_CONFIGS_TIMEOUT_MS);
|
|
10145
|
+
try {
|
|
10146
|
+
return await readJson(await fetch(path, {
|
|
10147
|
+
method: "POST",
|
|
10148
|
+
headers: { "content-type": "application/json" },
|
|
10149
|
+
body: JSON.stringify(body),
|
|
10150
|
+
signal: controller.signal
|
|
10151
|
+
}), t);
|
|
10152
|
+
} catch (err) {
|
|
10153
|
+
if (controller.signal.aborted) throw new ConfigManagerApiError(t("error.syncTimeout", { minutes: String(Math.round(MY_CONFIGS_TIMEOUT_MS / 6e4)) }));
|
|
10154
|
+
throw err;
|
|
10155
|
+
} finally {
|
|
10156
|
+
clearTimeout(timer);
|
|
10157
|
+
}
|
|
10158
|
+
}
|
|
10159
|
+
/**
|
|
10160
|
+
* 「我的配置」浏览器半数据入口(MarketPanel 的「我的配置」子视图注入业务面)。
|
|
10161
|
+
* 登录(GitHub device flow)不在此类 —— 复用 SyncApi.githubStart/githubPoll/githubCancel。
|
|
10162
|
+
*/
|
|
10163
|
+
var MyConfigsApi = class {
|
|
10164
|
+
t;
|
|
10165
|
+
constructor(t = zhUiT) {
|
|
10166
|
+
this.t = t;
|
|
10167
|
+
}
|
|
10168
|
+
/** 读取登录态 + 用户配置仓库状态(token 失效 → Host 返回 401/未登录,UI 引导重新登录) */
|
|
10169
|
+
async meStatus() {
|
|
10170
|
+
return postJson(MY_CONFIGS_API.status, {}, this.t);
|
|
10171
|
+
}
|
|
10172
|
+
/** 一键上传:校验 → 建/复用仓库 → 写入用户仓库 → fork → 改官方 index → 提收录 PR */
|
|
10173
|
+
async meUpload(payload) {
|
|
10174
|
+
return postJson(MY_CONFIGS_API.upload, payload, this.t);
|
|
10175
|
+
}
|
|
10176
|
+
/** 读取已上传条目列表(用户仓库 index.json;每条目含 Host 侧判定的收录状态) */
|
|
10177
|
+
async meItems() {
|
|
10178
|
+
return postJson(MY_CONFIGS_API.items, {}, this.t);
|
|
10179
|
+
}
|
|
10180
|
+
/** 一键更新:version 自动 +1,复用/重开收录 PR */
|
|
10181
|
+
async meUpdate(payload) {
|
|
10182
|
+
return postJson(MY_CONFIGS_API.update, payload, this.t);
|
|
10183
|
+
}
|
|
10184
|
+
};
|
|
10185
|
+
//#endregion
|
|
9662
10186
|
//#region src/client/market/market-locales.ts
|
|
9663
10187
|
/**
|
|
9664
10188
|
* 配置市场区块(config-manager-market)表面文案:zh 为源语言,en 镜像每个键。
|
|
@@ -9712,61 +10236,62 @@ const zh = {
|
|
|
9712
10236
|
"detail.approval.safe": "可导入",
|
|
9713
10237
|
"detail.approval.count": "已批准 {selected}/{total} 分区",
|
|
9714
10238
|
"detail.noApproval": "未批准任何分区,无法导入",
|
|
9715
|
-
"
|
|
9716
|
-
"
|
|
9717
|
-
"
|
|
9718
|
-
"
|
|
9719
|
-
"
|
|
9720
|
-
"
|
|
9721
|
-
"
|
|
9722
|
-
"
|
|
9723
|
-
"
|
|
9724
|
-
"
|
|
9725
|
-
"
|
|
9726
|
-
"
|
|
9727
|
-
"
|
|
9728
|
-
"
|
|
9729
|
-
"
|
|
9730
|
-
"
|
|
9731
|
-
"
|
|
9732
|
-
"
|
|
9733
|
-
"
|
|
9734
|
-
"
|
|
9735
|
-
"
|
|
9736
|
-
"
|
|
9737
|
-
"
|
|
9738
|
-
"
|
|
9739
|
-
"
|
|
9740
|
-
"
|
|
9741
|
-
"
|
|
9742
|
-
"
|
|
9743
|
-
"
|
|
9744
|
-
"
|
|
9745
|
-
"
|
|
9746
|
-
"
|
|
9747
|
-
"
|
|
9748
|
-
"
|
|
9749
|
-
"
|
|
9750
|
-
"
|
|
9751
|
-
"
|
|
9752
|
-
"
|
|
9753
|
-
"
|
|
9754
|
-
"
|
|
9755
|
-
"
|
|
9756
|
-
"
|
|
9757
|
-
"
|
|
9758
|
-
"
|
|
9759
|
-
"
|
|
9760
|
-
"
|
|
9761
|
-
"
|
|
9762
|
-
"
|
|
9763
|
-
"
|
|
9764
|
-
"
|
|
9765
|
-
"
|
|
9766
|
-
"
|
|
9767
|
-
"
|
|
9768
|
-
"
|
|
9769
|
-
"
|
|
10239
|
+
"myconfigs.tab.browse": "浏览市场",
|
|
10240
|
+
"myconfigs.tab.myconfigs": "我的配置",
|
|
10241
|
+
"myconfigs.back": "返回市场",
|
|
10242
|
+
"myconfigs.login.title": "GitHub 登录",
|
|
10243
|
+
"myconfigs.login.hint": "登录后即可一键上传配置到你的公开仓库(自动创建 <login>/dsh-configs)并提交官方市场收录(目标仓库固定,不可修改)。",
|
|
10244
|
+
"myconfigs.login.checking": "正在检查登录状态…",
|
|
10245
|
+
"myconfigs.login.loggedInAs": "已登录:{login}",
|
|
10246
|
+
"myconfigs.login.targetRepo": "收录目标(固定):{repo}",
|
|
10247
|
+
"myconfigs.login.repoMissing": "配置仓库尚未创建,首次上传将自动创建(公开)",
|
|
10248
|
+
"myconfigs.login.repoReady": "配置仓库:{repo}",
|
|
10249
|
+
"myconfigs.login.start": "使用 GitHub 登录",
|
|
10250
|
+
"myconfigs.login.relogin": "重新登录",
|
|
10251
|
+
"myconfigs.login.cancel": "取消登录",
|
|
10252
|
+
"myconfigs.login.userCode": "一次性代码:{code}",
|
|
10253
|
+
"myconfigs.login.openAuth": "打开授权页",
|
|
10254
|
+
"myconfigs.upload.title": "一键上传",
|
|
10255
|
+
"myconfigs.upload.selectHint": "选择导出的配置 zip(先在「导出」tab 生成备份文件,再回到这里选择)。市场通道永不携带秘密——包含密钥的备份将被拒绝。",
|
|
10256
|
+
"myconfigs.upload.select": "选择 ZIP…",
|
|
10257
|
+
"myconfigs.upload.reselect": "重新选择",
|
|
10258
|
+
"myconfigs.upload.selected": "已选择:{name}",
|
|
10259
|
+
"myconfigs.upload.validate": "开始校验",
|
|
10260
|
+
"myconfigs.upload.validating": "校验中…",
|
|
10261
|
+
"myconfigs.upload.validateOk": "校验通过:内容合法、不含密钥",
|
|
10262
|
+
"myconfigs.upload.validateSecrets": "包含密钥:市场通道永不携带秘密,拒绝上传",
|
|
10263
|
+
"myconfigs.upload.validateInvalid": "内容校验未通过,无法上传",
|
|
10264
|
+
"myconfigs.upload.form.title": "条目信息",
|
|
10265
|
+
"myconfigs.upload.form.name": "名称",
|
|
10266
|
+
"myconfigs.upload.form.nameHint": "预填 zip 文件名,可修改",
|
|
10267
|
+
"myconfigs.upload.form.description": "描述(可选)",
|
|
10268
|
+
"myconfigs.upload.form.categories": "类别(可选,逗号分隔)",
|
|
10269
|
+
"myconfigs.upload.form.autoHint": "以下字段由系统自动生成:",
|
|
10270
|
+
"myconfigs.upload.run": "一键上传",
|
|
10271
|
+
"myconfigs.upload.running": "上传中…",
|
|
10272
|
+
"myconfigs.update.title": "更新配置",
|
|
10273
|
+
"myconfigs.update.hint": "版本将自动 +1,已预填原条目信息(名称可改)。",
|
|
10274
|
+
"myconfigs.update.run": "一键更新",
|
|
10275
|
+
"myconfigs.update.running": "更新中…",
|
|
10276
|
+
"myconfigs.result.title": "上传/更新完成",
|
|
10277
|
+
"myconfigs.result.version": "版本 {version}",
|
|
10278
|
+
"myconfigs.result.sha256": "SHA-256:{hash}",
|
|
10279
|
+
"myconfigs.result.sections": "分区:{sections}",
|
|
10280
|
+
"myconfigs.result.repo": "配置仓库",
|
|
10281
|
+
"myconfigs.result.openRepo": "打开仓库",
|
|
10282
|
+
"myconfigs.result.pr": "收录 PR #{number}",
|
|
10283
|
+
"myconfigs.result.openPr": "查看 PR",
|
|
10284
|
+
"myconfigs.list.title": "已上传",
|
|
10285
|
+
"myconfigs.list.empty": "尚未上传任何配置",
|
|
10286
|
+
"myconfigs.list.loading": "正在读取…",
|
|
10287
|
+
"myconfigs.list.refresh": "刷新",
|
|
10288
|
+
"myconfigs.list.summary": "共 {total} 条 · {listed} 已收录 · {pending} 待审核 · {none} 未收录",
|
|
10289
|
+
"myconfigs.list.openRepo": "打开仓库",
|
|
10290
|
+
"myconfigs.item.update": "更新",
|
|
10291
|
+
"myconfigs.item.install": "装回本地",
|
|
10292
|
+
"myconfigs.item.openPr": "查看收录 PR",
|
|
10293
|
+
"myconfigs.error.loadStatus": "登录状态读取失败",
|
|
10294
|
+
"myconfigs.error.loadItems": "已上传列表读取失败",
|
|
9770
10295
|
"common.cancel": "取消",
|
|
9771
10296
|
"common.close": "关闭",
|
|
9772
10297
|
"common.retry": "重试",
|
|
@@ -9814,61 +10339,62 @@ const en = {
|
|
|
9814
10339
|
"detail.approval.safe": "Importable",
|
|
9815
10340
|
"detail.approval.count": "{selected}/{total} section(s) approved",
|
|
9816
10341
|
"detail.noApproval": "No sections approved — cannot import",
|
|
9817
|
-
"
|
|
9818
|
-
"
|
|
9819
|
-
"
|
|
9820
|
-
"
|
|
9821
|
-
"
|
|
9822
|
-
"
|
|
9823
|
-
"
|
|
9824
|
-
"
|
|
9825
|
-
"
|
|
9826
|
-
"
|
|
9827
|
-
"
|
|
9828
|
-
"
|
|
9829
|
-
"
|
|
9830
|
-
"
|
|
9831
|
-
"
|
|
9832
|
-
"
|
|
9833
|
-
"
|
|
9834
|
-
"
|
|
9835
|
-
"
|
|
9836
|
-
"
|
|
9837
|
-
"
|
|
9838
|
-
"
|
|
9839
|
-
"
|
|
9840
|
-
"
|
|
9841
|
-
"
|
|
9842
|
-
"
|
|
9843
|
-
"
|
|
9844
|
-
"
|
|
9845
|
-
"
|
|
9846
|
-
"
|
|
9847
|
-
"
|
|
9848
|
-
"
|
|
9849
|
-
"
|
|
9850
|
-
"
|
|
9851
|
-
"
|
|
9852
|
-
"
|
|
9853
|
-
"
|
|
9854
|
-
"
|
|
9855
|
-
"
|
|
9856
|
-
"
|
|
9857
|
-
"
|
|
9858
|
-
"
|
|
9859
|
-
"
|
|
9860
|
-
"
|
|
9861
|
-
"
|
|
9862
|
-
"
|
|
9863
|
-
"
|
|
9864
|
-
"
|
|
9865
|
-
"
|
|
9866
|
-
"
|
|
9867
|
-
"
|
|
9868
|
-
"
|
|
9869
|
-
"
|
|
9870
|
-
"
|
|
9871
|
-
"
|
|
10342
|
+
"myconfigs.tab.browse": "Browse Market",
|
|
10343
|
+
"myconfigs.tab.myconfigs": "My Configs",
|
|
10344
|
+
"myconfigs.back": "Back to market",
|
|
10345
|
+
"myconfigs.login.title": "GitHub sign-in",
|
|
10346
|
+
"myconfigs.login.hint": "After signing in you can upload configs to your public repo (auto-created as <login>/dsh-configs) and submit a listing PR to the official market (target repo is fixed and not editable).",
|
|
10347
|
+
"myconfigs.login.checking": "Checking sign-in status…",
|
|
10348
|
+
"myconfigs.login.loggedInAs": "Signed in as {login}",
|
|
10349
|
+
"myconfigs.login.targetRepo": "Listing target (fixed): {repo}",
|
|
10350
|
+
"myconfigs.login.repoMissing": "Config repo not created yet — it will be auto-created (public) on first upload",
|
|
10351
|
+
"myconfigs.login.repoReady": "Config repo: {repo}",
|
|
10352
|
+
"myconfigs.login.start": "Sign in with GitHub",
|
|
10353
|
+
"myconfigs.login.relogin": "Sign in again",
|
|
10354
|
+
"myconfigs.login.cancel": "Cancel sign-in",
|
|
10355
|
+
"myconfigs.login.userCode": "One-time code: {code}",
|
|
10356
|
+
"myconfigs.login.openAuth": "Open authorization page",
|
|
10357
|
+
"myconfigs.upload.title": "Upload",
|
|
10358
|
+
"myconfigs.upload.selectHint": "Pick an exported config zip (create a backup on the \"Export\" tab first, then pick it here). The market channel never carries secrets — backups containing secrets are rejected.",
|
|
10359
|
+
"myconfigs.upload.select": "Choose ZIP…",
|
|
10360
|
+
"myconfigs.upload.reselect": "Choose again",
|
|
10361
|
+
"myconfigs.upload.selected": "Selected: {name}",
|
|
10362
|
+
"myconfigs.upload.validate": "Run validation",
|
|
10363
|
+
"myconfigs.upload.validating": "Validating…",
|
|
10364
|
+
"myconfigs.upload.validateOk": "Validation passed: valid content, no secrets",
|
|
10365
|
+
"myconfigs.upload.validateSecrets": "Contains secrets: the market channel never carries secrets — upload rejected",
|
|
10366
|
+
"myconfigs.upload.validateInvalid": "Content validation failed — cannot upload",
|
|
10367
|
+
"myconfigs.upload.form.title": "Item info",
|
|
10368
|
+
"myconfigs.upload.form.name": "Name",
|
|
10369
|
+
"myconfigs.upload.form.nameHint": "Prefilled from the zip file name; editable",
|
|
10370
|
+
"myconfigs.upload.form.description": "Description (optional)",
|
|
10371
|
+
"myconfigs.upload.form.categories": "Categories (optional, comma-separated)",
|
|
10372
|
+
"myconfigs.upload.form.autoHint": "These fields are generated automatically:",
|
|
10373
|
+
"myconfigs.upload.run": "Upload",
|
|
10374
|
+
"myconfigs.upload.running": "Uploading…",
|
|
10375
|
+
"myconfigs.update.title": "Update config",
|
|
10376
|
+
"myconfigs.update.hint": "The version will be bumped automatically (+1); item info is prefilled (name editable).",
|
|
10377
|
+
"myconfigs.update.run": "Update",
|
|
10378
|
+
"myconfigs.update.running": "Updating…",
|
|
10379
|
+
"myconfigs.result.title": "Upload/update complete",
|
|
10380
|
+
"myconfigs.result.version": "version {version}",
|
|
10381
|
+
"myconfigs.result.sha256": "SHA-256: {hash}",
|
|
10382
|
+
"myconfigs.result.sections": "Sections: {sections}",
|
|
10383
|
+
"myconfigs.result.repo": "Config repo",
|
|
10384
|
+
"myconfigs.result.openRepo": "Open repo",
|
|
10385
|
+
"myconfigs.result.pr": "Listing PR #{number}",
|
|
10386
|
+
"myconfigs.result.openPr": "View PR",
|
|
10387
|
+
"myconfigs.list.title": "Uploaded",
|
|
10388
|
+
"myconfigs.list.empty": "No configs uploaded yet",
|
|
10389
|
+
"myconfigs.list.loading": "Loading…",
|
|
10390
|
+
"myconfigs.list.refresh": "Refresh",
|
|
10391
|
+
"myconfigs.list.summary": "{total} item(s) · {listed} listed · {pending} pending · {none} not listed",
|
|
10392
|
+
"myconfigs.list.openRepo": "Open repo",
|
|
10393
|
+
"myconfigs.item.update": "Update",
|
|
10394
|
+
"myconfigs.item.install": "Install locally",
|
|
10395
|
+
"myconfigs.item.openPr": "View listing PR",
|
|
10396
|
+
"myconfigs.error.loadStatus": "Failed to read sign-in status",
|
|
10397
|
+
"myconfigs.error.loadItems": "Failed to read uploaded items",
|
|
9872
10398
|
"common.cancel": "Cancel",
|
|
9873
10399
|
"common.close": "Close",
|
|
9874
10400
|
"common.retry": "Retry",
|
|
@@ -9909,6 +10435,7 @@ function apply(ctx) {
|
|
|
9909
10435
|
const syncApi = new SyncApi(uiT);
|
|
9910
10436
|
const marketT = ctx.locale.bind(MARKET_NS);
|
|
9911
10437
|
const marketApi = new MarketApi(uiT);
|
|
10438
|
+
const myConfigsApi = new MyConfigsApi(uiT);
|
|
9912
10439
|
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
9913
10440
|
name: "settings.section",
|
|
9914
10441
|
id: "config-manager",
|
|
@@ -9920,7 +10447,8 @@ function apply(ctx) {
|
|
|
9920
10447
|
syncApi,
|
|
9921
10448
|
syncT,
|
|
9922
10449
|
marketApi,
|
|
9923
|
-
marketT
|
|
10450
|
+
marketT,
|
|
10451
|
+
myConfigsApi
|
|
9924
10452
|
})
|
|
9925
10453
|
}, ConfigManagerSection));
|
|
9926
10454
|
}
|