@modesto_ma/dsh-guanxiang 0.1.0-beta.3 → 0.2.0-beta.1
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/README.md +2 -1
- package/lib/client.js +140 -91
- package/lib/client.js.map +1 -1
- package/lib/index.js +32 -30
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/page.tsx +83 -36
- package/src/client/styles.ts +4 -0
- package/src/config.ts +11 -3
- package/src/repos/credentials.ts +2 -3
- package/src/settings/contract.ts +5 -0
package/README.md
CHANGED
|
@@ -96,6 +96,7 @@ npm 发行包已包含构建产物;本地 `link:` 安装前必须先执行 `pn
|
|
|
96
96
|
branch: release # 可选,默认跟随远端默认分支
|
|
97
97
|
gitCredentials: # https 仓库的访问令牌,只写令牌名称,不写令牌值
|
|
98
98
|
- url: https://gitlab.example.com # 地址前缀;其下的 https 仓库使用该令牌
|
|
99
|
+
alias: guanxiang # 可选,面板显示的名称
|
|
99
100
|
username: oauth2 # 可选,默认 oauth2
|
|
100
101
|
tokenEnv: GITLAB_TOKEN # DSH 凭据库中的令牌名称,也可以是同名环境变量
|
|
101
102
|
fetchIntervalMs: 0 # 加载 /guanxiang 时距上次拉取超过该时间的仓库先 fetch;0 = 每次
|
|
@@ -142,7 +143,7 @@ DSH Web 左侧栏的「观象」入口打开整页设置面板:
|
|
|
142
143
|
| 分页 | 内容 |
|
|
143
144
|
|---|---|
|
|
144
145
|
| 应用仓库 | git 地址列表(分支可选)、每个仓库的拉取状态、提交号、应用描述与不可用原因,单个或全部重新拉取 |
|
|
145
|
-
| 访问令牌 |
|
|
146
|
+
| 访问令牌 | 填 GitLab 地址、令牌别名和令牌三项即可添加;用户名固定为 oauth2,凭据库键名自动分配(`GUANXIANG_GITLAB_TOKEN`、`GUANXIANG_GITLAB_TOKEN_2`……)。每行显示令牌是否已设置及来源,可替换或清除 |
|
|
146
147
|
| 日志下载 | 允许下载的域名、单文件大小上限、下载超时 |
|
|
147
148
|
| 高级 | 拉取目录、更新间隔、单次应用数上限、派发超时、SKILL 改进开关与提议上限,恢复 cordis.yml 配置 |
|
|
148
149
|
|
package/lib/client.js
CHANGED
|
@@ -223,6 +223,7 @@ window.__ModuleLoader__.load({
|
|
|
223
223
|
}),
|
|
224
224
|
gitCredentials: draft.gitCredentials.map((credential) => ({
|
|
225
225
|
url: credential.url.trim(),
|
|
226
|
+
...credential.alias === void 0 || credential.alias.trim() === "" ? {} : { alias: credential.alias.trim() },
|
|
226
227
|
username: credential.username.trim(),
|
|
227
228
|
tokenEnv: credential.tokenEnv.trim()
|
|
228
229
|
})),
|
|
@@ -336,18 +337,19 @@ window.__ModuleLoader__.load({
|
|
|
336
337
|
setBusy(true);
|
|
337
338
|
setNotice(void 0);
|
|
338
339
|
try {
|
|
339
|
-
|
|
340
|
+
const next = await action();
|
|
341
|
+
adopt(next, resetDraft);
|
|
340
342
|
setNotice({
|
|
341
343
|
kind: "ok",
|
|
342
344
|
text: success
|
|
343
345
|
});
|
|
344
|
-
return
|
|
346
|
+
return next;
|
|
345
347
|
} catch (error) {
|
|
346
348
|
setNotice({
|
|
347
349
|
kind: "error",
|
|
348
350
|
text: messageOf(error)
|
|
349
351
|
});
|
|
350
|
-
return
|
|
352
|
+
return;
|
|
351
353
|
} finally {
|
|
352
354
|
setBusy(false);
|
|
353
355
|
}
|
|
@@ -461,8 +463,22 @@ window.__ModuleLoader__.load({
|
|
|
461
463
|
setDraft,
|
|
462
464
|
state,
|
|
463
465
|
busy,
|
|
464
|
-
|
|
465
|
-
|
|
466
|
+
onAdd: async (credential, token) => {
|
|
467
|
+
const added = (await run(() => saveSettings({
|
|
468
|
+
...state.settings,
|
|
469
|
+
gitCredentials: [...state.settings.gitCredentials, credential]
|
|
470
|
+
}), token === "" ? `已添加 ${credential.url},请填入令牌` : `已添加 ${credential.url}`, false))?.settings.gitCredentials[state.settings.gitCredentials.length];
|
|
471
|
+
if (added === void 0) return false;
|
|
472
|
+
setDraft((current) => current === void 0 || current.gitCredentials.some((item) => item.url === added.url) ? current : {
|
|
473
|
+
...current,
|
|
474
|
+
gitCredentials: [...current.gitCredentials, added]
|
|
475
|
+
});
|
|
476
|
+
return token === "" || await run(() => saveToken(added.tokenEnv, token), `已添加 ${added.url} 并保存令牌,使用它的仓库会重新拉取`, false) !== void 0;
|
|
477
|
+
},
|
|
478
|
+
onSave: async (tokenEnv, value) => await run(() => saveToken(tokenEnv, value), `已保存 ${tokenEnv},使用它的仓库会重新拉取`, false) !== void 0,
|
|
479
|
+
onClear: (tokenEnv) => {
|
|
480
|
+
run(() => clearToken(tokenEnv), `已清除面板保存的 ${tokenEnv}`, false);
|
|
481
|
+
}
|
|
466
482
|
}) : null,
|
|
467
483
|
tab === "download" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DownloadTab, {
|
|
468
484
|
draft,
|
|
@@ -698,7 +714,8 @@ window.__ModuleLoader__.load({
|
|
|
698
714
|
});
|
|
699
715
|
}
|
|
700
716
|
const DEFAULT_USERNAME = "oauth2";
|
|
701
|
-
|
|
717
|
+
/** Prefix of the credential keys the panel allocates; namespaced so an unrelated `GITLAB_TOKEN` in the environment is not picked up. */
|
|
718
|
+
const DEFAULT_TOKEN_ENV = "GUANXIANG_GITLAB_TOKEN";
|
|
702
719
|
/** Credential store layers of `@deepseek-ai/dsh-credentials-local` other than its own file. */
|
|
703
720
|
const TOKEN_SOURCES = {
|
|
704
721
|
"env": "启动环境变量",
|
|
@@ -728,29 +745,55 @@ window.__ModuleLoader__.load({
|
|
|
728
745
|
]
|
|
729
746
|
});
|
|
730
747
|
}
|
|
731
|
-
|
|
748
|
+
/**
|
|
749
|
+
* First of `GUANXIANG_GITLAB_TOKEN`, `GUANXIANG_GITLAB_TOKEN_2`, … that no
|
|
750
|
+
* saved or drafted entry uses.
|
|
751
|
+
* @param used - keys already in use.
|
|
752
|
+
* @returns a free key.
|
|
753
|
+
*/
|
|
754
|
+
function freeTokenEnv(used) {
|
|
755
|
+
if (!used.has(DEFAULT_TOKEN_ENV)) return DEFAULT_TOKEN_ENV;
|
|
756
|
+
let index = 2;
|
|
757
|
+
while (used.has(`${DEFAULT_TOKEN_ENV}_${index}`)) index += 1;
|
|
758
|
+
return `${DEFAULT_TOKEN_ENV}_${index}`;
|
|
759
|
+
}
|
|
760
|
+
function TokensTab({ draft, setDraft, state, busy, onAdd, onSave, onClear }) {
|
|
732
761
|
const [url, setUrl] = (0, react.useState)("");
|
|
733
|
-
const [
|
|
734
|
-
const [
|
|
762
|
+
const [alias, setAlias] = (0, react.useState)("");
|
|
763
|
+
const [token, setToken] = (0, react.useState)("");
|
|
764
|
+
const [addError, setAddError] = (0, react.useState)();
|
|
765
|
+
const [adding, setAdding] = (0, react.useState)(false);
|
|
735
766
|
const [values, setValues] = (0, react.useState)({});
|
|
736
|
-
const tokens = (0, react.useMemo)(() => new Map(state.credentials.tokens.map((
|
|
767
|
+
const tokens = (0, react.useMemo)(() => new Map(state.credentials.tokens.map((item) => [item.tokenEnv, item])), [state.credentials.tokens]);
|
|
737
768
|
const saved = (0, react.useMemo)(() => new Set(state.settings.gitCredentials.map((credential) => credential.tokenEnv)), [state.settings.gitCredentials]);
|
|
769
|
+
const complete = url.trim() !== "" && alias.trim() !== "" && token.trim() !== "";
|
|
738
770
|
const add = () => {
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
771
|
+
if (!complete || busy || adding) return;
|
|
772
|
+
const address = url.trim();
|
|
773
|
+
if (state.settings.gitCredentials.some((credential) => credential.url === address)) {
|
|
774
|
+
setAddError("这个地址已经添加过了,请在上面的列表里更新它的令牌。");
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
setAddError(void 0);
|
|
778
|
+
setAdding(true);
|
|
779
|
+
const tokenEnv = freeTokenEnv(/* @__PURE__ */ new Set([...saved, ...draft.gitCredentials.map((credential) => credential.tokenEnv)]));
|
|
780
|
+
onAdd({
|
|
781
|
+
url: address,
|
|
782
|
+
alias: alias.trim(),
|
|
783
|
+
username: DEFAULT_USERNAME,
|
|
784
|
+
tokenEnv
|
|
785
|
+
}, token.trim()).then((ok) => {
|
|
786
|
+
if (!ok) return;
|
|
787
|
+
setUrl("");
|
|
788
|
+
setAlias("");
|
|
789
|
+
setToken("");
|
|
790
|
+
}).finally(() => {
|
|
791
|
+
setAdding(false);
|
|
748
792
|
});
|
|
749
|
-
setUrl("");
|
|
750
793
|
};
|
|
751
794
|
const save = (name) => {
|
|
752
795
|
const value = values[name]?.trim() ?? "";
|
|
753
|
-
if (value === "") return;
|
|
796
|
+
if (value === "" || busy || adding) return;
|
|
754
797
|
onSave(name, value).then((ok) => {
|
|
755
798
|
if (ok) setValues((current) => ({
|
|
756
799
|
...current,
|
|
@@ -758,12 +801,15 @@ window.__ModuleLoader__.load({
|
|
|
758
801
|
}));
|
|
759
802
|
});
|
|
760
803
|
};
|
|
804
|
+
const submitOnEnter = (event) => {
|
|
805
|
+
if (event.key === "Enter") add();
|
|
806
|
+
};
|
|
761
807
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
762
808
|
className: "gx__sec",
|
|
763
809
|
children: [
|
|
764
810
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("h2", {
|
|
765
811
|
className: "gx__secTitle",
|
|
766
|
-
children: ["
|
|
812
|
+
children: ["GitLab 访问令牌 ", /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Overridden, {
|
|
767
813
|
keys: state.overridden,
|
|
768
814
|
name: "gitCredentials"
|
|
769
815
|
})]
|
|
@@ -777,10 +823,10 @@ window.__ModuleLoader__.load({
|
|
|
777
823
|
children: [
|
|
778
824
|
draft.gitCredentials.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
779
825
|
className: "gx__emptyRow",
|
|
780
|
-
children: "还没有访问令牌。用 https 地址拉取 GitLab
|
|
826
|
+
children: "还没有访问令牌。用 https 地址拉取 GitLab 私有仓库时,在下面填入 GitLab 地址、令牌别名和令牌。"
|
|
781
827
|
}) : null,
|
|
782
828
|
draft.gitCredentials.map((credential, index) => {
|
|
783
|
-
const
|
|
829
|
+
const status = saved.has(credential.tokenEnv) ? tokens.get(credential.tokenEnv) : void 0;
|
|
784
830
|
const value = values[credential.tokenEnv] ?? "";
|
|
785
831
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
786
832
|
className: "gx__row gx__row--top",
|
|
@@ -789,30 +835,17 @@ window.__ModuleLoader__.load({
|
|
|
789
835
|
children: [
|
|
790
836
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
791
837
|
className: "gx__rowLabel",
|
|
792
|
-
children: [
|
|
793
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
794
|
-
className: "gx__mono",
|
|
795
|
-
children: credential.url
|
|
796
|
-
}),
|
|
797
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
798
|
-
className: "gx__tag",
|
|
799
|
-
children: ["用户名 ", credential.username]
|
|
800
|
-
}),
|
|
801
|
-
tokenPill(token)
|
|
802
|
-
]
|
|
838
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: credential.alias ?? credential.url }), tokenPill(status)]
|
|
803
839
|
}),
|
|
804
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
805
|
-
className: "gx__rowSummary",
|
|
806
|
-
children:
|
|
807
|
-
className: "gx__mono",
|
|
808
|
-
children: credential.tokenEnv
|
|
809
|
-
})]
|
|
840
|
+
credential.alias === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
841
|
+
className: "gx__rowSummary gx__mono",
|
|
842
|
+
children: credential.url
|
|
810
843
|
}),
|
|
811
|
-
|
|
844
|
+
status?.error === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
812
845
|
className: "gx__rowError",
|
|
813
|
-
children:
|
|
846
|
+
children: status.error
|
|
814
847
|
}),
|
|
815
|
-
|
|
848
|
+
status !== void 0 && status.writable ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
816
849
|
className: "gx__addRow gx__addRow--flush",
|
|
817
850
|
children: [
|
|
818
851
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
@@ -820,7 +853,7 @@ window.__ModuleLoader__.load({
|
|
|
820
853
|
type: "password",
|
|
821
854
|
autoComplete: "new-password",
|
|
822
855
|
spellCheck: false,
|
|
823
|
-
placeholder:
|
|
856
|
+
placeholder: status.configured ? "输入新令牌以替换" : "粘贴令牌",
|
|
824
857
|
value,
|
|
825
858
|
onChange: (event) => {
|
|
826
859
|
setValues({
|
|
@@ -835,13 +868,13 @@ window.__ModuleLoader__.load({
|
|
|
835
868
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
836
869
|
type: "button",
|
|
837
870
|
className: "gx__btn",
|
|
838
|
-
disabled: busy || value.trim() === "",
|
|
871
|
+
disabled: busy || adding || value.trim() === "",
|
|
839
872
|
onClick: () => {
|
|
840
873
|
save(credential.tokenEnv);
|
|
841
874
|
},
|
|
842
875
|
children: "保存令牌"
|
|
843
876
|
}),
|
|
844
|
-
|
|
877
|
+
status.configured && status.source === "file" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
845
878
|
type: "button",
|
|
846
879
|
className: "gx__btn gx__btn--ghost",
|
|
847
880
|
disabled: busy,
|
|
@@ -870,55 +903,67 @@ window.__ModuleLoader__.load({
|
|
|
870
903
|
}, credential.url);
|
|
871
904
|
}),
|
|
872
905
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
873
|
-
className: "
|
|
874
|
-
children: [
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
906
|
+
className: "gx__addForm",
|
|
907
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
908
|
+
className: "gx__addRow gx__addRow--flush",
|
|
909
|
+
children: [
|
|
910
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
911
|
+
className: "gx__field",
|
|
912
|
+
children: ["GitLab 地址", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
913
|
+
className: "gx__input",
|
|
914
|
+
placeholder: "https://gitlab.example.com",
|
|
915
|
+
value: url,
|
|
916
|
+
onChange: (event) => {
|
|
917
|
+
setUrl(event.target.value);
|
|
918
|
+
},
|
|
919
|
+
onKeyDown: submitOnEnter
|
|
920
|
+
})]
|
|
921
|
+
}),
|
|
922
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
923
|
+
className: "gx__field",
|
|
924
|
+
children: ["令牌别名", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
925
|
+
className: "gx__input",
|
|
926
|
+
placeholder: "例如 guanxiang",
|
|
927
|
+
value: alias,
|
|
928
|
+
onChange: (event) => {
|
|
929
|
+
setAlias(event.target.value);
|
|
930
|
+
},
|
|
931
|
+
onKeyDown: submitOnEnter
|
|
932
|
+
})]
|
|
933
|
+
}),
|
|
934
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
935
|
+
className: "gx__field",
|
|
936
|
+
children: ["令牌", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
937
|
+
className: "gx__input gx__mono",
|
|
938
|
+
type: "password",
|
|
939
|
+
autoComplete: "new-password",
|
|
940
|
+
spellCheck: false,
|
|
941
|
+
placeholder: "粘贴个人访问令牌",
|
|
942
|
+
value: token,
|
|
943
|
+
onChange: (event) => {
|
|
944
|
+
setToken(event.target.value);
|
|
945
|
+
},
|
|
946
|
+
onKeyDown: submitOnEnter
|
|
947
|
+
})]
|
|
948
|
+
}),
|
|
949
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
950
|
+
type: "button",
|
|
951
|
+
className: "gx__btn gx__btn--end",
|
|
952
|
+
disabled: busy || adding || !complete,
|
|
953
|
+
onClick: add,
|
|
954
|
+
children: "添加"
|
|
955
|
+
})
|
|
956
|
+
]
|
|
957
|
+
}), addError === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
958
|
+
className: "gx__rowError",
|
|
959
|
+
children: addError
|
|
960
|
+
})]
|
|
916
961
|
})
|
|
917
962
|
]
|
|
918
963
|
}),
|
|
919
964
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
920
965
|
className: "gx__secFoot",
|
|
921
|
-
children: "地址在这个前缀下的 https
|
|
966
|
+
children: "地址在这个前缀下的 https 仓库使用对应令牌(多个匹配时取路径最长的),ssh 地址不受影响。拉取需要 read_repository 权限,开启 SKILL 改进时还需要 write_repository。令牌保存在 DSH 凭据库(默认 $DSH_HOME/.credentials.yaml,仅本机用户可读),面板只显示是否已设置;令牌只经环境变量交给 git,不写入设置文件、仓库配置、日志或模型上下文。"
|
|
922
967
|
})
|
|
923
968
|
]
|
|
924
969
|
});
|
|
@@ -1297,6 +1342,10 @@ window.__ModuleLoader__.load({
|
|
|
1297
1342
|
.gx__addRow .gx__input{flex:1 1 160px;width:auto}
|
|
1298
1343
|
.gx__addRow .gx__input--branch{flex:0 1 140px}
|
|
1299
1344
|
.gx__addRow--flush{padding:6px 0 0}
|
|
1345
|
+
.gx__addForm{display:flex;flex-direction:column;gap:6px;padding:10px 14px}
|
|
1346
|
+
.gx__field{display:flex;flex-direction:column;gap:2px;flex:1 1 160px;color:var(--gx-fg3);font-size:12px}
|
|
1347
|
+
.gx__field .gx__input{flex:none;width:100%}
|
|
1348
|
+
.gx__btn--end{align-self:flex-end;height:30px}
|
|
1300
1349
|
.gx__emptyRow{padding:11px 14px;color:var(--gx-fg3);font-size:12px}
|
|
1301
1350
|
.gx__btn{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:4px;height:28px;padding:0 12px;border:1px solid var(--gx-line);border-radius:7px;background:transparent;color:inherit;font:inherit;font-size:13px;cursor:pointer;white-space:nowrap}
|
|
1302
1351
|
.gx__btn:hover:not(:disabled){background:var(--gx-hover)}
|