hfs 3.3.3 → 3.4.0-alpha1

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.
Files changed (142) hide show
  1. package/admin/assets/AccountsPage-C6J2hRcY.js +4 -0
  2. package/admin/assets/ConfigFilePage-D5TZZyMR.js +4 -0
  3. package/admin/assets/CustomHtmlPage-DV2-ufWZ.js +2 -0
  4. package/admin/assets/InternetPage-DT3wSQYQ.js +6 -0
  5. package/admin/assets/LangPage-DbXCv7wR.js +2 -0
  6. package/admin/assets/LogsPage-DmAxUZpu.js +3 -0
  7. package/admin/assets/MonitorPage-D7ydOeq_.js +2 -0
  8. package/admin/assets/OptionsPage-DpN3ajRm.js +3 -0
  9. package/admin/assets/PluginsPage-BYzfwuvg.js +1 -0
  10. package/admin/assets/icons-CfBG9vvB.js +1 -0
  11. package/admin/assets/index-CVCnYMoB.js +755 -0
  12. package/admin/assets/index-mI9kZlpb.css +1 -0
  13. package/admin/assets/routing-C4l8rnk_.js +1 -0
  14. package/admin/index.html +2 -2
  15. package/central.json +100 -1
  16. package/frontend/assets/{dist-legacy-CSuZ0eoA.js → dist-legacy-CKczq_fx.js} +1 -1
  17. package/frontend/assets/index-legacy-ZYae30AY.js +1 -0
  18. package/frontend/assets/{lib-legacy-I77jyH6j.js → lib-legacy-D2tyILlj.js} +1 -1
  19. package/frontend/assets/{sha512-legacy-DxvM1xc4.js → sha512-legacy-CFcDeiiP.js} +1 -1
  20. package/frontend/index.html +1 -1
  21. package/npm-shrinkwrap.json +6 -6
  22. package/package.json +2 -2
  23. package/src/acme.js +143 -50
  24. package/src/acmeConfig.js +43 -0
  25. package/src/acmeDns.js +285 -0
  26. package/src/admin-langs/embedded.js +33 -0
  27. package/src/admin-langs/hfs-admin-lang-ar.json +1141 -0
  28. package/src/admin-langs/hfs-admin-lang-bg.json +1141 -0
  29. package/src/admin-langs/hfs-admin-lang-de.json +1141 -0
  30. package/src/admin-langs/hfs-admin-lang-el.json +1141 -0
  31. package/src/admin-langs/hfs-admin-lang-en.json +1141 -0
  32. package/src/admin-langs/hfs-admin-lang-es.json +1141 -0
  33. package/src/admin-langs/hfs-admin-lang-fi.json +1141 -0
  34. package/src/admin-langs/hfs-admin-lang-fr.json +1141 -0
  35. package/src/admin-langs/hfs-admin-lang-hu.json +1141 -0
  36. package/src/admin-langs/hfs-admin-lang-it.json +1141 -0
  37. package/src/admin-langs/hfs-admin-lang-ja.json +1141 -0
  38. package/src/admin-langs/hfs-admin-lang-ko.json +1141 -0
  39. package/src/admin-langs/hfs-admin-lang-lt.json +1141 -0
  40. package/src/admin-langs/hfs-admin-lang-ms.json +1141 -0
  41. package/src/admin-langs/hfs-admin-lang-nl.json +1141 -0
  42. package/src/admin-langs/hfs-admin-lang-pl.json +1141 -0
  43. package/src/admin-langs/hfs-admin-lang-pt-br.json +1141 -0
  44. package/src/admin-langs/hfs-admin-lang-ro.json +1141 -0
  45. package/src/admin-langs/hfs-admin-lang-ru.json +1141 -0
  46. package/src/admin-langs/hfs-admin-lang-sr-latn.json +1141 -0
  47. package/src/admin-langs/hfs-admin-lang-sr.json +1141 -0
  48. package/src/admin-langs/hfs-admin-lang-th.json +1141 -0
  49. package/src/admin-langs/hfs-admin-lang-tr.json +1141 -0
  50. package/src/admin-langs/hfs-admin-lang-uk.json +1141 -0
  51. package/src/admin-langs/hfs-admin-lang-vi.json +1141 -0
  52. package/src/admin-langs/hfs-admin-lang-zh-tw.json +1141 -0
  53. package/src/admin-langs/hfs-admin-lang-zh.json +1141 -0
  54. package/src/adminApis.js +2 -2
  55. package/src/adminLang.js +50 -0
  56. package/src/api.lang.js +22 -13
  57. package/src/api.log.js +5 -3
  58. package/src/api.net.js +20 -3
  59. package/src/api.plugins.js +53 -19
  60. package/src/argv.js +4 -0
  61. package/src/auth.js +0 -2
  62. package/src/basicWeb.js +6 -2
  63. package/src/block.js +1 -1
  64. package/src/config.js +1 -0
  65. package/src/connections.js +4 -7
  66. package/src/const.js +1 -1
  67. package/src/cross-const.js +7 -5
  68. package/src/cross.js +15 -1
  69. package/src/frontEndApis.js +44 -7
  70. package/src/github.js +6 -37
  71. package/src/i18n.js +16 -4
  72. package/src/index.js +4 -2
  73. package/src/lang.js +15 -8
  74. package/src/langs/embedded.js +2 -1
  75. package/src/langs/hfs-lang-ar.json +5 -1
  76. package/src/langs/hfs-lang-bg.json +5 -1
  77. package/src/langs/hfs-lang-de.json +5 -1
  78. package/src/langs/hfs-lang-el.json +5 -1
  79. package/src/langs/hfs-lang-en.json +188 -0
  80. package/src/langs/hfs-lang-es.json +7 -3
  81. package/src/langs/hfs-lang-fi.json +6 -2
  82. package/src/langs/hfs-lang-fr.json +10 -6
  83. package/src/langs/hfs-lang-hu.json +5 -1
  84. package/src/langs/hfs-lang-it.json +9 -4
  85. package/src/langs/hfs-lang-ja.json +5 -1
  86. package/src/langs/hfs-lang-ko.json +5 -1
  87. package/src/langs/hfs-lang-lt.json +5 -1
  88. package/src/langs/hfs-lang-ms.json +7 -1
  89. package/src/langs/hfs-lang-nl.json +5 -1
  90. package/src/langs/hfs-lang-pl.json +5 -1
  91. package/src/langs/hfs-lang-pt-br.json +7 -3
  92. package/src/langs/hfs-lang-ro.json +5 -1
  93. package/src/langs/hfs-lang-ru.json +7 -3
  94. package/src/langs/hfs-lang-sr-latn.json +5 -1
  95. package/src/langs/hfs-lang-sr.json +5 -1
  96. package/src/langs/hfs-lang-th.json +6 -2
  97. package/src/langs/hfs-lang-tr.json +6 -2
  98. package/src/langs/hfs-lang-uk.json +14 -10
  99. package/src/langs/hfs-lang-vi.json +5 -1
  100. package/src/langs/hfs-lang-zh-tw.json +5 -1
  101. package/src/langs/hfs-lang-zh.json +5 -1
  102. package/src/log.js +7 -1
  103. package/src/middlewares.js +85 -14
  104. package/src/multipartUpload.js +21 -0
  105. package/src/perm.js +38 -20
  106. package/src/pluginCatalog.js +66 -0
  107. package/src/pluginUsage.js +64 -0
  108. package/src/plugins.js +12 -0
  109. package/src/roots.js +22 -13
  110. package/src/serveFile.js +26 -5
  111. package/src/serveGuiFiles.js +5 -2
  112. package/src/update.js +51 -18
  113. package/src/upload.js +49 -8
  114. package/src/util-http.js +31 -5
  115. package/src/vfs.js +84 -4
  116. package/src/webdav.js +12 -6
  117. package/admin/assets/AccountsPage-C_ayTmn7.js +0 -3
  118. package/admin/assets/ArrayField-B20NUH0C.js +0 -2
  119. package/admin/assets/ConfigFilePage-Dw0-gus3.js +0 -4
  120. package/admin/assets/CustomHtmlPage-CLGyZfZ8.js +0 -2
  121. package/admin/assets/DataTable-BwyFEGxv.js +0 -119
  122. package/admin/assets/DateTimeField-ZUy9IBhM.js +0 -1
  123. package/admin/assets/EventManager-DomWgRty.js +0 -1
  124. package/admin/assets/FileField-Bj3P7JUc.js +0 -117
  125. package/admin/assets/InternetPage-CGMQCcBM.js +0 -8
  126. package/admin/assets/LangPage-DGFrOItT.js +0 -2
  127. package/admin/assets/LogsPage-CvCYNhID.js +0 -4
  128. package/admin/assets/MenuButton-BTRAUH7H.js +0 -1
  129. package/admin/assets/MonitorPage-CVa2ILH2.js +0 -2
  130. package/admin/assets/OptionsPage-C3cprG9u.js +0 -3
  131. package/admin/assets/PluginsPage-CIOnncph.js +0 -1
  132. package/admin/assets/Tabs-zcHwsK2s.js +0 -1
  133. package/admin/assets/TextEditor-BeS-mR2I.css +0 -1
  134. package/admin/assets/TextEditor-CDOjRy40.js +0 -34
  135. package/admin/assets/VfsPathField-DHifSPJi.js +0 -1
  136. package/admin/assets/agentIcons-DB1m4pGa.js +0 -1
  137. package/admin/assets/baseUrl-Bt9R8Da2.js +0 -2
  138. package/admin/assets/icons-DR_Rvxtn.js +0 -1
  139. package/admin/assets/index-11n5z0Ez.css +0 -1
  140. package/admin/assets/index-B3difDBD.js +0 -477
  141. package/admin/assets/routing-CBrDVeFB.js +0 -1
  142. package/frontend/assets/index-legacy-tk5ZmBDE.js +0 -1
package/src/update.js CHANGED
@@ -10,7 +10,7 @@ exports.getVersions = getVersions;
10
10
  exports.getUpdates = getUpdates;
11
11
  exports.localUpdateAvailable = localUpdateAvailable;
12
12
  exports.previousAvailable = previousAvailable;
13
- exports.updateSupported = updateSupported;
13
+ exports.updateNotSupported = updateNotSupported;
14
14
  exports.update = update;
15
15
  const github_1 = require("./github");
16
16
  const const_1 = require("./const");
@@ -29,6 +29,7 @@ const lodash_1 = __importDefault(require("lodash"));
29
29
  const argv_1 = require("./argv");
30
30
  const promises_2 = require("stream/promises");
31
31
  const updateChangelog_1 = require("./updateChangelog");
32
+ const fast_xml_parser_1 = require("fast-xml-parser");
32
33
  const updateToBeta = (0, config_1.defineConfig)(misc_1.CFG.update_to_beta, false);
33
34
  const autoCheckUpdate = (0, config_1.defineConfig)(misc_1.CFG.auto_check_update, true);
34
35
  const lastCheckUpdate = persistence_1.storedMap.singleSync('lastCheckUpdate', 0);
@@ -59,15 +60,16 @@ exports.checkForUpdates = (0, misc_1.debounceAsync)(async () => {
59
60
  async function getBestUpdate() {
60
61
  return (await getUpdates(true))[0];
61
62
  }
62
- const ReleaseKeys = ['prerelease', 'tag_name', 'name', 'body', 'assets', 'isNewer', 'versionScalar'];
63
- const ReleaseAssetKeys = ['name', 'browser_download_url'];
63
+ const releaseKeys = ['prerelease', 'tag_name', 'name', 'body', 'assets', 'isNewer', 'versionScalar'];
64
+ const releaseAssetKeys = ['name', 'browser_download_url'];
65
+ const assetPrefix = `hfs-${(0, misc_1.xlate)(process.platform, { win32: 'windows', darwin: 'mac' })}-${process.arch}`;
64
66
  const curV = config_1.currentVersion.scalar;
65
67
  function prepareRelease(r) {
66
68
  const v = (0, config_1.versionToScalar)(r.name);
67
- return Object.assign(lodash_1.default.pick(r, ReleaseKeys), {
69
+ return Object.assign(lodash_1.default.pick(r, releaseKeys), {
68
70
  versionScalar: v,
69
71
  isNewer: v > curV, // make easy to know what's newer
70
- assets: r.assets.map((a) => lodash_1.default.pick(a, ReleaseAssetKeys))
72
+ assets: r.assets.map((a) => lodash_1.default.pick(a, releaseAssetKeys))
71
73
  });
72
74
  }
73
75
  async function getVersions(filter, max = 30, stopAtCurrent = false) {
@@ -91,10 +93,19 @@ async function getVersions(filter, max = 30, stopAtCurrent = false) {
91
93
  async function getUpdates(strict = false) {
92
94
  console.log("Checking for updates");
93
95
  void (0, github_1.getProjectInfo)(); // also check for alerts and print them asap in the console
94
- const stable = prepareRelease(await (0, github_1.getRepoInfo)(const_1.HFS_REPO + '/releases/latest'));
95
96
  const includeBetas = updateToBeta.get() || const_1.RUNNING_BETA;
96
- // we don't consider betas before stable
97
- const betas = !includeBetas ? [] : await getVersions(x => x.prerelease && x.versionScalar > stable.versionScalar && (!strict || x.isNewer));
97
+ const latest = await (0, misc_1.httpStream)(`https://github.com/${const_1.HFS_REPO}/releases/latest`, { method: 'HEAD', noRedirect: true });
98
+ const stableTag = latest.headers.location?.split('/').at(-1);
99
+ if (!stableTag)
100
+ throw Error('Cannot determine latest stable version');
101
+ if ((0, config_1.versionToScalar)(stableTag) === curV && !includeBetas)
102
+ return [];
103
+ let stable = prepareRelease({ prerelease: false, tag_name: stableTag, name: stableTag, body: '', assets: [probableAsset(stableTag)], isNewer: false, versionScalar: 0 });
104
+ if (stable.isNewer || const_1.RUNNING_BETA && !strict)
105
+ stable = await (0, github_1.getRepoInfo)(const_1.HFS_REPO + '/releases/tags/' + stableTag).then(prepareRelease).catch(() => stable);
106
+ const betas = !includeBetas ? [] : await getVersions(x => x.prerelease && x.versionScalar > stable.versionScalar && (!strict || x.isNewer))
107
+ // preserve update detection when GitHub's REST quota is exhausted
108
+ .catch(() => getVersionsFromAtom(x => x.prerelease && x.versionScalar > stable.versionScalar && (!strict || x.isNewer)));
98
109
  if (stable.isNewer || const_1.RUNNING_BETA && !strict)
99
110
  betas.push(stable);
100
111
  if (stable.isNewer && stable.body) {
@@ -102,7 +113,28 @@ async function getUpdates(strict = false) {
102
113
  .catch(() => []); // missing history must not prevent installing an available update
103
114
  stable.body = (0, updateChangelog_1.updateChangelog)(stable, history, curV);
104
115
  }
105
- return betas;
116
+ return lodash_1.default.sortBy(betas, x => -x.versionScalar);
117
+ async function getVersionsFromAtom(filter) {
118
+ const feed = new fast_xml_parser_1.XMLParser({ ignoreAttributes: false })
119
+ .parse(await (0, misc_1.httpString)(`https://github.com/${const_1.HFS_REPO}/releases.atom`))?.feed;
120
+ return lodash_1.default.sortBy(lodash_1.default.castArray(feed?.entry)
121
+ .map((x) => prepareRelease({
122
+ prerelease: /^v?\d+\.\d+\.\d+-/.test(x.title),
123
+ tag_name: x.id.split('/').at(-1),
124
+ name: x.title,
125
+ body: x.content?.['#text'] || '',
126
+ assets: [probableAsset(x.id.split('/').at(-1))],
127
+ isNewer: false,
128
+ versionScalar: 0,
129
+ }))
130
+ .filter(x => Number.isFinite(x.versionScalar) && !x.name.endsWith('-ignore')
131
+ && x.versionScalar !== curV && filter(x)), x => -x.versionScalar);
132
+ }
133
+ function probableAsset(tag) {
134
+ const name = `${assetPrefix}-${tag.replace(/^v/, '')}.zip`;
135
+ // release-assets.yml uses this predictable name, keeping degraded checks installable
136
+ return { name, browser_download_url: `https://github.com/${const_1.HFS_REPO}/releases/download/${tag}/${name}` };
137
+ }
106
138
  }
107
139
  const LOCAL_UPDATE = 'hfs-update.zip'; // update from file takes precedence over net
108
140
  const INSTALLED_FN = 'hfs-installed.zip';
@@ -113,13 +145,16 @@ function localUpdateAvailable() {
113
145
  function previousAvailable() {
114
146
  return (0, misc_1.exists)(PREVIOUS_FN);
115
147
  }
116
- function updateSupported() {
117
- return !process.env.DISABLE_UPDATE && (argv_1.argv.forceupdate || const_1.IS_BINARY);
148
+ function updateNotSupported() {
149
+ return process.env.DISABLE_UPDATE ? "Automatic updates are disabled"
150
+ : !(argv_1.argv.forceupdate || const_1.IS_BINARY) ? "Only binary versions support automatic updates"
151
+ : (0, promises_1.access)((0, path_1.dirname)(process.execPath), fs_1.constants.W_OK | fs_1.constants.X_OK)
152
+ .then(() => '', () => `Executable directory is not writable: ${(0, path_1.dirname)(process.execPath)}`);
118
153
  }
119
154
  async function update(tagOrUrl = '') {
120
- if (!updateSupported())
121
- throw process.env.DISABLE_UPDATE ? "Automatic updates are disabled"
122
- : "Only binary versions support automatic updates";
155
+ const error = await updateNotSupported();
156
+ if (error)
157
+ throw error;
123
158
  let url = tagOrUrl.includes('://') && tagOrUrl;
124
159
  if (tagOrUrl === const_1.PREVIOUS_TAG)
125
160
  await (0, promises_1.rename)(PREVIOUS_FN, LOCAL_UPDATE);
@@ -135,11 +170,9 @@ async function update(tagOrUrl = '') {
135
170
  });
136
171
  if (!update)
137
172
  throw "No update has been found";
138
- const plat = '-' + (0, misc_1.xlate)(process.platform, { win32: 'windows', darwin: 'mac' });
139
- const assetSearch = `${plat}-${process.arch}`;
140
- const asset = update.assets.find((x) => x.name.includes(assetSearch) && x.name.endsWith('.zip'));
173
+ const asset = update.assets.find((x) => x.name.includes(assetPrefix) && x.name.endsWith('.zip'));
141
174
  if (!asset)
142
- throw `Asset not found: ${assetSearch}`;
175
+ throw `Asset not found: ${assetPrefix}`;
143
176
  url = asset.browser_download_url;
144
177
  }
145
178
  if (url) {
package/src/upload.js CHANGED
@@ -5,7 +5,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.dontOverwriteUploading = exports.minAvailableMb = exports.deleteUnfinishedUploadsAfter = void 0;
7
7
  exports.getUploadMeta = getUploadMeta;
8
+ exports.saveUploadMeta = saveUploadMeta;
9
+ exports.setUploadApproved = setUploadApproved;
8
10
  exports.getUploadTempFor = getUploadTempFor;
11
+ exports.isUploading = isUploading;
12
+ exports.whileUploadMetaPending = whileUploadMetaPending;
9
13
  exports.uploadWriter = uploadWriter;
10
14
  const vfs_1 = require("./vfs");
11
15
  const const_1 = require("./const");
@@ -25,6 +29,7 @@ const expiringCache_1 = require("./expiringCache");
25
29
  const first_1 = require("./first");
26
30
  const uploadOwners_1 = require("./uploadOwners");
27
31
  const webdav_1 = require("./webdav");
32
+ const adminApis_1 = require("./adminApis");
28
33
  exports.deleteUnfinishedUploadsAfter = (0, config_1.defineConfig)(misc_1.CFG.delete_unfinished_uploads_after, 86_400);
29
34
  exports.minAvailableMb = (0, config_1.defineConfig)(misc_1.CFG.min_available_mb, 100);
30
35
  exports.dontOverwriteUploading = (0, config_1.defineConfig)(misc_1.CFG.dont_overwrite_uploading, true);
@@ -56,10 +61,18 @@ const ATTR_UPLOADER = 'uploader';
56
61
  function getUploadMeta(path) {
57
62
  return (0, misc_1.loadFileAttr)(path, ATTR_UPLOADER);
58
63
  }
59
- function setUploadMeta(path, ctx) {
60
- return (0, misc_1.storeFileAttr)(path, ATTR_UPLOADER, {
64
+ function saveUploadMeta(path, meta) {
65
+ return (0, misc_1.storeFileAttr)(path, ATTR_UPLOADER, meta);
66
+ }
67
+ async function setUploadApproved(path, approved) {
68
+ const meta = await getUploadMeta(path);
69
+ return Boolean(meta && await saveUploadMeta(path, { ...meta, approved }));
70
+ }
71
+ function fillUploadMeta(path, ctx) {
72
+ return saveUploadMeta(path, {
61
73
  username: (0, auth_1.getCurrentUsername)(ctx) || undefined,
62
74
  ip: ctx.ip,
75
+ approved: (0, adminApis_1.ctxAdminAccess)(ctx) || undefined,
63
76
  });
64
77
  }
65
78
  function getUploadTempFor(fullPath) {
@@ -67,6 +80,24 @@ function getUploadTempFor(fullPath) {
67
80
  }
68
81
  const diskSpaceCache = (0, expiringCache_1.expiringCache)(3_000); // invalidate shortly
69
82
  const uploadingFiles = new Map();
83
+ const uploadMetaPending = new Set();
84
+ function isUploading(path) {
85
+ const key = (0, vfs_1.normalizeFilename)(path);
86
+ return (0, path_1.basename)(path).startsWith(const_1.UPLOAD_TEMP_PREFIX)
87
+ || uploadingFiles.has(key)
88
+ || uploadingFiles.has((0, vfs_1.normalizeFilename)(getUploadTempFor(path))) // active uploads are keyed by their temporary file
89
+ || uploadMetaPending.has(key);
90
+ }
91
+ async function whileUploadMetaPending(path, cb) {
92
+ const key = (0, vfs_1.normalizeFilename)(path);
93
+ uploadMetaPending.add(key);
94
+ try {
95
+ return await cb();
96
+ }
97
+ finally {
98
+ uploadMetaPending.delete(key);
99
+ }
100
+ }
70
101
  // initially sync for formidable; still sync to avoid async races and PUT piping gaps
71
102
  function uploadWriter(base, baseUri, filename, ctx) {
72
103
  if (!filename || !(0, misc_1.isValidFileName)(filename) || !filename)
@@ -123,7 +154,7 @@ function uploadWriter(base, baseUri, filename, ctx) {
123
154
  const createdFolders = [];
124
155
  const firstCreated = !dir.endsWith(':\\') && fs_1.default.mkdirSync(dir, { recursive: true });
125
156
  if (firstCreated) {
126
- setUploadMeta(dir, ctx);
157
+ fillUploadMeta(dir, ctx);
127
158
  // recursive mkdir returns the first created directory; existing ancestors must not receive ownership
128
159
  const firstCreatedPath = (0, path_1.toNamespacedPath)((0, path_1.resolve)(firstCreated)); // Node 24 mkdir can return a namespace-prefixed Windows path
129
160
  let source = (0, path_1.resolve)(dir);
@@ -219,12 +250,16 @@ function uploadWriter(base, baseUri, filename, ctx) {
219
250
  await new Promise(res => fileStream.close(res)); // this only seems necessary on Windows
220
251
  for (const { uri, source } of createdFolders)
221
252
  await (0, uploadOwners_1.setUploadOwner)(uri, ctx, source);
222
- if (errored)
223
- return;
224
253
  if (simulate)
225
254
  return (0, promises_1.rm)(tempName).catch(() => { });
255
+ if (!await fillUploadMeta(tempName, ctx)) {
256
+ await (0, promises_1.rm)(tempName).catch(() => { });
257
+ return fail(const_1.HTTP_SERVER_ERROR);
258
+ }
259
+ if (errored)
260
+ return;
226
261
  if (ctx.isAborted()) { // in the very unlikely case the connection is interrupted between last-byte and here, we still consider it unfinished, as the client had no way to know, and will resume, but it would get an error if we finish the process
227
- const sec = exports.deleteUnfinishedUploadsAfter.get();
262
+ const sec = first_1.quitting ? undefined : exports.deleteUnfinishedUploadsAfter.get(); // preserve uploads interrupted by shutdown for resuming
228
263
  await (0, uploadOwners_1.setUploadOwner)(tempOwnerUri, ctx, tempName, lodash_1.default.isNumber(sec) ? Date.now() + sec * 1000 : null);
229
264
  return lodash_1.default.isNumber(sec) && delayedDelete(tempName, sec, tempOwnerUri);
230
265
  }
@@ -254,11 +289,16 @@ function uploadWriter(base, baseUri, filename, ctx) {
254
289
  () => (0, promises_1.rename)(tempName, dest).then(() => true, e => e?.code !== 'EBUSY' && Promise.reject(e)), { timeout: 10_000 });
255
290
  if (!done)
256
291
  throw 'EBUSY';
292
+ await (0, misc_1.moveStoredFileAttrs)(tempName, dest);
257
293
  (0, uploadOwners_1.deleteUploadOwner)(tempOwnerUri); // the temp URI no longer exists after rename; final ownership is recorded below
258
294
  if (mtime) // so we use it to touch the file
259
295
  await (0, promises_1.utimes)(dest, Date.now() / 1000, mtime / 1000);
260
296
  obj.fullPath = ctx.state.uploadDestinationPath = dest;
261
- void setUploadMeta(dest, ctx);
297
+ if (!await fillUploadMeta(dest, ctx)) {
298
+ await (0, promises_1.rename)(dest, tempName);
299
+ await (0, misc_1.moveStoredFileAttrs)(dest, tempName);
300
+ throw Error("couldn't store upload metadata");
301
+ }
262
302
  if (ctx.query.comment)
263
303
  void (0, comments_1.setCommentFor)(dest, String(ctx.query.comment));
264
304
  obj.uri = (0, misc_1.enforceFinal)('/', baseUri) + (0, misc_1.pathEncode)((0, path_1.basename)(dest));
@@ -272,8 +312,9 @@ function uploadWriter(base, baseUri, filename, ctx) {
272
312
  cb(obj);
273
313
  }
274
314
  catch (err) {
275
- void setUploadMeta(tempName, ctx);
315
+ void fillUploadMeta(tempName, ctx);
276
316
  console.error("Couldn't rename temp to", dest, String(err));
317
+ fail(const_1.HTTP_SERVER_ERROR);
277
318
  }
278
319
  }
279
320
  finally {
package/src/util-http.js CHANGED
@@ -38,6 +38,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
38
38
  };
39
39
  Object.defineProperty(exports, "__esModule", { value: true });
40
40
  exports.stream2string = void 0;
41
+ exports.readTextLimited = readTextLimited;
42
+ exports.readRequestBodyLimited = readRequestBodyLimited;
41
43
  exports.httpString = httpString;
42
44
  exports.httpWithBody = httpWithBody;
43
45
  exports.httpStream = httpStream;
@@ -51,6 +53,29 @@ const consumers_1 = require("node:stream/consumers");
51
53
  Object.defineProperty(exports, "stream2string", { enumerable: true, get: function () { return consumers_1.text; } });
52
54
  const tls = __importStar(require("node:tls"));
53
55
  const cross_1 = require("./cross");
56
+ const cross_const_1 = require("./cross-const");
57
+ async function readTextLimited(stream, maxBytes) {
58
+ if (Number(stream.headers['content-length']) > maxBytes)
59
+ return { error: 'too_large' };
60
+ const chunks = [];
61
+ let size = 0;
62
+ // keep the socket alive so callers can send the rejection before closing an undrained request
63
+ for await (const chunk of stream.iterator({ destroyOnReturn: false })) {
64
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
65
+ size += buffer.length;
66
+ if (size > maxBytes)
67
+ return { error: 'too_large' };
68
+ chunks.push(buffer);
69
+ }
70
+ return { text: new TextDecoder().decode(Buffer.concat(chunks, size)) };
71
+ }
72
+ async function readRequestBodyLimited(ctx, maxBytes) {
73
+ const body = await readTextLimited(ctx.req, maxBytes);
74
+ if (!('error' in body))
75
+ return body;
76
+ ctx.set('Connection', 'close'); // an unread rejected body makes this connection unsafe to reuse
77
+ ctx.status = cross_const_1.HTTP_PAYLOAD_TOO_LARGE;
78
+ }
54
79
  async function httpString(url, { maxBytes = 10 * cross_1.MB, ...options } = {}) {
55
80
  const stream = await httpStream(url, options);
56
81
  if (maxBytes === Infinity)
@@ -85,12 +110,13 @@ function httpStream(url, { body, proxy, jar, noRedirect, httpThrow = true, ...op
85
110
  options.headers['user-agent'] = httpStream.defaultUA;
86
111
  if (body) {
87
112
  options.method ||= 'POST';
88
- if (lodash_1.default.isPlainObject(body)) {
89
- options.headers['content-type'] ??= 'application/json';
90
- body = JSON.stringify(body);
91
- }
92
- if (!(body instanceof node_stream_1.Readable))
113
+ if (!(body instanceof node_stream_1.Readable)) {
114
+ if (typeof body === 'object' && !(body instanceof Buffer)) {
115
+ options.headers['content-type'] ??= 'application/json';
116
+ body = JSON.stringify(body);
117
+ }
93
118
  options.headers['content-length'] ??= Buffer.byteLength(body);
119
+ }
94
120
  }
95
121
  const { auth, ...parsed } = parseHttpUrl(url);
96
122
  const hostJar = jar && (jar[parsed.hostname || ''] ||= {});
package/src/vfs.js CHANGED
@@ -42,6 +42,7 @@ const walkDir_1 = require("./walkDir");
42
42
  const node_stream_1 = require("node:stream");
43
43
  const adminApis_1 = require("./adminApis");
44
44
  const showHiddenFiles = (0, config_1.defineConfig)(misc_1.CFG.show_hidden_files, false);
45
+ const sourceNames = new Map();
45
46
  function setVfsPath(node, vfsPath, parent) {
46
47
  return (0, misc_1.setHidden)(node, {
47
48
  vfsPath: (0, misc_1.enforceFinal)('/', parent?.vfsPath) + (0, misc_1.pathEncode)(vfsPath)
@@ -189,6 +190,27 @@ async function getNodeByName(name, parent, assumeMissingToBeFolder = false) {
189
190
  // does the tree node have a child that goes by this name, otherwise attempt disk
190
191
  let virtualName = name;
191
192
  let child = parent.children?.find(isSameFilenameAs(name));
193
+ const sameName = isSameFilenameAs(name);
194
+ const sourceName = Object.entries(parent.rename || {}).find(([, to]) => sameName(to))?.[0] || name;
195
+ if (!child) {
196
+ child = findSourceChild();
197
+ if (!child && const_1.IS_WINDOWS && parent.source) {
198
+ const aliases = await windowsPathNames((0, path_1.join)(parent.source, sourceName), parent.source);
199
+ if (!aliases)
200
+ return;
201
+ child = findSourceChild(aliases);
202
+ if (!child) {
203
+ const normalized = new Set(aliases.map(normalizeFilename));
204
+ const candidates = parent.children?.filter(candidate => candidate.source
205
+ && normalized.has(normalizeFilename((0, path_1.basename)(candidate.source)))) || [];
206
+ if (candidates.length > 1)
207
+ return; // an unverified physical alias must identify one VFS child
208
+ const [candidate] = candidates;
209
+ if (candidate?.source && await isSameFilePath((0, path_1.dirname)(candidate.source), parent.source))
210
+ child = candidate;
211
+ }
212
+ }
213
+ }
192
214
  if (child) { // found as vfs node
193
215
  virtualName = getNodeName(child);
194
216
  await setIsFolder(child); // in case it's pointing to a folder that didn't exist at loading time
@@ -202,7 +224,6 @@ async function getNodeByName(name, parent, assumeMissingToBeFolder = false) {
202
224
  const ret = {};
203
225
  let onDisk = name;
204
226
  if (parent.rename) { // reverse the mapping
205
- const sameName = isSameFilenameAs(name);
206
227
  const entries = Object.entries(parent.rename);
207
228
  // search display names first: with { A: B, C: A }, A must resolve to C rather than be hidden
208
229
  const asDisplayName = entries.find(([, to]) => sameName(to));
@@ -211,12 +232,17 @@ async function getNodeByName(name, parent, assumeMissingToBeFolder = false) {
211
232
  virtualName = asDisplayName[1];
212
233
  }
213
234
  else {
214
- const asPhysicalName = entries.find(([from]) => sameName(from));
235
+ if (!await canonicalizeWindowsAlias())
236
+ return;
237
+ const sameSourceName = isSameFilenameAs(onDisk);
238
+ const asPhysicalName = entries.find(([from]) => sameSourceName(from));
215
239
  if (asPhysicalName)
216
240
  return; // a VFS rename replaces the original public name
217
241
  }
218
242
  ret.rename = renameUnderPath(parent.rename, onDisk);
219
243
  }
244
+ else if (!await canonicalizeWindowsAlias())
245
+ return;
220
246
  if (!(0, misc_1.isValidFileName)(onDisk))
221
247
  return;
222
248
  ret.source = (0, path_1.join)(parent.source, onDisk);
@@ -225,6 +251,22 @@ async function getNodeByName(name, parent, assumeMissingToBeFolder = false) {
225
251
  if (assumeMissingToBeFolder)
226
252
  ret.isFolder ??= true;
227
253
  return ret;
254
+ async function canonicalizeWindowsAlias() {
255
+ if (!const_1.IS_WINDOWS || !name.includes('~'))
256
+ return true;
257
+ // Windows exposes 8.3 aliases that must keep the long name's VFS identity
258
+ const longPath = await convertWindowsPath((0, path_1.join)(parent.source, name), true);
259
+ if (!longPath)
260
+ return true; // new names containing '~' are still valid
261
+ if (!await isSameFilePath((0, path_1.dirname)(longPath), parent.source))
262
+ return false;
263
+ onDisk = virtualName = (0, path_1.basename)(longPath);
264
+ return true;
265
+ }
266
+ }
267
+ function findSourceChild(names = [sourceName]) {
268
+ const normalized = new Set(names.map(normalizeFilename));
269
+ return parent.children?.find(x => getSourceNames(x, parent).some(source => normalized.has(normalizeFilename(source))));
228
270
  }
229
271
  }
230
272
  const smartUncFolderDetection = (0, config_1.defineConfig)(misc_1.CFG.smart_unc_folder_detection, false);
@@ -261,8 +303,42 @@ async function reviewVfs() {
261
303
  usedNames.add(normalizeFilename(getNodeName(child)));
262
304
  }
263
305
  await Promise.allSettled(node.children.map(recur));
306
+ await Promise.allSettled(node.children.map(child => setSourceNames(child, node)));
264
307
  })(exports.vfs.compiled());
265
308
  }
309
+ async function setSourceNames(node, parent) {
310
+ if (!node.source || !parent.source)
311
+ return;
312
+ const sameParent = normalizeFilename((0, path_1.dirname)((0, path_1.resolve)(node.source))) === normalizeFilename((0, path_1.resolve)(parent.source));
313
+ if (!sameParent && (!const_1.IS_WINDOWS || !await isSameFilePath((0, path_1.dirname)(node.source), parent.source)))
314
+ return;
315
+ const names = [(0, path_1.basename)(node.source)];
316
+ const key = sourceNamesKey(node, parent);
317
+ if (const_1.IS_WINDOWS) {
318
+ const converted = await Promise.all([convertWindowsPath(node.source), convertWindowsPath(node.source, true)]);
319
+ names.push(...converted.map(x => x && (0, path_1.basename)(x)).filter(Boolean));
320
+ }
321
+ sourceNames.set(key, lodash_1.default.uniq(names));
322
+ }
323
+ function convertWindowsPath(path, isLong = false) {
324
+ return new Promise(resolvePromise => {
325
+ if (!fswin_1.default.convertPath(path, resolvePromise, isLong))
326
+ resolvePromise(undefined);
327
+ });
328
+ }
329
+ async function windowsPathNames(path, parent) {
330
+ const converted = await Promise.all([convertWindowsPath(path), convertWindowsPath(path, true)]);
331
+ const existing = converted.find(Boolean);
332
+ if (existing && !await isSameFilePath((0, path_1.dirname)(existing), parent))
333
+ return;
334
+ return lodash_1.default.uniq([(0, path_1.basename)(path), ...converted.map(x => x && (0, path_1.basename)(x)).filter(Boolean)]);
335
+ }
336
+ function getSourceNames(node, parent) {
337
+ return node.source && parent.source ? sourceNames.get(sourceNamesKey(node, parent)) || [] : [];
338
+ }
339
+ function sourceNamesKey(node, parent) {
340
+ return normalizeFilename((0, path_1.resolve)(parent.source)) + '\0' + normalizeFilename((0, path_1.resolve)(node.source));
341
+ }
266
342
  exports.saveVfs = (0, misc_1.debounceAsync)(async () => {
267
343
  await reviewVfs(); // refresh runtime-derived folder flags before saving mutated VFS state
268
344
  exports.vfs.set(exports.vfs.compiled()); // sync the mutable runtime tree back to config state before persisting it
@@ -385,6 +461,7 @@ async function* walkNode(parent, { ctx, depth = Infinity, prefixPath = '', requi
385
461
  started = true;
386
462
  const { source } = parent;
387
463
  const taken = new Set();
464
+ const takenSources = new Set();
388
465
  const maskApplier = parentMaskApplier(parent);
389
466
  const visitLater = [];
390
467
  const childrenWorking = parent.children?.length && Promise.all(parent.children.map(async (child) => {
@@ -392,7 +469,9 @@ async function* walkNode(parent, { ctx, depth = Infinity, prefixPath = '', requi
392
469
  return;
393
470
  const nodeName = getNodeName(child);
394
471
  const name = prefixPath + nodeName;
395
- taken?.add(normalizeFilename(name));
472
+ taken.add(normalizeFilename(name));
473
+ for (const sourceName of getSourceNames(child, parent))
474
+ takenSources.add(normalizeFilename(prefixPath + sourceName));
396
475
  const item = setVfsPath({ ...child, original: child, name, parent }, name, parent);
397
476
  if (await cantSee(item))
398
477
  return;
@@ -434,7 +513,8 @@ async function* walkNode(parent, { ctx, depth = Infinity, prefixPath = '', requi
434
513
  renamed = dir + '/' + renamed;
435
514
  }
436
515
  const name = prefixPath + (renamed || path);
437
- if (taken?.has(normalizeFilename(name))) // taken by vfs node above
516
+ if (taken.has(normalizeFilename(name))
517
+ || takenSources.has(normalizeFilename(prefixPath + path))) // taken by vfs node above
438
518
  return false; // false just in case it's a folder
439
519
  const item = setVfsPath({ name, isFolder, source: (0, path_1.join)(source, path), parent, stats: entry.stats }, name, parent);
440
520
  // masks containing '/' must be matched against the relative path while keeping walkDir recursion enabled
package/src/webdav.js CHANGED
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.webdav = void 0;
7
7
  exports.releaseWebdavLock = releaseWebdavLock;
8
8
  exports.isWebdavLocked = isWebdavLocked;
9
- const consumers_1 = require("node:stream/consumers");
9
+ const util_http_1 = require("./util-http");
10
10
  const vfs_1 = require("./vfs");
11
11
  const cross_1 = require("./cross");
12
12
  const stream_1 = require("stream");
@@ -315,11 +315,14 @@ const webdav = async (ctx, next) => {
315
315
  setWebdavHeaders(true);
316
316
  return;
317
317
  }
318
- const body = ctx.length || ctx.get('content-length') || ctx.get('transfer-encoding') ? await (0, consumers_1.text)(ctx.req) : '';
318
+ const body = await (0, util_http_1.readRequestBodyLimited)(ctx, 1024 * 1024);
319
+ if (!body)
320
+ return;
321
+ const bodyText = body.text;
319
322
  const token = getProvidedLockToken();
320
323
  let seconds = Number(ctx.get('timeout').split(',').find(x => /^Second-\d+$/i.test(x.trim()))?.trim().split('-', 2)[1]);
321
324
  seconds = lodash_1.default.clamp(seconds || LOCK_DEFAULT_SECONDS, 1, LOCK_MAX_SECONDS);
322
- if (!body) {
325
+ if (!bodyText) {
323
326
  // Finder and similar clients refresh an existing lock by sending LOCK without a body
324
327
  if (!token)
325
328
  return ctx.status = cross_1.HTTP_BAD_REQUEST;
@@ -338,7 +341,7 @@ const webdav = async (ctx, next) => {
338
341
  ctx.body = renderLockResponse(lock.token, lock.seconds);
339
342
  return;
340
343
  }
341
- const lockinfo = (0, cross_1.try_)(() => xmlParser.parse(body).lockinfo);
344
+ const lockinfo = (0, cross_1.try_)(() => xmlParser.parse(bodyText).lockinfo);
342
345
  const scope = lodash_1.default.keys(lockinfo?.lockscope)[0];
343
346
  const type = lodash_1.default.keys(lockinfo?.locktype)[0];
344
347
  if (!scope || !type)
@@ -416,8 +419,11 @@ const webdav = async (ctx, next) => {
416
419
  setWebdavHeaders(true);
417
420
  return;
418
421
  }
419
- const body = ctx.length || ctx.get('content-length') || ctx.get('transfer-encoding') ? await (0, consumers_1.text)(ctx.req) : '';
420
- const props = (0, cross_1.try_)(() => parseProppatchProps(body)) || [];
422
+ const body = await (0, util_http_1.readRequestBodyLimited)(ctx, 1024 * 1024);
423
+ if (!body)
424
+ return;
425
+ const bodyText = body.text;
426
+ const props = (0, cross_1.try_)(() => parseProppatchProps(bodyText)) || [];
421
427
  if (!props.length)
422
428
  return ctx.status = cross_1.HTTP_BAD_REQUEST;
423
429
  const statuses = [];
@@ -1,3 +0,0 @@
1
- import{i as e}from"./rolldown-runtime-CacB07dV.js";import{t}from"./DateTimeField-ZUy9IBhM.js";import{n,r,t as i}from"./MenuButton-BTRAUH7H.js";import{n as a,t as o}from"./VfsPathField-DHifSPJi.js";import{Bn as s,C as c,Ct as l,Da as u,E as d,Ea as f,Et as p,F as m,Ft as h,H as g,Ir as _,Lt as v,M as y,Ma as b,N as x,O as S,Oa as C,Pa as w,R as T,S as E,St as D,Ta as O,U as k,Ut as A,W as j,Wr as M,Yt as N,_ as ee,_i as P,_r as te,ai as F,en as I,fr as L,ft as R,gt as ne,ht as re,it as ie,j as ae,jr as oe,ka as se,lr as z,lt as ce,mt as B,ni as le,nr as ue,q as de,ri as fe,st as pe,tr as me,tt as V,vi as H,w as he,y as U,zt as ge}from"./index-B3difDBD.js";import{$ as _e,J as ve,Q as ye,X as be,Y as xe,Z as Se,et as W,it as Ce,nt as we,rt as G,tt as Te}from"./icons-DR_Rvxtn.js";var K=e(w());u();var Ee=[`password`,`password2`,`adminActualAccess`,`hasPassword`,`invalidated`,`canLogin`,`members`];function De({account:e,done:n,groups:r,addToBar:i,reload:a}){var u,m;let{username:h}=ie(),[_,v]=(0,K.useState)(e),[x,S]=(0,K.useState)([]),C=I();(0,K.useEffect)(()=>{var t;v(e),S(r.filter(t=>t!==e.username)),C||(t=k.current)==null||(t=t.querySelector(`input`))==null||t.focus()},[JSON.stringify(e)]);let w=!e.username,{isGroup:T}=_,k=(0,K.useRef)(),{members:A}=e,j=(u=e.plugin)==null?void 0:u.auth;return(0,K.createElement)(ce,{key:e.username,formRef:k,values:_,set(e,t){v(n=>f(f({},n),{},{[t]:e}))},barSx:f({gap:2,width:`100%`},de()),stickyBar:!0,addToBar:[!w&&(0,K.createElement)(c,f(f({icon:G,title:`Delete`,confirm:`Delete ${e.username}?`},h===e.username&&{disabled:!0,title:`Cannot delete current account`}),{},{onClick:()=>z(`del_account`,{username:e.username}).then(a)})),(0,K.createElement)(c,{icon:xe,title:`Invalidate past sessions${s(`
2
- (already invalidated sessions before `,D(e.invalidated||0),`)`)}`,confirm:`Invalidate all sessions up to now for "${e.username}"?`,doneMessage:!0,onClick:()=>z(`invalidate_sessions`,{username:e.username}).then(a)}),...me(i)],fields:[{k:`username`,label:T?`Group name`:void 0,autoComplete:`off`,required:!0,md:T&&!j?12:4,getError:t=>t!==e.username&&z(`get_account`,{username:t}).then(t=>(t==null?void 0:t.username)===e.username?`usernames are case-insensitive`:`already used`,()=>!1)},j&&{k:``,md:8,comp:(0,K.createElement)(H,{severity:`info`},` Authentication handled by a plugin`)},!T&&!j&&{k:`password`,xs:6,md:4,type:`password`,autoComplete:`new-password`,required:w,label:w?`Password`:`Change password`},!T&&!j&&{k:`password2`,xs:6,md:4,type:`password`,autoComplete:`new-password`,label:`Repeat password`,getError:(e,{values:t})=>(e||``)!==(t.password||``)&&`Enter same password`},{k:`disabled`,comp:R,fromField:e=>!e,toField:e=>!e,label:`Enabled`,xs:12,sm:6,lg:4,helperText:_.disabled||_.canLogin!==!1?`Login is prevented if account is disabled, or all its groups are disabled`:(0,K.createElement)(F,{sx:{color:`warning.main`},component:`span`},new Date(e.expire)<new Date?`Login is prevented because account is expired`:`Login is prevented because all of its groups are disabled`)},{k:`ignore_limits`,comp:R,xs:12,sm:6,lg:4,helperText:_.ignore_limits?`Speed limits don't apply to this account`:`Speed limits apply to this account`},f({k:`admin`,comp:R,fromField:e=>e||null,label:`Admin-panel access`,xs:12,sm:6,lg:4,helperText:`To access THIS interface you are using right now`},!e.admin&&e.adminActualAccess&&{value:!0,disabled:!0,helperText:`This permission is inherited. To disable it, act on the groups.`}),!T&&{k:`require_password_change`,comp:R,xs:12,sm:6,lg:4,helperText:`At next login, but can be dismissed`},{k:`disable_password_change`,label:`Password change`,comp:ne,xs:12,sm:6,lg:4,defaultValue:null,options:{[`Default (${_.canChangePassword?`Allowed`:`Disabled`})`]:null,Allowed:!1,Disabled:!0}},A?T&&!A.length?(0,K.createElement)(F,{},`No members`):A.length>0&&(0,K.createElement)(E,{gap:0,flexWrap:`wrap`},`${A.length} members: `,N(`,\xA0`,(m=e.members)==null?void 0:m.map(e=>(0,K.createElement)(r.includes(e)?`i`:`span`,{},e))),(0,K.createElement)(U,{icon:G,confirm:`Delete ${e.members.length} accounts?`,onClick:()=>z(`del_account`,{username:e.members}).then(a),sx:{verticalAlign:`text-top`}})):null,T&&(0,K.createElement)(H,{severity:`info`},`To add users to this group, select the user and then click "Inherit"`),{k:`belongs`,comp:re,label:`Inherit from groups`,options:x,sm:6,lg:4,helperText:`Specify groups to inherit permissions from`+(T?`. A group can inherit from another group`:``)+(x.length?``:`. Now disabled because there are no groups to select, create one first.`)},{k:`allow_net`,comp:d,label:`Allowed network address`,sm:6,lg:4,placeholder:`any address`},!T&&{k:`auto_login_net`,comp:d,label:`Auto-login by IP address`,sm:6,lg:4,placeholder:`none`},{k:`redirect`,comp:o,placeholder:`no`,sm:6,lg:4,helperText:`If you want this account to be redirected to a specific folder/address (or even file) at login time`},{k:`expire`,label:`Expiration`,sm:6,lg:4,comp:t,toField:e=>e&&new Date(e),helperText:`When expired, login won't be allowed`},f(f({k:`days_to_live`,sm:6,lg:4,comp:B,step:`any`,min:1/1e3},_.expire&&{xs:12,disabled:!0,sx:{opacity:.2}}),{},{helperText:`Used to set expiration on first login`}),{k:`notes`,multiline:!0,sm:6,lg:4}],onError:g,save:f(f({},y(l(_,e))),{},{onClick(){return b(function*(){var t;let{password:r=``,password2:i,adminActualAccess:a,hasPassword:o,invalidated:s,canLogin:c,members:l}=_,u=O(_,Ee),d=((t=k.current)==null?void 0:t.querySelector(`button.saveBtn`))||void 0;if(w){let e=yield z(`add_account`,u);if(r)try{yield p(_.username,r)}catch(e){throw z(`del_account`,{username:_.username}),e}n(e==null?void 0:e.username,d);return}let m=yield z(`set_account`,{username:e.username,changes:u});r&&(yield p(_.username,r),v(e=>f(f({},e),{},{password:``,password2:``}))),e.username===h&&(V.username=m.username),n(m==null?void 0:m.username,d)})()}})})}var q=e(L());se(),u();var J={fieldSeparator:`,`,lineSeparator:`
3
- `,quote:`"`},Y={field:0,fieldOffset:0,line:0,lineOffset:0,quoted:!1,appendCell:!1,appendField:!1,appendRow:!1},X=class extends Error{constructor(e,t){super(`Invalid CSV at ${e}:${t}`)}},Oe=class{constructor(e=J){C(this,`_rows`,[]),C(this,`_row`,[]),C(this,`_cell`,``),C(this,`_options`,J),C(this,`_state`,f({},Y)),C(this,`_index`,0),C(this,`_current`,``),C(this,`_previous`,``),C(this,`_quoteState`,f({},Y)),this._options=Object.assign({},J,e)}parse(e){for(this.reset(),this._index=0;this._index<e.length;this._index++)this._state.appendCell=!0,this._previous=this._current,this._current=e[this._index],this.handleNext();if(this._row.length>0&&(this.addField(this.fieldValue(this._cell),this._row,this._state),this.addRow(this._row,this._rows,this._state)),this._state.quoted)throw new X(this._quoteState.line,this._quoteState.lineOffset);return this.makeImmutable(),this._rows}get rows(){return this._rows}get json(){if(this.rows.length>0){let e=this.rows[0].filter(e=>typeof e==`string`);return Object.freeze(this.rows.filter((e,t)=>e&&t>0).map(t=>{let n={};return e.forEach((e,r)=>{n[e]=t[r]}),Object.freeze(n)}))}return Object.freeze([])}handleNext(){this.handleQuote()||this.handleFieldSeparator()||this.handleLineSeparator(),this.processState()}handleQuote(){return this._current===this._options.quote?(this._quoteState=f({},this._state),this._previous===`\\`?this.handleQuoteEscaped():this.handleQuoteNotEscaped(),!0):!1}handleQuoteEscaped(){this._cell=this._cell.slice(0,Math.max(0,this._cell.length-1))}handleQuoteNotEscaped(){if(this._cell.length===0||this._state.quoted)this._state.quoted=!this._state.quoted;else throw new X(this._state.line,this._state.lineOffset);this._state.appendCell=!1}handleFieldSeparator(){return this._current===this._options.fieldSeparator?(this._state.quoted||(this._state.appendCell=!1,this._state.appendField=!0),!0):!1}handleLineSeparator(){return this._current===this._options.lineSeparator?(this._state.quoted||(this._state.appendCell=!1,this._state.appendField=!0,this._state.appendRow=!0),!0):!1}processState(){this._state.appendCell&&(this._cell+=this._current),this._state.appendField&&(this.addField(this.fieldValue(this._cell),this._row,this._state),this._cell=``),this._state.appendRow&&(this.addRow(this._row,this._rows,this._state),this._row=[]),this._state.lineOffset++,this._state.fieldOffset++}fieldValue(e){return e}addField(e,t,n){t.push(e),n.field++,n.fieldOffset=-1,n.appendField=!1}addRow(e,t,n){t.push(e),n.field=0,n.line++,n.lineOffset=-1,n.appendRow=!1}makeImmutable(){this.rows.forEach(e=>{e.forEach(e=>Object.freeze(e)),Object.freeze(e)}),Object.freeze(this._rows)}reset(){this._rows=[],this._row=[],this._cell=``,this._state=f({},Y),this._index=0,this._current=``,this._previous=``,this._quoteState=f({},Y)}};u();function ke(e){return Z.apply(this,arguments)}function Z(){return Z=b(function*(e){v(function(){var t=b(function*(t){let n=t==null?void 0:t[0];if(!n)return;let r=yield h(n);if(!r)return;let i=new Oe().parse(r.trim()),a={comp:B,min:1,max:9,xs:6,typing:!0},o={skipFirstLines:0,usernameColumn:1,passwordColumn:2,groupColumn:3,redirectColumn:4,overwriteExistingAccounts:!1},c=yield j({title:`Import accounts from CSV`,dialogProps:{maxWidth:`sm`},values:o,form:e=>{let t=i[e.skipFirstLines||0]||[],n=u(t,f(f({},o),e));return{save:{startIcon:(0,K.createElement)(ve),children:`Go`},fields:[(0,K.createElement)(F,{sx:{p:1}},`Total lines:`,i.length),{k:`skipFirstLines`,comp:B,max:i.length-1,typing:!0,md:6,getError:e=>e!=null&&!Number.isInteger(e)&&`Enter an integer`,helperText:(0,K.createElement)(K.Fragment,{},`First line: `,(0,K.createElement)(`code`,{},t.join(`, `)))},{k:`overwriteExistingAccounts`,comp:R,md:6},f(f({k:`usernameColumn`},a),{},{helperText:(0,K.createElement)(K.Fragment,{},`First username: `,n.u)}),f(f({k:`passwordColumn`},a),{},{helperText:(0,K.createElement)(K.Fragment,{},`First password: `,n.p)}),f(f({k:`groupColumn`},a),{},{helperText:(0,K.createElement)(K.Fragment,{},`First group: `,n.g)}),f(f({k:`redirectColumn`},a),{},{helperText:(0,K.createElement)(K.Fragment,{},`First redirect: `,n.r)})]}}});if(!c)return;let{close:l}=A({title:`Importing...`,Content(){let[t,n]=(0,K.useState)(0),[r,a]=(0,K.useState)();return(0,K.useEffect)(()=>{let t=!1;return setTimeout(b(function*(){if(t)return;let r=0,o=0,d=c.skipFirstLines,f=i.length-d,m=0;try{for(let e of i){var h;if(t)return;if(d){d--;continue}let i=u(e,c);a(i),n(m++/f),yield z(`add_account`,{username:i.u,belongs:(h=i.g)==null?void 0:h.split(`,`),redirect:i.r,overwrite:c.overwriteExistingAccounts}).then(()=>{if(i.p)return p(i.u,i.p)}).catch(e=>{if(e.code===409)return o++;r++})}}finally{l();let t=m-r-o;g(`Results: `+[s(``,r,` failed`),s(``,t,` succeeded`),s(``,o,` skipped because already present`)].filter(Boolean).join(`, `),!t&&r?`error`:r||o?`warning`:`success`),e==null||e()}})),()=>{t=!0}},[]),(0,K.createElement)(F,{sx:{display:`flex`,gap:2,alignItems:`center`}},(0,K.createElement)(he,{icon:W,progress:t}),r==null?void 0:r.u)}});function u(e,t){return{u:e[t.usernameColumn-1],p:e[t.passwordColumn-1],g:e[t.groupColumn-1],r:e[t.redirectColumn-1]}}});return function(e){return t.apply(this,arguments)}}(),{multiple:!1,accept:`.csv`})}),Z.apply(this,arguments)}u();var Ae=` `,Q=e=>e==null?void 0:e.split(Ae).at(-1);function je(){let{username:e,accountsAsTree:t}=ie(),{data:o,reload:s,element:l}=ee(`get_accounts`),[u,d]=(0,K.useState)([]),p=Array.isArray(u);(0,K.useEffect)(()=>{Array.isArray(o==null?void 0:o.list)&&p&&d(u.filter(e=>o.list.find(t=>(t==null?void 0:t.username)===Q(e))))},[o]);let h=(0,K.useMemo)(()=>o&&q.default.sortBy(o.list,[e=>!e.isGroup,e=>!e.adminActualAccess,`username`]),[o]),v=p&&q.default.find(h,{username:Q(u[0])}),y=m(`md`),C=!(u.length>0)||!h?null:p&&u.length>1?(0,K.createElement)(K.Fragment,{},(0,K.createElement)(E,{},(0,K.createElement)(P,{variant:`h6`},u.length+` selected`),(0,K.createElement)(U,{onClick:I,icon:G},`Remove`)),(0,K.createElement)(_,{},q.default.uniq(u.map(Q)).map(e=>(0,K.createElement)(a,{key:e},(0,K.createElement)(oe,{},e))))):ue(v||j(),e=>(0,K.createElement)(De,{account:e,groups:h.filter(e=>e.isGroup).map(e=>e.username),addToBar:y&&[(0,K.createElement)(F,{sx:{flex:1}}),$(e,{fontSize:`large`,sx:{p:1}}),(0,K.createElement)(c,{icon:pe,title:`Close`,onClick:N})],reload:s,done(e,t){d(y?[e]:[]),s(),S(``,t)}}));(0,K.useEffect)(()=>{if(y||!C||!u.length)return;let{close:e}=A({title:q.default.isString(u)?q.default.startCase(u):u.length>1?`Multiple selection`:v?(v.isGroup?`Group: `:`User: `)+v.username:`?`,Content:()=>C,onClose(e){e||N()}});return()=>void e(!0)},[y,u,v]);let w={height:`100%`,display:`flex`,flexDirection:`column`,overflow:`auto`},[D,O]=T(`Show tree`,`Show list`,()=>({icon:te}),t);return V.accountsAsTree=D,l||(0,K.createElement)(M,{container:!0,sx:{rowSpacing:1,columnSpacing:2,top:0,flex:`1 1 auto`,height:0}},(0,K.createElement)(M,{size:{xs:12,md:5,lg:4,xl:5},sx:w},(0,K.createElement)(F,{sx:{display:`flex`,flexWrap:`wrap`,gap:2,mb:2,boxShadow:e=>`0px -8px 4px 10px ${e.palette.background.paper}`,position:`sticky`,top:0,zIndex:2,backgroundColor:`background.paper`,width:`fit-content`}},(0,K.createElement)(i,{variant:`contained`,startIcon:(0,K.createElement)(Se),items:[{children:`user`,onClick:()=>d(`new-user`)},{children:`group`,onClick:()=>d(`new-group`)},{children:`from CSV`,onClick:()=>ke(s)}]},`Add`),x(s),O,(h==null?void 0:h.length)>0&&(0,K.createElement)(P,{sx:{p:1}},`${h.length} account(s)`)),!(h!=null&&h.length)&&(0,K.createElement)(H,{severity:`info`},ge`To access administration <u>remotely</u> you will need to create a user account with admin permission`),(0,K.createElement)(r,{multiSelect:!0,sx:{pr:4,pb:2,minWidth:`15em`},selectedItems:p?u:[],slots:{collapseIcon:Te,expandIcon:Ce},onSelectedItemsChange(e,t){var n,r;!(e==null||(n=e.target)==null||(r=n.closest)==null)&&r.call(n,`.MuiTreeItem-iconContainer`)||d(t)}},h&&(function e(t,r=``){return t.map(t=>{var i,a;return(0,K.createElement)(n,{key:t.username,itemId:r+t.username,label:(0,K.createElement)(F,{sx:{display:`flex`,flexWrap:`wrap`,padding:`.2em 0`,columnGap:`.5em`,alignItems:`center`}},$(t),(t.disabled||t.canLogin===!1)&&ae(we,t.disabled?`Disabled`:`Disabled by its groups`,t.disabled?void 0:{color:`text.secondary`}),(t.expire||t.days_to_live)&&(0,K.createElement)(be),t.adminActualAccess&&ae(_e,`Can login into Admin`),t.username,!!((i=t.belongs)!=null&&i.length)&&(0,K.createElement)(F,{sx:{color:`text.secondary`,fontSize:`small`}},`(`,(a=t.belongs)==null?void 0:a.join(`, `),`)`))},D&&e(h.filter(e=>{var n;return(n=t.directMembers)==null?void 0:n.includes(e.username)}),r+t.username+Ae))})})(D?h.filter(e=>!h.some(t=>{var n;return(n=t.members)==null?void 0:n.includes(e.username)})):h))),y&&C&&(0,K.createElement)(M,{size:`grow`,sx:f(f({},w),{},{maxWidth:`100%`})},(0,K.createElement)(fe,{sx:{overflow:`initial`}},(0,K.createElement)(le,{},C))));function j(){return{username:``,hasPassword:u===`new-user`,adminActualAccess:!1,invalidated:void 0,canLogin:!0,canChangePassword:!0,isGroup:u===`new-group`,members:[],directMembers:[]}}function N(){d([])}function I(){return L.apply(this,arguments)}function L(){return L=b(function*(){if(typeof u==`string`||u.some(t=>Q(t)===e)&&!(yield k(`You cannot ask to delete the account you are using. Continue with the rest?`)))return;let t=q.default.without(q.default.uniq(u.map(Q)),e);if(!t.length)return g(`Nothing to delete`,`info`);if(!(yield k(`Delete ${t.length} item(s)?`)))return;let n=[];for(let e of t)(yield z(`del_account`,{username:e}).then(()=>1,()=>0))||n.push(e);if(s(),n.length)return g(`The following items couldn't be deleted: `+n.join(`, `),`error`)}),L.apply(this,arguments)}}function $(e,t={}){return(0,K.createElement)(e.isGroup?W:ye,t)}export{je as default};
@@ -1,2 +0,0 @@
1
- import{i as e}from"./rolldown-runtime-CacB07dV.js";import{n as t,t as n}from"./DataTable-BwyFEGxv.js";import{t as r}from"./DateTimeField-ZUy9IBhM.js";import{Aa as i,C as a,Da as o,Ea as s,F as c,Gr as l,I as u,Kr as d,Ma as f,Pa as p,S as m,St as h,Ta as g,W as _,Wn as v,Yn as y,ai as b,b as ee,ct as x,dt as S,fr as C,ft as w,ii as T,ja as E,un as D,ut as O,wn as te}from"./index-B3difDBD.js";import{F as ne,I as re,L as ie,P as ae,q as oe,rt as se}from"./icons-DR_Rvxtn.js";var k=e(p()),A=e(C());o(),i();var j=[`label`,`helperText`,`fields`,`value`,`onChange`,`onError`,`setApi`,`reorder`,`prepend`,`noRows`,`valuesForAdd`,`autoRowHeight`,`dialog`,`form`,`details`,`objectK`,`saveOn`,`applyButton`,`height`,`error`,`sx`],M=[`$width`,`$column`,`$type`,`$hideUnder`,`showIf`,`$render`,`$mergeRender`];function N(e){let{label:r,helperText:i,fields:o,value:u,onChange:p,onError:h,setApi:x,reorder:C,prepend:w,noRows:T,valuesForAdd:N,autoRowHeight:I,dialog:L,form:R,details:z,objectK:B,saveOn:V,applyButton:H,height:U,error:W,sx:G}=e,ce=g(e,j),K=Array.isArray(u)?u:!B||!u?[]:Object.entries(u).map(([e,t])=>s({[B]:e},t)),q=(0,k.useMemo)(()=>K.map((e,t)=>v(s({},e),e.hasOwnProperty(`id`)?{$idx:t}:{id:t})),[JSON.stringify(K)]),J=!!W||void 0,Y=e=>t=>s(s({fields:D(o,t).map(e=>{var n;let{$width:r,$column:i,$type:a,$hideUnder:o,showIf:s,$render:c,$mergeRender:l}=e,u=g(e,M);return(!s||s(t))&&A.default.defaults(u,(n=F[a])==null?void 0:n.field)})},R),e);x==null||x({isEqual:te});let[X,le]=(0,k.useState)();return(0,k.createElement)(k.Fragment,{},(0,k.createElement)(m,{rowGap:0,flexWrap:`wrap`,ml:`2px`},r&&(0,k.createElement)(l,{error:J,sx:{color:J?void 0:`text.primary`}},r),i&&(0,k.createElement)(d,{error:J},i)),(0,k.createElement)(b,s(s({},ce),{},{sx:S({height:U},G)}),(0,k.createElement)(n,s(s(s({rows:q,details:z},I&&{getRowHeight:()=>`auto`}),!c(`sm`)&&{compact:!0}),{},{sx:s({".MuiDataGrid-virtualScroller":{minHeight:`3em`}},I&&{".MuiDataGrid-cell":{minHeight:`52px !important`}}),hideFooterSelectedRowCount:!0,hideFooter:!0,slots:{noRowsOverlay:()=>(0,k.createElement)(ee,{},T||`No entries`)},slotProps:{pagination:{showFirstButton:!0,showLastButton:!0}},onCellDoubleClick({field:e,row:t},n){var r;let i=D(o,!1).find(t=>!(0,k.isValidElement)(t)&&t.k===e&&t.$type===`boolean`);if(!i)return;let a=(r=t.$idx)==null?t.id:r,c=t[e],l=!(i.toField||A.default.identity)(c);Z(a,s(s({},K[a]),{},{[e]:(i.fromField||A.default.identity)(l,{originalValue:c})}),n)},columns:[...D(o,!1).map(e=>{var t,n;if((0,k.isValidElement)(e))return;let r=(t=F[e.$type])==null?void 0:t.column;return A.default.defaults(s(s({field:e.k,headerName:(n=e.headerName)==null?typeof e.label==`string`?e.label:O(e.k):n,disableColumnMenu:!0,valueGetter:t=>(e.toField||A.default.identity)(t)},e.$width?{[e.$width>=8?`width`:`flex`]:e.$width}:!(r!=null&&r.width)&&!(r!=null&&r.flex)&&{flex:1}),{},{renderCell:e.$render,mergeRender:e.$mergeRender,hideUnder:e.$hideUnder},e.$column),r)}).filter(Boolean),{field:``,type:`actions`,width:90,headerAlign:`center`,renderHeader(){return(0,k.createElement)(k.Fragment,{},(0,k.createElement)(a,{icon:ie,title:`Add`,size:`small`,onClick(e){return f(function*(){let t=yield _({form:Y({}),title:`Add`,values:N,dialogProps:L});if(!t)return;let n=K.slice()||[];w?n.unshift(t):n.push(t),Q(n,e)})()}}),X!==void 0&&(0,k.createElement)(a,{icon:ae,title:`Undo`,size:`small`,onClick:e=>Q(X,e)}))},getActions({row:e}){let{$idx:n=e.id}=e,i=`Modify`;return[(0,k.createElement)(t,{key:`edit`,icon:(0,k.createElement)(oe),label:i,title:i,onClick(t){return f(function*(){t.stopPropagation();let a=yield _({values:e,form:(e,{submit:t})=>Y(V===`change`&&{save:!1,onChange(e){Z(n,e,`change`)}}||H&&{addToBar:(0,k.createElement)(P,s(s({children:`Apply`,sx:s({ml:2},H.sx)},H),{},{onClick(){t(function(){var e=f(function*(e){let t=Z(n,s(s({},K[n]),e),`save`);yield H.onClick(t)});return function(t){return e.apply(this,arguments)}}())}}))}||V&&{save:!1})(e),title:(0,k.createElement)(k.Fragment,{},i,r&&` - `,r),dialogProps:L});a&&Z(n,a,t)})()}}),(0,k.createElement)(t,{key:`delete`,icon:(0,k.createElement)(se),label:`Delete`,showInMenu:C,onClick(e){e.stopPropagation(),Q(K.filter((e,t)=>t!==n),e)}}),C&&n&&(0,k.createElement)(t,{key:`up`,icon:(0,k.createElement)(re),label:`Move up`,showInMenu:!0,onClick(e){e.stopPropagation(),Q(y(K.slice(),n,n-1),e)}}),C&&n<q.length-1&&(0,k.createElement)(t,{key:`down`,icon:(0,k.createElement)(ne),label:`Move down`,showInMenu:!0,onClick(e){e.stopPropagation(),Q(y(K.slice(),n,n+1),e)}})].filter(Boolean)}}]}))));function Z(e,t,n){return Q(K.map((n,r)=>r===e?t:n),n)}function Q(e,t){let n=$(e);return p(n,{was:u,event:t}),V!==`change`&&le(K),n}function $(e){return B?Object.fromEntries(e.map(e=>{let{[B]:t}=e;return[t,g(e,[B].map(E))]})):e}}function P(e){return(0,k.createElement)(T,s({ref:u([`s`]).ref,title:`Apply
2
- (ctrl+s)`},e))}var F={boolean:{field:{comp:w},column:{renderCell:({value:e})=>e&&(0,k.createElement)(x)}},dateTime:{field:{comp:r},column:{minWidth:96,flex:.5,renderCell:({value:e})=>h(e)}}};export{N as t};
@@ -1,4 +0,0 @@
1
- import{i as e}from"./rolldown-runtime-CacB07dV.js";import{Bn as t,C as n,Jt as r,N as i,Nt as a,Pa as o,S as s,_ as c,ai as l,jt as u,lr as d,pt as f,qt as p,tt as m,vi as h,vt as g,y as _}from"./index-B3difDBD.js";import{K as v,q as y}from"./icons-DR_Rvxtn.js";import{t as b}from"./TextEditor-CDOjRy40.js";var x=e(o());function S(){m.title=`Config file`;let{data:e,reload:o,element:S}=c(`get_config_text`,{}),[C,w]=(0,x.useState)(),[T,E]=(0,x.useState)(),[D,O]=(0,x.useState)(!1);return(0,x.useEffect)(()=>{E(e==null?void 0:e.text),w(e==null?void 0:e.text)},[e]),S||(0,x.createElement)(x.Fragment,{},(0,x.createElement)(s,{flexWrap:`wrap`,justifyContent:`space-between`},(0,x.createElement)(_,{icon:v,onClick:A,disabled:!e},`Export without passwords`),D?(0,x.createElement)(x.Fragment,{},i(o),(0,x.createElement)(n,{icon:g,title:`Save
2
- (ctrl+enter)`,modified:C!==T,onClick:k}),(0,x.createElement)(h,{severity:`warning`,sx:{minWidth:`10em`}},`Be careful, you can easily break things here`)):(0,x.createElement)(_,{icon:y,variant:`outlined`,onClick(){O(!0),setTimeout(()=>a(`main textarea`),500)}},`Edit`),(0,x.createElement)(l,{sx:{flex:1,minWidth:`fit-content`}},(0,x.createElement)(f,{label:`File path`,value:e==null?void 0:e.fullPath,size:`small`}))),S||C!==void 0&&(0,x.createElement)(l,{sx:{"& pre,& textarea":{wordBreak:`break-all !important`}}},(0,x.createElement)(p,{margin:10},(0,x.createElement)(b,{value:C,disabled:!D,style:{background:`#8881`},onValueChange:w,onKeyDown(e){[`s`,`Enter`].includes(r(e))&&(k(),e.preventDefault())}}))));function k(){return d(`set_config_text`,{text:C}).then(()=>E(C))}function A(){var n;let r=(C||``).replace(/^(\s*(\w*password|_\w*|token|srp|dynamic_dns_url):\s*).+(?:\r?\n|$)/gm,`$1removed
3
- `).replace(/(:\/\/)[^/@\s]+@/g,`$1removed@`)+t(`custom_html: | # this is currently ignored by hfs, just here for reference
4
- `,(n=e.customHtml)==null?void 0:n.replace(/^/gm,` `));r&&u(`config_no_passwords.yaml`,r)}}export{S as default};
@@ -1,2 +0,0 @@
1
- import{i as e}from"./rolldown-runtime-CacB07dV.js";import{A as t,Bn as n,C as r,Cn as i,Da as a,Ea as o,H as s,I as c,Ma as l,N as u,Pa as d,_ as f,ai as p,fr as m,gt as h,ir as g,it as _,lr as v,na as y,or as b,tt as x,vi as S,vt as C,xr as w,z as T,zt as E}from"./index-B3difDBD.js";import{t as D}from"./TextEditor-CDOjRy40.js";var O=e(m()),k=e(d());a();function A(e,t,n){return(0,k.createElement)(j,o({checked:e||!1,onChange(e,n){t(n)},disabled:e===void 0},n))}var j=y(w)(({theme:e,size:t})=>{let n=t&&t===`small`;return{height:n?36:48,width:n?18:26,padding:5,"& .MuiSwitch-switchBase":{margin:1,padding:0,transform:`translateY(${n?16:22}px)`,"&.Mui-checked":{color:e.palette.primary.contrastText,transform:`translateY(2px)`,"& .MuiSwitch-thumb:before":{backgroundImage:`url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="${n?14:20}" width="20" viewBox="0 0 20 20"><path fill="${encodeURIComponent(e.palette.primary.contrastText)}" d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"/></svg>')`},"& + .MuiSwitch-track":{backgroundColor:e.palette.grey[500]}}},"& .Mui-checked .MuiSwitch-thumb":{backgroundColor:e.palette.primary.main,boxShadow:`0px 2px 1px 1px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 1px 3px 0px rgba(0, 0, 0, 0.12)`},"& .MuiSwitch-thumb":{width:n?16:24,height:n?16:24,backgroundColor:e.palette.grey[700],"&::before":{content:`''`,position:`absolute`,width:`85%`,height:`85%`,left:0,top:0,backgroundRepeat:`no-repeat`,backgroundPosition:`center`,backgroundImage:`url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="${n?14:20}" width="20" viewBox="0 0 20 20"><path fill="${encodeURIComponent(e.palette.primary.contrastText)}" d="M19,13H5V11H19V13Z"/></svg>')`}},"& .MuiSwitch-track":{backgroundColor:e.palette.grey[500],borderRadius:20/2}}});a();var M={top:`Top of HTML Body`,bottom:`Bottom of HTML Body`};function N({setTitleSide:e}){let{data:a,reload:d,element:m}=f(`get_custom_html`),{customHtmlSection:y}=_(),[w,j]=(0,k.useState)({}),[N,P]=(0,k.useState)({});(0,k.useEffect)(()=>{a&&(P(a.sections),j(a.sections),R(a.enabled))},[a]);let F=(0,k.useMemo)(()=>{let e=O.default.sortBy(Object.keys(w),i);return e.length&&!e.includes(y)&&(x.customHtmlSection=O.default.findKey(w,Boolean)||(e==null?void 0:e[0])||``),e.map(e=>{var t;return{value:e,label:(M[e]||n(`HTTP `,b[e])||O.default.startCase(e))+((t=w[e])!=null&&t.trim()?` *`:``)}})},[w]),I=(0,k.useMemo)(()=>!O.default.isEqualWith(N,w,(e,t)=>!e&&!t||void 0),[N,w]),[L,R]=(0,k.useState)();e((0,k.useMemo)(()=>(0,k.createElement)(p,{sx:{display:{xs:`none`,md:`block`}}},(0,k.createElement)(S,{severity:`info`},E("Add HTML code to some parts of the Front-end. It's saved to file `custom.html`, that you can edit directly with your editor of choice. "),T(`customization`,`More help`)),(0,k.createElement)(S,{severity:`info`},`To customize icons `,T(`customization#icons`,`read documentation`))),[]));let z=c([`s`,`Enter`]);return m||(0,k.createElement)(k.Fragment,{},(0,k.createElement)(p,{sx:{display:`flex`,alignItems:`center`,gap:1,mb:1}},(0,k.createElement)(h,{label:`Section`,value:y,options:F,onChange:e=>x.customHtmlSection=e}),u(d),(0,k.createElement)(r,{ref:z.ref,icon:C,title:`Save
2
- (ctrl+s)`,modified:I,doneAnimation:!0,onClick:B}),t(`Enable all sections`,void 0,A(L,function(){var e=l(function*(e){try{yield v(`set_config`,{values:{[g.disable_custom_html]:!e}}),R(e)}catch(e){s(e)}});return function(t){return e.apply(this,arguments)}}()))),(0,k.createElement)(D,{lang:y===`style`?`css`:y===`script`?`js`:`html`,value:(w==null?void 0:w[y])||``,style:{background:`#8881`},onValueChange(e){j(t=>o(o({},t),{},{[y]:e}))}}));function B(){return v(`set_custom_html`,{sections:w}).then(()=>P(w))}}export{N as default};