@ddtcorex/dsh-maestro-config 0.3.1 → 0.4.0
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.js +107 -5
- package/lib/types/client/MaestroSettings.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client/MaestroSettings.tsx +115 -30
- package/lib/types/client/maestro-card.d.ts +0 -10
- package/lib/types/client/maestro-card.d.ts.map +0 -1
- package/src/client/maestro-card.jsx +0 -945
package/lib/client.js
CHANGED
|
@@ -2906,12 +2906,96 @@ function ProjectMappingsEditor({ mappings, onChange, catalog, globalReviewModel
|
|
|
2906
2906
|
label: null
|
|
2907
2907
|
})
|
|
2908
2908
|
)
|
|
2909
|
+
),
|
|
2910
|
+
// Row for per-project trigger overrides — tri-state: inherit the global toggle or force on/off.
|
|
2911
|
+
(0, import_react.createElement)(
|
|
2912
|
+
"div",
|
|
2913
|
+
{ "data-maestro-project-triggers-row": "", style: { display: "flex", gap: 12, flexWrap: "wrap", alignItems: "flex-start" } },
|
|
2914
|
+
(0, import_react.createElement)(
|
|
2915
|
+
"label",
|
|
2916
|
+
{ style: { ...fieldLabelStyle, flex: "1 1 160px", minWidth: 0 } },
|
|
2917
|
+
"Re-review on push",
|
|
2918
|
+
(0, import_react.createElement)(
|
|
2919
|
+
"select",
|
|
2920
|
+
{
|
|
2921
|
+
value: row.rereviewOnPush === void 0 ? "inherit" : row.rereviewOnPush ? "on" : "off",
|
|
2922
|
+
onChange: (e) => updateRow(i, "rereviewOnPush", e.target.value === "inherit" ? void 0 : e.target.value === "on"),
|
|
2923
|
+
"aria-label": `Re-review on push ${i + 1}`,
|
|
2924
|
+
style: {
|
|
2925
|
+
height: 36,
|
|
2926
|
+
width: "100%",
|
|
2927
|
+
padding: "0 14px",
|
|
2928
|
+
border: "none",
|
|
2929
|
+
borderRadius: 18,
|
|
2930
|
+
background: "var(--dsw-alias-bg-module-platform, #F5F6F7)",
|
|
2931
|
+
color: t.labelPrimary,
|
|
2932
|
+
font: "inherit",
|
|
2933
|
+
fontSize: 13
|
|
2934
|
+
}
|
|
2935
|
+
},
|
|
2936
|
+
(0, import_react.createElement)("option", { value: "inherit" }, "Inherit (global)"),
|
|
2937
|
+
(0, import_react.createElement)("option", { value: "on" }, "On"),
|
|
2938
|
+
(0, import_react.createElement)("option", { value: "off" }, "Off")
|
|
2939
|
+
)
|
|
2940
|
+
),
|
|
2941
|
+
(0, import_react.createElement)(
|
|
2942
|
+
"label",
|
|
2943
|
+
{ style: { ...fieldLabelStyle, flex: "1 1 160px", minWidth: 0 } },
|
|
2944
|
+
"Review on assign",
|
|
2945
|
+
(0, import_react.createElement)(
|
|
2946
|
+
"select",
|
|
2947
|
+
{
|
|
2948
|
+
value: row.reviewOnAssign === void 0 ? "inherit" : row.reviewOnAssign ? "on" : "off",
|
|
2949
|
+
onChange: (e) => updateRow(i, "reviewOnAssign", e.target.value === "inherit" ? void 0 : e.target.value === "on"),
|
|
2950
|
+
"aria-label": `Review on assign ${i + 1}`,
|
|
2951
|
+
style: {
|
|
2952
|
+
height: 36,
|
|
2953
|
+
width: "100%",
|
|
2954
|
+
padding: "0 14px",
|
|
2955
|
+
border: "none",
|
|
2956
|
+
borderRadius: 18,
|
|
2957
|
+
background: "var(--dsw-alias-bg-module-platform, #F5F6F7)",
|
|
2958
|
+
color: t.labelPrimary,
|
|
2959
|
+
font: "inherit",
|
|
2960
|
+
fontSize: 13
|
|
2961
|
+
}
|
|
2962
|
+
},
|
|
2963
|
+
(0, import_react.createElement)("option", { value: "inherit" }, "Inherit (global)"),
|
|
2964
|
+
(0, import_react.createElement)("option", { value: "on" }, "On"),
|
|
2965
|
+
(0, import_react.createElement)("option", { value: "off" }, "Off")
|
|
2966
|
+
)
|
|
2967
|
+
)
|
|
2909
2968
|
)
|
|
2910
2969
|
)
|
|
2911
2970
|
)
|
|
2912
2971
|
)
|
|
2913
2972
|
);
|
|
2914
2973
|
}
|
|
2974
|
+
function SecretField({ placeholder, hasSaved, onSave, width }) {
|
|
2975
|
+
const [draft, setDraft] = (0, import_react.useState)("");
|
|
2976
|
+
(0, import_react.useEffect)(() => {
|
|
2977
|
+
if (!hasSaved) setDraft("");
|
|
2978
|
+
}, [hasSaved]);
|
|
2979
|
+
const commit = () => {
|
|
2980
|
+
if (draft !== "") {
|
|
2981
|
+
onSave(draft);
|
|
2982
|
+
setDraft("");
|
|
2983
|
+
}
|
|
2984
|
+
};
|
|
2985
|
+
return (0, import_react.createElement)(FieldInput, {
|
|
2986
|
+
placeholder: hasSaved === true ? "saved \u2014 type new value to replace" : placeholder,
|
|
2987
|
+
type: "password",
|
|
2988
|
+
autoComplete: "off",
|
|
2989
|
+
value: draft,
|
|
2990
|
+
onChange: (e) => setDraft(e.target.value),
|
|
2991
|
+
onBlur: commit,
|
|
2992
|
+
onKeyDown: (e) => {
|
|
2993
|
+
if (e.key === "Enter") e.target.blur();
|
|
2994
|
+
},
|
|
2995
|
+
"aria-label": placeholder,
|
|
2996
|
+
style: { width: width ?? 200 }
|
|
2997
|
+
});
|
|
2998
|
+
}
|
|
2915
2999
|
function LanAccess({ proxyStatus, lanPin }) {
|
|
2916
3000
|
const urls = proxyStatus?.lanUrls ?? [];
|
|
2917
3001
|
const [selected, setSelected] = (0, import_react.useState)(0);
|
|
@@ -3409,11 +3493,28 @@ function MaestroSettingsTab({ rpcCall, configRpcCall }) {
|
|
|
3409
3493
|
setBusy(false);
|
|
3410
3494
|
}
|
|
3411
3495
|
};
|
|
3496
|
+
const SECRET_SAVE_FLAGS = { gitlabToken: "hasGitlabToken", webhookSecret: "hasWebhookSecret" };
|
|
3412
3497
|
const saveField = async (field, value) => {
|
|
3413
3498
|
setError(null);
|
|
3414
|
-
setConfig((prev) =>
|
|
3499
|
+
setConfig((prev) => {
|
|
3500
|
+
const next = { ...prev, [field]: value };
|
|
3501
|
+
const flag = SECRET_SAVE_FLAGS[field];
|
|
3502
|
+
if (flag) {
|
|
3503
|
+
delete next[field];
|
|
3504
|
+
next[flag] = value !== "";
|
|
3505
|
+
}
|
|
3506
|
+
return next;
|
|
3507
|
+
});
|
|
3415
3508
|
try {
|
|
3416
|
-
await call(MAESTRO_ENDPOINTS.saveConfig, { [field]: value });
|
|
3509
|
+
const saved = await call(MAESTRO_ENDPOINTS.saveConfig, { [field]: value });
|
|
3510
|
+
if (saved && typeof saved === "object") {
|
|
3511
|
+
setConfig((prev) => {
|
|
3512
|
+
const next = { ...prev, ...saved };
|
|
3513
|
+
const flag = SECRET_SAVE_FLAGS[field];
|
|
3514
|
+
if (flag) delete next[field];
|
|
3515
|
+
return next;
|
|
3516
|
+
});
|
|
3517
|
+
}
|
|
3417
3518
|
} catch (err) {
|
|
3418
3519
|
setError(err.message);
|
|
3419
3520
|
}
|
|
@@ -3448,9 +3549,9 @@ function MaestroSettingsTab({ rpcCall, configRpcCall }) {
|
|
|
3448
3549
|
"div",
|
|
3449
3550
|
{ style: { display: "flex", flexDirection: "column" } },
|
|
3450
3551
|
(0, import_react.createElement)(SettingRow, { title: "GitLab base URL", description: "e.g. https://gitlab.example.com", control: (0, import_react.createElement)(FieldInput, { placeholder: "https://gitlab.example.com", value: config.gitlabBaseUrl ?? "", onChange: (e) => saveField("gitlabBaseUrl", e.target.value), style: { width: 260 } }) }),
|
|
3451
|
-
(0, import_react.createElement)(SettingRow, { title: "GitLab token", description: "Personal access token with api scope.", control: (0, import_react.createElement)("div", { style: { display: "flex", gap: 8, alignItems: "center" } }, (0, import_react.createElement)(
|
|
3552
|
+
(0, import_react.createElement)(SettingRow, { title: "GitLab token", description: "Personal access token with api scope.", control: (0, import_react.createElement)("div", { style: { display: "flex", gap: 8, alignItems: "center" } }, (0, import_react.createElement)(SecretField, { placeholder: "GitLab token", hasSaved: config.hasGitlabToken === true, width: 200, onSave: (v) => saveField("gitlabToken", v) }), config.hasGitlabToken ? (0, import_react.createElement)(Button, { variant: "outline", size: "md", onClick: () => saveField("gitlabToken", "") }, "Clear") : null) }),
|
|
3452
3553
|
(0, import_react.createElement)(SettingRow, { title: "Bot username", description: "Username of the bot that posts reviews.", control: (0, import_react.createElement)(FieldInput, { placeholder: "maestro-bot", value: config.botUsername ?? "", onChange: (e) => saveField("botUsername", e.target.value), style: { width: 220 } }) }),
|
|
3453
|
-
(0, import_react.createElement)(SettingRow, { title: "Webhook secret", description: "Secret token for GitLab webhooks.", control: (0, import_react.createElement)("div", { style: { display: "flex", gap: 8, alignItems: "center" } }, (0, import_react.createElement)(
|
|
3554
|
+
(0, import_react.createElement)(SettingRow, { title: "Webhook secret", description: "Secret token for GitLab webhooks.", control: (0, import_react.createElement)("div", { style: { display: "flex", gap: 8, alignItems: "center" } }, (0, import_react.createElement)(SecretField, { placeholder: "Webhook secret", hasSaved: config.hasWebhookSecret === true, width: 200, onSave: (v) => saveField("webhookSecret", v) }), (0, import_react.createElement)(Button, { variant: "outline", size: "md", onClick: () => saveField("webhookSecret", generateWebhookSecret()) }, "Generate")) }),
|
|
3454
3555
|
(0, import_react.createElement)(
|
|
3455
3556
|
"div",
|
|
3456
3557
|
{ style: { padding: "16px 0", display: "flex", flexDirection: "column", gap: 6 } },
|
|
@@ -3462,6 +3563,7 @@ function MaestroSettingsTab({ rpcCall, configRpcCall }) {
|
|
|
3462
3563
|
"div",
|
|
3463
3564
|
{ style: { display: "flex", flexDirection: "column" } },
|
|
3464
3565
|
(0, import_react.createElement)(ToggleRow, { title: "Re-review on push", description: "When new commits are pushed, trigger an automatic re-review.", checked: config.autoRereviewOnPush === true, onChange: (v) => saveField("autoRereviewOnPush", v) }),
|
|
3566
|
+
(0, import_react.createElement)(ToggleRow, { title: "Review on assign", description: "When the bot is assigned as reviewer, trigger an automatic review.", checked: config.autoReviewOnAssign !== false, onChange: (v) => saveField("autoReviewOnAssign", v) }),
|
|
3465
3567
|
(0, import_react.createElement)(SettingRow, { title: "Global review model", description: "Model for automated reviews. Empty = DSH default.", control: (0, import_react.createElement)(ReviewModelSelector, { value: config.reviewModel ?? null, catalog, fallbackValue: catalog?.current ?? null, fallbackLabel: "Use DSH default", onChange: (v) => saveField("reviewModel", v), label: null }) }),
|
|
3466
3568
|
(0, import_react.createElement)(ProjectMappingsEditor, { mappings: config.projectMappings ?? [], onChange: (mappings) => saveField("projectMappings", mappings), catalog, globalReviewModel: config.reviewModel ?? null })
|
|
3467
3569
|
),
|
|
@@ -3519,7 +3621,7 @@ function MaestroSettingsTab({ rpcCall, configRpcCall }) {
|
|
|
3519
3621
|
"div",
|
|
3520
3622
|
{ style: { display: "flex", flexDirection: "column" } },
|
|
3521
3623
|
(0, import_react.createElement)("div", { style: { padding: "12px 0", borderBottom: `1px solid ${t.borderL2}` } }, (0, import_react.createElement)("p", { style: captionStyle }, "Telegram bot settings for notifications: startup PIN, review digests, PIN rotation. Leave blank to disable.")),
|
|
3522
|
-
(0, import_react.createElement)(SettingRow, { title: "Bot token", description: "Telegram bot token from @BotFather.", control: (0, import_react.createElement)("div", { style: { display: "flex", gap: 8, alignItems: "center" } }, (0, import_react.createElement)(
|
|
3624
|
+
(0, import_react.createElement)(SettingRow, { title: "Bot token", description: "Telegram bot token from @BotFather.", control: (0, import_react.createElement)("div", { style: { display: "flex", gap: 8, alignItems: "center" } }, (0, import_react.createElement)(SecretField, { placeholder: "123456:ABC-DEF...", hasSaved: (notifierCfg.telegram?.botToken ?? "") !== "", width: 220, onSave: (v) => saveNotifierCfg({ telegram: { botToken: v } }) }), notifierCfg.telegram?.botToken ? (0, import_react.createElement)(Button, { variant: "outline", size: "md", onClick: () => saveNotifierCfg({ telegram: { botToken: "" } }) }, "Clear") : null) }),
|
|
3523
3625
|
(0, import_react.createElement)(SettingRow, { title: "Chat ID", description: "Target chat, e.g. -1001234567890.", control: (0, import_react.createElement)(FieldInput, { value: notifierCfg.telegram?.chatId ?? "", placeholder: "-1001234567890", onChange: (e) => saveNotifierCfg({ telegram: { chatId: e.target.value } }), style: { width: 220 } }) }),
|
|
3524
3626
|
(0, import_react.createElement)(ToggleRow, { title: "Review notifications", description: "Also notify about finished reviews.", checked: notifierCfg.policy?.reviewNotifications === true, onChange: (v) => saveNotifierCfg({ policy: { reviewNotifications: v } }) })
|
|
3525
3627
|
)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MaestroSettings.d.ts","sourceRoot":"","sources":["../../../src/client/MaestroSettings.tsx"],"names":[],"mappings":"AACA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"MaestroSettings.d.ts","sourceRoot":"","sources":["../../../src/client/MaestroSettings.tsx"],"names":[],"mappings":"AACA;;;;;;;;GAQG;AAgwCH,wBAAgB,kBAAkB,CAAC,EAAE,OAAO,EAAE,aAAa,EAAE,EAAE;IAAE,OAAO,EAAE,GAAG,CAAC;IAAC,aAAa,CAAC,EAAE,GAAG,CAAA;CAAE;;;;;;;;;mBA4ZmE,GAAG;;gBAoBzK"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ddtcorex/dsh-maestro-config",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Maestro Config — shared settings service for the dsh-maestro-* suite over the single namespaced store (~/.dsh/maestro/settings.json)",
|
|
6
6
|
"type": "module",
|
|
@@ -941,39 +941,102 @@ function ProjectMappingsEditor({ mappings, onChange, catalog, globalReviewModel
|
|
|
941
941
|
}),
|
|
942
942
|
),
|
|
943
943
|
),
|
|
944
|
+
// Row for per-project trigger overrides — tri-state: inherit the global toggle or force on/off.
|
|
945
|
+
h(
|
|
946
|
+
'div',
|
|
947
|
+
{ 'data-maestro-project-triggers-row': '', style: { display: 'flex', gap: 12, flexWrap: 'wrap' as const, alignItems: 'flex-start' } },
|
|
948
|
+
h(
|
|
949
|
+
'label',
|
|
950
|
+
{ style: { ...fieldLabelStyle, flex: '1 1 160px', minWidth: 0 } },
|
|
951
|
+
'Re-review on push',
|
|
952
|
+
h(
|
|
953
|
+
'select',
|
|
954
|
+
{
|
|
955
|
+
value: row.rereviewOnPush === undefined ? 'inherit' : row.rereviewOnPush ? 'on' : 'off',
|
|
956
|
+
onChange: (e: any) => updateRow(i, 'rereviewOnPush', e.target.value === 'inherit' ? undefined : e.target.value === 'on'),
|
|
957
|
+
'aria-label': `Re-review on push ${i + 1}`,
|
|
958
|
+
style: {
|
|
959
|
+
height: 36,
|
|
960
|
+
width: '100%',
|
|
961
|
+
padding: '0 14px',
|
|
962
|
+
border: 'none',
|
|
963
|
+
borderRadius: 18,
|
|
964
|
+
background: 'var(--dsw-alias-bg-module-platform, #F5F6F7)' as string,
|
|
965
|
+
color: t.labelPrimary as string,
|
|
966
|
+
font: 'inherit',
|
|
967
|
+
fontSize: 13,
|
|
968
|
+
},
|
|
969
|
+
},
|
|
970
|
+
h('option', { value: 'inherit' }, 'Inherit (global)'),
|
|
971
|
+
h('option', { value: 'on' }, 'On'),
|
|
972
|
+
h('option', { value: 'off' }, 'Off'),
|
|
973
|
+
),
|
|
974
|
+
),
|
|
975
|
+
h(
|
|
976
|
+
'label',
|
|
977
|
+
{ style: { ...fieldLabelStyle, flex: '1 1 160px', minWidth: 0 } },
|
|
978
|
+
'Review on assign',
|
|
979
|
+
h(
|
|
980
|
+
'select',
|
|
981
|
+
{
|
|
982
|
+
value: row.reviewOnAssign === undefined ? 'inherit' : row.reviewOnAssign ? 'on' : 'off',
|
|
983
|
+
onChange: (e: any) => updateRow(i, 'reviewOnAssign', e.target.value === 'inherit' ? undefined : e.target.value === 'on'),
|
|
984
|
+
'aria-label': `Review on assign ${i + 1}`,
|
|
985
|
+
style: {
|
|
986
|
+
height: 36,
|
|
987
|
+
width: '100%',
|
|
988
|
+
padding: '0 14px',
|
|
989
|
+
border: 'none',
|
|
990
|
+
borderRadius: 18,
|
|
991
|
+
background: 'var(--dsw-alias-bg-module-platform, #F5F6F7)' as string,
|
|
992
|
+
color: t.labelPrimary as string,
|
|
993
|
+
font: 'inherit',
|
|
994
|
+
fontSize: 13,
|
|
995
|
+
},
|
|
996
|
+
},
|
|
997
|
+
h('option', { value: 'inherit' }, 'Inherit (global)'),
|
|
998
|
+
h('option', { value: 'on' }, 'On'),
|
|
999
|
+
h('option', { value: 'off' }, 'Off'),
|
|
1000
|
+
),
|
|
1001
|
+
),
|
|
1002
|
+
),
|
|
944
1003
|
),
|
|
945
1004
|
),
|
|
946
1005
|
),
|
|
947
1006
|
)
|
|
948
1007
|
}
|
|
949
1008
|
|
|
950
|
-
|
|
1009
|
+
// SecretField — control-sized secret input for SettingRow controls (no label).
|
|
1010
|
+
// The server never echoes secrets back (getConfig masks them to has* flags),
|
|
1011
|
+
// so the field stays EMPTY and commits a typed draft on blur / Enter; an
|
|
1012
|
+
// untouched field keeps the stored secret and Clear writes '' to erase it.
|
|
1013
|
+
// Never render mask bullets ('••••') as the input value: a controlled input
|
|
1014
|
+
// locked to a constant string swallows keystrokes and saves bullet-contaminated
|
|
1015
|
+
// text on the first change (the GitLab-token-not-editable regression).
|
|
1016
|
+
function SecretField({ placeholder, hasSaved, onSave, width }: { placeholder: string; hasSaved?: boolean; onSave: (v: string) => void; width?: number }) {
|
|
951
1017
|
const [draft, setDraft] = useState('')
|
|
952
|
-
|
|
953
|
-
setDraft('')
|
|
954
|
-
|
|
1018
|
+
useEffect(() => {
|
|
1019
|
+
if (!hasSaved) setDraft('')
|
|
1020
|
+
}, [hasSaved])
|
|
1021
|
+
const commit = () => {
|
|
1022
|
+
if (draft !== '') {
|
|
1023
|
+
onSave(draft)
|
|
1024
|
+
setDraft('')
|
|
1025
|
+
}
|
|
955
1026
|
}
|
|
956
|
-
return h(
|
|
957
|
-
'
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
onBlur: () => {
|
|
970
|
-
if (draft !== '') onSave(draft)
|
|
971
|
-
},
|
|
972
|
-
style: { flex: 1 } as any,
|
|
973
|
-
}),
|
|
974
|
-
hasSaved === true ? h(Button as any, { variant: 'outline', size: 'sm', onClick: clear }, 'Clear') : null,
|
|
975
|
-
),
|
|
976
|
-
)
|
|
1027
|
+
return h(FieldInput as any, {
|
|
1028
|
+
placeholder: hasSaved === true ? 'saved — type new value to replace' : placeholder,
|
|
1029
|
+
type: 'password',
|
|
1030
|
+
autoComplete: 'off',
|
|
1031
|
+
value: draft,
|
|
1032
|
+
onChange: (e: any) => setDraft(e.target.value),
|
|
1033
|
+
onBlur: commit,
|
|
1034
|
+
onKeyDown: (e: any) => {
|
|
1035
|
+
if (e.key === 'Enter') (e.target as HTMLInputElement).blur()
|
|
1036
|
+
},
|
|
1037
|
+
'aria-label': placeholder,
|
|
1038
|
+
style: { width: width ?? 200 } as any,
|
|
1039
|
+
})
|
|
977
1040
|
}
|
|
978
1041
|
|
|
979
1042
|
function ToggleField({ label, caption, checked, onChange }: { label: string; caption?: string; checked?: boolean; onChange: (v: boolean) => void }) {
|
|
@@ -1508,11 +1571,32 @@ export function MaestroSettingsTab({ rpcCall, configRpcCall }: { rpcCall: any; c
|
|
|
1508
1571
|
setBusy(false)
|
|
1509
1572
|
}
|
|
1510
1573
|
}
|
|
1574
|
+
const SECRET_SAVE_FLAGS: Record<string, string> = { gitlabToken: 'hasGitlabToken', webhookSecret: 'hasWebhookSecret' }
|
|
1511
1575
|
const saveField = async (field: string, value: unknown) => {
|
|
1512
1576
|
setError(null)
|
|
1513
|
-
|
|
1577
|
+
// Optimistic update — for secrets only flip the has* presence flag, never
|
|
1578
|
+
// stash the raw secret in state (the server never echoes it back).
|
|
1579
|
+
setConfig((prev: any) => {
|
|
1580
|
+
const next = { ...prev, [field]: value }
|
|
1581
|
+
const flag = SECRET_SAVE_FLAGS[field]
|
|
1582
|
+
if (flag) {
|
|
1583
|
+
delete next[field]
|
|
1584
|
+
next[flag] = value !== ''
|
|
1585
|
+
}
|
|
1586
|
+
return next
|
|
1587
|
+
})
|
|
1514
1588
|
try {
|
|
1515
|
-
|
|
1589
|
+
// saveConfig returns the masked config — merge it so has* flags (and a
|
|
1590
|
+
// cleared secret) sync without waiting for a reload.
|
|
1591
|
+
const saved = await call(MAESTRO_ENDPOINTS.saveConfig, { [field]: value })
|
|
1592
|
+
if (saved && typeof saved === 'object') {
|
|
1593
|
+
setConfig((prev: any) => {
|
|
1594
|
+
const next = { ...prev, ...(saved as object) }
|
|
1595
|
+
const flag = SECRET_SAVE_FLAGS[field]
|
|
1596
|
+
if (flag) delete next[field]
|
|
1597
|
+
return next
|
|
1598
|
+
})
|
|
1599
|
+
}
|
|
1516
1600
|
} catch (err: any) {
|
|
1517
1601
|
setError(err.message)
|
|
1518
1602
|
}
|
|
@@ -1550,9 +1634,9 @@ export function MaestroSettingsTab({ rpcCall, configRpcCall }: { rpcCall: any; c
|
|
|
1550
1634
|
'div',
|
|
1551
1635
|
{ style: { display: 'flex', flexDirection: 'column' } },
|
|
1552
1636
|
h(SettingRow as any, { title: 'GitLab base URL', description: 'e.g. https://gitlab.example.com', control: h(FieldInput as any, { placeholder: 'https://gitlab.example.com', value: config.gitlabBaseUrl ?? '', onChange: (e: any) => saveField('gitlabBaseUrl', e.target.value), style: { width: 260 } as any }) }),
|
|
1553
|
-
h(SettingRow as any, { title: 'GitLab token', description: 'Personal access token with api scope.', control: h('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } }, h(
|
|
1637
|
+
h(SettingRow as any, { title: 'GitLab token', description: 'Personal access token with api scope.', control: h('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } }, h(SecretField as any, { placeholder: 'GitLab token', hasSaved: config.hasGitlabToken === true, width: 200, onSave: (v: string) => saveField('gitlabToken', v) }), config.hasGitlabToken ? h(Button as any, { variant: 'outline', size: 'md', onClick: () => saveField('gitlabToken', '') }, 'Clear') : null) }),
|
|
1554
1638
|
h(SettingRow as any, { title: 'Bot username', description: 'Username of the bot that posts reviews.', control: h(FieldInput as any, { placeholder: 'maestro-bot', value: config.botUsername ?? '', onChange: (e: any) => saveField('botUsername', e.target.value), style: { width: 220 } as any }) }),
|
|
1555
|
-
h(SettingRow as any, { title: 'Webhook secret', description: 'Secret token for GitLab webhooks.', control: h('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } }, h(
|
|
1639
|
+
h(SettingRow as any, { title: 'Webhook secret', description: 'Secret token for GitLab webhooks.', control: h('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } }, h(SecretField as any, { placeholder: 'Webhook secret', hasSaved: config.hasWebhookSecret === true, width: 200, onSave: (v: string) => saveField('webhookSecret', v) }), h(Button as any, { variant: 'outline', size: 'md', onClick: () => saveField('webhookSecret', generateWebhookSecret()) }, 'Generate')) }),
|
|
1556
1640
|
h('div', { style: { padding: '16px 0', display: 'flex', flexDirection: 'column', gap: 6 } },
|
|
1557
1641
|
h('p', { style: captionStyle }, 'In GitLab: Settings → Webhooks, Secret token = this value, enable Merge request events. Webhook URL:'),
|
|
1558
1642
|
h('div', { style: { fontFamily: 'ui-monospace, monospace', fontSize: 12, color: t.labelPrimary as string, wordBreak: 'break-all', padding: '10px 12px', borderRadius: 8, background: t.bgLayer3 as string, border: `1px solid ${t.borderL2}`, overflowWrap:'anywhere' as any } }, gitlabWebhookUrl(config.tunnelHostname)),
|
|
@@ -1562,6 +1646,7 @@ export function MaestroSettingsTab({ rpcCall, configRpcCall }: { rpcCall: any; c
|
|
|
1562
1646
|
'div',
|
|
1563
1647
|
{ style: { display: 'flex', flexDirection: 'column' } },
|
|
1564
1648
|
h(ToggleRow as any, { title: 'Re-review on push', description: 'When new commits are pushed, trigger an automatic re-review.', checked: config.autoRereviewOnPush === true, onChange: (v: boolean) => saveField('autoRereviewOnPush', v) }),
|
|
1649
|
+
h(ToggleRow as any, { title: 'Review on assign', description: 'When the bot is assigned as reviewer, trigger an automatic review.', checked: config.autoReviewOnAssign !== false, onChange: (v: boolean) => saveField('autoReviewOnAssign', v) }),
|
|
1565
1650
|
h(SettingRow as any, { title: 'Global review model', description: 'Model for automated reviews. Empty = DSH default.', control: h(ReviewModelSelector as any, { value: config.reviewModel ?? null, catalog, fallbackValue: catalog?.current ?? null, fallbackLabel: 'Use DSH default', onChange: (v: any) => saveField('reviewModel', v), label: null }) }),
|
|
1566
1651
|
h(ProjectMappingsEditor as any, { mappings: config.projectMappings ?? [], onChange: (mappings: any) => saveField('projectMappings', mappings), catalog, globalReviewModel: config.reviewModel ?? null }),
|
|
1567
1652
|
),
|
|
@@ -1606,7 +1691,7 @@ export function MaestroSettingsTab({ rpcCall, configRpcCall }: { rpcCall: any; c
|
|
|
1606
1691
|
'div',
|
|
1607
1692
|
{ style: { display: 'flex', flexDirection: 'column' } },
|
|
1608
1693
|
h('div', { style: { padding: '12px 0', borderBottom: `1px solid ${t.borderL2}` } }, h('p', { style: captionStyle }, 'Telegram bot settings for notifications: startup PIN, review digests, PIN rotation. Leave blank to disable.')),
|
|
1609
|
-
h(SettingRow as any, { title: 'Bot token', description: 'Telegram bot token from @BotFather.', control: h('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } }, h(
|
|
1694
|
+
h(SettingRow as any, { title: 'Bot token', description: 'Telegram bot token from @BotFather.', control: h('div', { style: { display: 'flex', gap: 8, alignItems: 'center' } }, h(SecretField as any, { placeholder: '123456:ABC-DEF...', hasSaved: (notifierCfg.telegram?.botToken ?? '') !== '', width: 220, onSave: (v: string) => saveNotifierCfg({ telegram: { botToken: v } }) }), notifierCfg.telegram?.botToken ? h(Button as any, { variant: 'outline', size: 'md', onClick: () => saveNotifierCfg({ telegram: { botToken: '' } }) }, 'Clear') : null) }),
|
|
1610
1695
|
h(SettingRow as any, { title: 'Chat ID', description: 'Target chat, e.g. -1001234567890.', control: h(FieldInput as any, { value: notifierCfg.telegram?.chatId ?? '', placeholder: '-1001234567890', onChange: (e: any) => saveNotifierCfg({ telegram: { chatId: e.target.value } }), style: { width: 220 } as any }) }),
|
|
1611
1696
|
h(ToggleRow as any, { title: 'Review notifications', description: 'Also notify about finished reviews.', checked: notifierCfg.policy?.reviewNotifications === true, onChange: (v: boolean) => saveNotifierCfg({ policy: { reviewNotifications: v } }) }),
|
|
1612
1697
|
),
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export function MaestroSettingsTab({ rpcCall, configRpcCall }: {
|
|
2
|
-
rpcCall: any;
|
|
3
|
-
configRpcCall: any;
|
|
4
|
-
}): import("react").DetailedReactHTMLElement<{
|
|
5
|
-
'data-maestro-settings-card': string;
|
|
6
|
-
style: {
|
|
7
|
-
maxWidth: number;
|
|
8
|
-
};
|
|
9
|
-
}, HTMLElement>;
|
|
10
|
-
//# sourceMappingURL=maestro-card.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"maestro-card.d.ts","sourceRoot":"","sources":["../../../src/client/maestro-card.jsx"],"names":[],"mappings":"AAweA;;;;;;;;gBA2bC"}
|