dsh-vault 1.2.0-rc.2 → 1.2.0-rc.4

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 CHANGED
@@ -17,80 +17,80 @@ window.__ModuleLoader__.load({
17
17
  document.head.appendChild(tag);
18
18
  }
19
19
  var VaultSection_module_css_default = {
20
- "backupButton": "t_j5va_backupButton",
21
- "footer": "t_j5va_footer",
22
- "saveButton": "t_j5va_saveButton",
23
- "emptyHint": "t_j5va_emptyHint",
20
+ "reportBox": "t_j5va_reportBox",
21
+ "modeAuto": "t_j5va_modeAuto",
24
22
  "status": "t_j5va_status",
25
- "kindIcon": "t_j5va_kindIcon",
26
- "searchBox": "t_j5va_searchBox",
27
- "intro": "t_j5va_intro",
28
- "detailItem": "t_j5va_detailItem",
29
- "policyField": "t_j5va_policyField",
30
- "field": "t_j5va_field",
31
- "pwFair": "t_j5va_pwFair",
32
- "identity": "t_j5va_identity",
33
- "checkField": "t_j5va_checkField",
34
- "pwLabel": "t_j5va_pwLabel",
35
- "dupGroup": "t_j5va_dupGroup",
36
- "modeReadonly": "t_j5va_modeReadonly",
37
- "badge": "t_j5va_badge",
38
- "dupNames": "t_j5va_dupNames",
39
- "badgeWarn": "t_j5va_badgeWarn",
40
- "auditRow": "t_j5va_auditRow",
41
- "genOptRow": "t_j5va_genOptRow",
42
23
  "rowDue": "t_j5va_rowDue",
43
- "deleteButton": "t_j5va_deleteButton",
44
- "detailBox": "t_j5va_detailBox",
45
- "emptyBox": "t_j5va_emptyBox",
24
+ "section": "t_j5va_section",
46
25
  "rowActions": "t_j5va_rowActions",
47
- "kindFilter": "t_j5va_kindFilter",
26
+ "totp": "t_j5va_totp",
27
+ "row": "t_j5va_row",
48
28
  "reportTitle": "t_j5va_reportTitle",
49
- "empty": "t_j5va_empty",
50
- "pwWeak": "t_j5va_pwWeak",
51
- "copied": "t_j5va_copied",
52
- "scoreRing": "t_j5va_scoreRing",
53
- "dangerButton": "t_j5va_dangerButton",
54
29
  "pwBar": "t_j5va_pwBar",
55
- "list": "t_j5va_list",
56
- "error": "t_j5va_error",
30
+ "addButton": "t_j5va_addButton",
31
+ "intro": "t_j5va_intro",
32
+ "reportLine": "t_j5va_reportLine",
33
+ "backupButton": "t_j5va_backupButton",
34
+ "pwMeter": "t_j5va_pwMeter",
35
+ "policyField": "t_j5va_policyField",
36
+ "badge": "t_j5va_badge",
37
+ "detailItem": "t_j5va_detailItem",
38
+ "identity": "t_j5va_identity",
39
+ "kindIcon": "t_j5va_kindIcon",
40
+ "dupMerge": "t_j5va_dupMerge",
41
+ "trashButton": "t_j5va_trashButton",
57
42
  "sortButton": "t_j5va_sortButton",
43
+ "copied": "t_j5va_copied",
44
+ "auditIssues": "t_j5va_auditIssues",
58
45
  "editorBody": "t_j5va_editorBody",
59
- "trashButton": "t_j5va_trashButton",
60
46
  "retryButton": "t_j5va_retryButton",
47
+ "genOptRow": "t_j5va_genOptRow",
48
+ "clearButton": "t_j5va_clearButton",
49
+ "kindFilter": "t_j5va_kindFilter",
50
+ "policyBar": "t_j5va_policyBar",
51
+ "pwFair": "t_j5va_pwFair",
52
+ "checkField": "t_j5va_checkField",
53
+ "error": "t_j5va_error",
54
+ "saveButton": "t_j5va_saveButton",
55
+ "rowMain": "t_j5va_rowMain",
56
+ "emptyBox": "t_j5va_emptyBox",
57
+ "editor": "t_j5va_editor",
58
+ "pinStar": "t_j5va_pinStar",
61
59
  "title": "t_j5va_title",
62
- "badgeOk": "t_j5va_badgeOk",
63
- "dupMerge": "t_j5va_dupMerge",
64
- "reportLine": "t_j5va_reportLine",
65
- "totp": "t_j5va_totp",
66
- "reportSub": "t_j5va_reportSub",
67
- "secretField": "t_j5va_secretField",
60
+ "searchBox": "t_j5va_searchBox",
68
61
  "genOpts": "t_j5va_genOpts",
69
- "dueBadge": "t_j5va_dueBadge",
70
- "rowMain": "t_j5va_rowMain",
62
+ "toolbar": "t_j5va_toolbar",
63
+ "healthBar": "t_j5va_healthBar",
64
+ "badgeDanger": "t_j5va_badgeDanger",
65
+ "dangerButton": "t_j5va_dangerButton",
66
+ "pwLabel": "t_j5va_pwLabel",
71
67
  "revealButton": "t_j5va_revealButton",
72
- "pinStar": "t_j5va_pinStar",
73
- "rowExpired": "t_j5va_rowExpired",
74
- "pwMeter": "t_j5va_pwMeter",
68
+ "badgeWarn": "t_j5va_badgeWarn",
69
+ "field": "t_j5va_field",
70
+ "dupNames": "t_j5va_dupNames",
71
+ "auditRow": "t_j5va_auditRow",
72
+ "totpRing": "t_j5va_totpRing",
73
+ "empty": "t_j5va_empty",
75
74
  "lockedBanner": "t_j5va_lockedBanner",
76
- "modeAuto": "t_j5va_modeAuto",
77
- "clearButton": "t_j5va_clearButton",
78
- "badgeDanger": "t_j5va_badgeDanger",
79
75
  "modeAsk": "t_j5va_modeAsk",
76
+ "dueBadge": "t_j5va_dueBadge",
77
+ "deleteButton": "t_j5va_deleteButton",
78
+ "rowExpired": "t_j5va_rowExpired",
79
+ "secretField": "t_j5va_secretField",
80
80
  "editorActions": "t_j5va_editorActions",
81
- "policyBar": "t_j5va_policyBar",
82
- "pwStrong": "t_j5va_pwStrong",
83
- "healthBar": "t_j5va_healthBar",
84
- "totpRing": "t_j5va_totpRing",
85
- "section": "t_j5va_section",
86
- "toolbar": "t_j5va_toolbar",
87
- "srOnly": "t_j5va_srOnly",
88
81
  "highBadge": "t_j5va_highBadge",
89
- "row": "t_j5va_row",
90
- "reportBox": "t_j5va_reportBox",
91
- "editor": "t_j5va_editor",
92
- "auditIssues": "t_j5va_auditIssues",
93
- "addButton": "t_j5va_addButton"
82
+ "reportSub": "t_j5va_reportSub",
83
+ "modeReadonly": "t_j5va_modeReadonly",
84
+ "list": "t_j5va_list",
85
+ "detailBox": "t_j5va_detailBox",
86
+ "dupGroup": "t_j5va_dupGroup",
87
+ "scoreRing": "t_j5va_scoreRing",
88
+ "badgeOk": "t_j5va_badgeOk",
89
+ "srOnly": "t_j5va_srOnly",
90
+ "footer": "t_j5va_footer",
91
+ "pwWeak": "t_j5va_pwWeak",
92
+ "pwStrong": "t_j5va_pwStrong",
93
+ "emptyHint": "t_j5va_emptyHint"
94
94
  };
95
95
  //#endregion
96
96
  //#region src/client/VaultSection.tsx
@@ -231,7 +231,7 @@ window.__ModuleLoader__.load({
231
231
  }
232
232
  /** Render the Vault settings section. */
233
233
  function VaultSection(props) {
234
- const { t, config, setAccessMode, setAutoCapture, list, search, get, add, update, remove, trash, rotation, health, duplicates, duplicateGroups, merge, history, stats, backupStatus, backup, recent, restore, undeleteAll, totp, status, switchVault, listVaults, touch, verifyAll, breachCheck, generatePassword, strength, generateUsername } = props;
234
+ const { t, config, setAccessMode, setAutoCapture, list, search, get, add, update, remove, trash, rotation, health, duplicates, duplicateGroups, merge, history, stats, backupStatus, backup, recent, restore, undeleteAll, totp, status, switchVault, listVaults, touch, verifyAll, breachCheck, generatePassword, strength, generateUsername, templates, saveTemplate } = props;
235
235
  const searchId = (0, react.useId)();
236
236
  const [query, setQuery] = (0, react.useState)("");
237
237
  const [state, setState] = (0, react.useState)({ status: "loading" });
@@ -255,6 +255,7 @@ window.__ModuleLoader__.load({
255
255
  });
256
256
  const [showGenOpts, setShowGenOpts] = (0, react.useState)(false);
257
257
  const [pwStrength, setPwStrength] = (0, react.useState)(null);
258
+ const [tplList, setTplList] = (0, react.useState)([]);
258
259
  const [kindFilter, setKindFilter] = (0, react.useState)("");
259
260
  const [tagFilter, setTagFilter] = (0, react.useState)("");
260
261
  const [expandedId, setExpandedId] = (0, react.useState)(null);
@@ -276,6 +277,43 @@ window.__ModuleLoader__.load({
276
277
  const [audit, setAudit] = (0, react.useState)([]);
277
278
  const [breach, setBreach] = (0, react.useState)(null);
278
279
  const readonly = policy?.accessMode === "readonly";
280
+ (0, react.useEffect)(() => {
281
+ const url = form.url ?? "";
282
+ if (url.length === 0 || (form.icon ?? "").length > 0) return;
283
+ const host = url.replace(/^https?:\/\//i, "").split("/")[0].toLowerCase();
284
+ const map = {
285
+ github: "🐙",
286
+ gitlab: "🦊",
287
+ google: "🔎",
288
+ "google.com": "🔎",
289
+ amazon: "📦",
290
+ apple: "",
291
+ aws: "☁️",
292
+ azure: "☁️",
293
+ digitalocean: "🐳",
294
+ docker: "🐳",
295
+ npm: "📦",
296
+ figma: "🎨",
297
+ notion: "📝",
298
+ slack: "💬",
299
+ discord: "🎮",
300
+ twitter: "🐦",
301
+ facebook: "👥",
302
+ instagram: "📷",
303
+ linkedin: "💼",
304
+ youtube: "▶️",
305
+ netflix: "🎬",
306
+ spotify: "🎵",
307
+ dropbox: "📁",
308
+ drive: "🗂️"
309
+ };
310
+ const hostBase = host.split(".")[0] ?? "";
311
+ const icon = map[host] ?? map[hostBase] ?? "";
312
+ if (icon) setForm((previous) => ({
313
+ ...previous,
314
+ icon
315
+ }));
316
+ }, [form.url, form.icon]);
279
317
  (0, react.useEffect)(() => {
280
318
  const pw = form.password ?? "";
281
319
  if (pw.length === 0) {
@@ -290,6 +328,44 @@ window.__ModuleLoader__.load({
290
328
  }, 250);
291
329
  return () => window.clearTimeout(timer);
292
330
  }, [form.password, strength]);
331
+ /** Apply a template's field values to the current form. */
332
+ function applyTemplate(name) {
333
+ const tpl = tplList.find((t) => t.name === name);
334
+ if (!tpl) return;
335
+ const next = {};
336
+ if (tpl.kind !== "builtin:custom") next.kind = tpl.kind.replace("builtin:", "");
337
+ for (const [key, value] of Object.entries(tpl.fields)) {
338
+ if (key === "password" || key === "otpSecret" || key === "apiKey" || key === "secret") continue;
339
+ next[key] = value;
340
+ }
341
+ setForm((previous) => ({
342
+ ...previous,
343
+ ...next
344
+ }));
345
+ }
346
+ /** Save the current form as a reusable template. */
347
+ async function saveAsTemplate() {
348
+ const name = window.prompt(t("tplNamePrompt"));
349
+ if (!name) return;
350
+ const fields = {};
351
+ for (const key of [
352
+ "username",
353
+ "email",
354
+ "phone",
355
+ "host",
356
+ "port",
357
+ "url",
358
+ "notes",
359
+ "icon",
360
+ "color"
361
+ ]) {
362
+ const v = form[key];
363
+ if (typeof v === "string" && v.length > 0) fields[key] = v;
364
+ }
365
+ await saveTemplate(name.trim(), form.kind ?? "login", fields);
366
+ await templates().then(setTplList).catch(() => {});
367
+ setMessage(`${t("tplSaved")} ${name.trim()}`);
368
+ }
293
369
  /** Switch the active vault and reload everything. */
294
370
  async function switchVaultTo(name) {
295
371
  setBusy(true);
@@ -363,13 +439,15 @@ window.__ModuleLoader__.load({
363
439
  if (current) setLocked(value.locked);
364
440
  }, () => {});
365
441
  listVaults().then(setVaults).catch(() => {});
442
+ templates().then(setTplList).catch(() => {});
366
443
  return () => {
367
444
  current = false;
368
445
  };
369
446
  }, [
370
447
  config,
371
448
  status,
372
- listVaults
449
+ listVaults,
450
+ templates
373
451
  ]);
374
452
  const refresh = (0, react.useMemo)(() => async () => {
375
453
  setState({ status: "loading" });
@@ -1482,6 +1560,32 @@ window.__ModuleLoader__.load({
1482
1560
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1483
1561
  className: VaultSection_module_css_default.editorBody,
1484
1562
  children: [
1563
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1564
+ className: VaultSection_module_css_default.genOpts,
1565
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1566
+ className: VaultSection_module_css_default.genOptRow,
1567
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("tplApply") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1568
+ value: "",
1569
+ onChange: (event) => {
1570
+ const v = event.target.value;
1571
+ if (v) applyTemplate(v);
1572
+ },
1573
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1574
+ value: "",
1575
+ children: "—"
1576
+ }), tplList.map((tpl) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1577
+ value: tpl.name,
1578
+ children: tpl.name
1579
+ }, tpl.name))]
1580
+ })]
1581
+ }), editor.status === "editing" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1582
+ type: "button",
1583
+ className: VaultSection_module_css_default.retryButton,
1584
+ onClick: () => void saveAsTemplate(),
1585
+ disabled: busy,
1586
+ children: t("tplSave")
1587
+ })]
1588
+ }),
1485
1589
  FORM_FIELDS.map((field) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1486
1590
  className: VaultSection_module_css_default.field,
1487
1591
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t(field.label) }), field.key === "kind" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("select", {
@@ -1775,6 +1879,10 @@ window.__ModuleLoader__.load({
1775
1879
  genOptSymbols: "Symbols",
1776
1880
  genOptExcludeAmbiguous: "No ambiguous chars",
1777
1881
  strengthLabel: "Strength",
1882
+ tplApply: "Apply template",
1883
+ tplSave: "Save as template",
1884
+ tplNamePrompt: "Template name:",
1885
+ tplSaved: "Template saved:",
1778
1886
  httpSites: "HTTP sites",
1779
1887
  healthStrength: "strength",
1780
1888
  dueExpired: "EXPIRED",
@@ -1913,6 +2021,10 @@ window.__ModuleLoader__.load({
1913
2021
  genOptSymbols: "符号",
1914
2022
  genOptExcludeAmbiguous: "排除易混淆字符",
1915
2023
  strengthLabel: "强度",
2024
+ tplApply: "套用模板",
2025
+ tplSave: "存为模板",
2026
+ tplNamePrompt: "模板名称:",
2027
+ tplSaved: "模板已保存:",
1916
2028
  httpSites: "HTTP站点",
1917
2029
  healthStrength: "强度",
1918
2030
  dueExpired: "已过期",
@@ -2043,6 +2155,12 @@ window.__ModuleLoader__.load({
2043
2155
  breachCheck: (online) => invoke("breachCheck", { online: online ?? true }),
2044
2156
  generatePassword: (options) => invoke("generatePassword", { ...options ?? {} }),
2045
2157
  strength: (password) => invoke("strength", { password }),
2158
+ templates: () => invoke("templates"),
2159
+ saveTemplate: (name, kind, fields) => invoke("saveTemplate", {
2160
+ name,
2161
+ kind,
2162
+ fields
2163
+ }),
2046
2164
  generateUsername: () => invoke("generateUsername"),
2047
2165
  duplicateGroups: () => invoke("duplicateGroups"),
2048
2166
  merge: (fromId, toId, keepSource) => invoke("merge", {