user.html 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851
  1. <!--
  2. Copyright (C) 2024 Nicola Murino
  3. This WebUI uses the KeenThemes Mega Bundle, a proprietary theme:
  4. https://keenthemes.com/products/templates-mega-bundle
  5. KeenThemes HTML/CSS/JS components are allowed for use only within the
  6. SFTPGo product and restricted to be used in a resealable HTML template
  7. that can compete with KeenThemes products anyhow.
  8. This WebUI is allowed for use only within the SFTPGo product and
  9. therefore cannot be used in derivative works/products without an
  10. explicit grant from the SFTPGo Team ([email protected]).
  11. -->
  12. {{template "base" .}}
  13. {{- define "page_body"}}
  14. <div class="card shadow-sm">
  15. <div class="card-header bg-light">
  16. <h3 data-i18n="{{.Title}}" class="card-title section-title"></h3>
  17. </div>
  18. <div class="card-body">
  19. {{- if eq .Mode 3}}
  20. <div class="notice d-flex bg-light-primary rounded border-primary border border-dashed p-6 mb-5">
  21. <i class="ki-duotone ki-shield-tick fs-2tx text-primary me-4">
  22. <span class="path1"></span>
  23. <span class="path2"></span>
  24. </i>
  25. <div class="d-flex flex-stack flex-grow-1 flex-wrap flex-md-nowrap">
  26. <div class="mb-3 mb-md-0 fw-semibold">
  27. <h4 class="text-gray-900 fw-bold">
  28. <span data-i18n="user.template_title">Create one or more new users from this template</span>
  29. </h4>
  30. <div class="fs-6 text-gray-800 pe-7">
  31. <p data-i18n="general.template_placeholders" class="mt-5">The following placeholders are supported</p>
  32. <ul>
  33. <li><span class="text-info">%username%</span>, <span data-i18n="user.template_username_placeholder">replaced with the specified username</span></li>
  34. <li><span class="text-info">%password%</span>, <span data-i18n="user.template_password_placeholder">replaced with the specified password</span></li>
  35. </ul>
  36. <p data-i18n="user.template_help1">Placeholders will be replaced in paths and credentials of the configured storage backend.</p>
  37. <p data-i18n="user.template_help2">The generated users can be saved or exported. Exported users can be imported from the "Maintenance" section of this SFTPGo instance or another.</p>
  38. </div>
  39. </div>
  40. </div>
  41. </div>
  42. {{- end}}
  43. {{- template "errmsg" .Error}}
  44. <form id="user_form" enctype="multipart/form-data" action="{{.CurrentURL}}" method="POST" autocomplete="off" {{if eq .Mode 3}}target="_blank"{{end}}>
  45. {{- if eq .Mode 3}}
  46. <div class="card mt-10">
  47. <div class="card-header bg-light">
  48. <h3 data-i18n="title.users" class="card-title section-title-inner">Users</h3>
  49. </div>
  50. <div class="card-body">
  51. <div id="template_users">
  52. <p class="fs-5 fw-semibold mb-4" data-i18n="user.template_help">For each user set the username and at least one of the password and public key</p>
  53. <div class="form-group">
  54. <div data-repeater-list="template_users">
  55. <div data-repeater-item>
  56. <div class="form-group row">
  57. <div class="col-md-3 mt-3 mt-md-8">
  58. <input data-i18n="[placeholder]login.username" type="text" class="form-control" name="tpl_username" autocomplete="off" spellcheck="false" value="" />
  59. </div>
  60. <div class="col-md-3 mt-3 mt-md-8">
  61. <input data-i18n="[placeholder]login.password" type="password" class="form-control" name="tpl_password" autocomplete="new-password" spellcheck="false" value="" />
  62. </div>
  63. <div class="col-md-5 mt-3 mt-md-8">
  64. <textarea class="form-control" name="tpl_public_keys" rows="5" placeholder="Paste your public key here"></textarea>
  65. </div>
  66. <div class="col-md-1 mt-3 mt-md-8">
  67. <a href="#" data-repeater-delete
  68. class="btn btn-light-danger ps-5 pe-4">
  69. <i class="ki-duotone ki-trash fs-2">
  70. <span class="path1"></span>
  71. <span class="path2"></span>
  72. <span class="path3"></span>
  73. <span class="path4"></span>
  74. <span class="path5"></span>
  75. </i>
  76. </a>
  77. </div>
  78. </div>
  79. </div>
  80. </div>
  81. </div>
  82. <div class="form-group mt-5">
  83. <a href="#" data-repeater-create class="btn btn-light-primary">
  84. <i class="ki-duotone ki-plus fs-3"></i>
  85. <span data-i18n="general.add">Add</span>
  86. </a>
  87. </div>
  88. </div>
  89. </div>
  90. </div>
  91. <input type="hidden" name="username" id="idUsername" value="{{.User.Username}}">
  92. {{- else}}
  93. <div class="form-group row">
  94. <label for="idUsername" data-i18n="login.username" class="col-md-3 col-form-label">Username</label>
  95. <div class="col-md-9">
  96. <input id="idUsername" type="text" placeholder="" name="username" value="{{.User.Username}}" maxlength="255" autocomplete="off"
  97. spellcheck="false" required {{if ge .Mode 2}}class="form-control-plaintext readonly-input" readonly{{else}}class="form-control"{{end}} />
  98. </div>
  99. </div>
  100. {{- end}}
  101. {{- if .Roles}}
  102. <div class="form-group row mt-10">
  103. <label for="idRole" data-i18n="general.role" class="col-md-3 col-form-label">Role</label>
  104. <div class="col-md-9">
  105. <select id="idRole" name="role" data-i18n="[data-placeholder]general.role_placeholder" class="form-select" data-control="i18n-select2" data-placeholder="Select a role" data-allow-clear="true" aria-describedby="idRoleHelp">
  106. <option value=""></option>
  107. {{- range .Roles}}
  108. <option value="{{.Name}}" {{if eq $.User.Role .Name}}selected{{end}}>{{.Name}}</option>
  109. {{- end}}
  110. </select>
  111. <div id="idRoleHelp" data-i18n="user.role_help" class="form-text">
  112. Users with a role can be managed by global administrators and administrators with the same role
  113. </div>
  114. </div>
  115. </div>
  116. {{- end}}
  117. {{- if ne .Mode 3}}
  118. <div class="form-group row mt-10">
  119. <label for="idPassword" data-i18n="login.password" class="col-md-3 col-form-label">Password</label>
  120. <div class="col-md-9">
  121. <input id="idPassword" type="password" class="form-control" name="password" autocomplete="new-password"
  122. spellcheck="false" value="{{.User.Password}}" />
  123. </div>
  124. </div>
  125. <div class="form-group row align-items-center mt-10">
  126. <label data-i18n="user.require_pwd_change" class="col-md-3 col-form-label" for="idRequirePasswordChange">Require password change</label>
  127. <div class="col-md-9">
  128. <div class="form-check form-switch form-check-custom form-check-solid">
  129. <input class="form-check-input" type="checkbox" id="idRequirePasswordChange" name="require_password_change" {{if .User.Filters.RequirePasswordChange}}checked="checked"{{end}}/>
  130. <label data-i18n="user.require_pwd_change_help" class="form-check-label fw-semibold text-gray-800" for="idRequirePasswordChange">
  131. User must change password from the WebClient at next login
  132. </label>
  133. </div>
  134. </div>
  135. </div>
  136. <div class="card mt-10">
  137. <div class="card-header bg-light">
  138. <h3 data-i18n="general.pub_keys" class="card-title section-title-inner">Public keys</h3>
  139. </div>
  140. <div class="card-body">
  141. <div id="public_keys">
  142. <div class="form-group">
  143. <div data-repeater-list="public_keys">
  144. {{- range $idx, $val := .User.PublicKeys}}
  145. <div data-repeater-item>
  146. <div class="form-group row">
  147. <div class="col-md-9 mt-3 mt-md-8">
  148. <textarea data-i18n="[placeholder]general.pub_key_placeholder" class="form-control" name="public_key" rows="4"
  149. placeholder="Paste your public key here">{{$val}}</textarea>
  150. </div>
  151. <div class="col-md-3 mt-3 mt-md-8">
  152. <a href="#" data-repeater-delete
  153. class="btn btn-light-danger">
  154. <i class="ki-duotone ki-trash fs-5">
  155. <span class="path1"></span>
  156. <span class="path2"></span>
  157. <span class="path3"></span>
  158. <span class="path4"></span>
  159. <span class="path5"></span>
  160. </i>
  161. <span data-i18n="general.delete">Delete</span>
  162. </a>
  163. </div>
  164. </div>
  165. </div>
  166. {{- else}}
  167. <div data-repeater-item>
  168. <div class="form-group row">
  169. <div class="col-md-9 mt-3 mt-md-8">
  170. <textarea data-i18n="[placeholder]general.pub_key_placeholder" class="form-control" name="public_key" rows="4"
  171. placeholder="Paste your public key here"></textarea>
  172. </div>
  173. <div class="col-md-3 mt-3 mt-md-8">
  174. <a href="#" data-repeater-delete
  175. class="btn btn-light-danger">
  176. <i class="ki-duotone ki-trash fs-5">
  177. <span class="path1"></span>
  178. <span class="path2"></span>
  179. <span class="path3"></span>
  180. <span class="path4"></span>
  181. <span class="path5"></span>
  182. </i>
  183. <span data-i18n="general.delete">Delete</span>
  184. </a>
  185. </div>
  186. </div>
  187. </div>
  188. {{- end}}
  189. </div>
  190. </div>
  191. <div class="form-group mt-5">
  192. <a href="#" data-repeater-create class="btn btn-light-primary">
  193. <i class="ki-duotone ki-plus fs-3"></i>
  194. <span data-i18n="general.add">Add</span>
  195. </a>
  196. </div>
  197. </div>
  198. </div>
  199. </div>
  200. {{- end}}
  201. {{- if .Groups}}
  202. <div class="card mt-10 {{if .LoggedUser.Filters.Preferences.HideGroups}}d-none{{end}}">
  203. <div class="card-header bg-light">
  204. <h3 data-i18n="title.groups" class="card-title section-title-inner">Groups</h3>
  205. </div>
  206. <div class="card-body">
  207. {{- template "infomsg" "user.groups_help"}}
  208. <div class="form-group row">
  209. <label for="idPrimaryGroup" data-i18n="user.primary_group" class="col-md-3 col-form-label">Primary group</label>
  210. <div class="col-md-9">
  211. <select id="idPrimaryGroup" name="primary_group" data-i18n="[data-placeholder]general.group_placeholder" class="form-select" data-control="i18n-select2" data-placeholder="Select a group" data-allow-clear="true">
  212. <option value=""></option>
  213. {{- range .Groups}}
  214. <option value="{{.Name}}" {{if $.User.HasPrimaryGroup .Name}}selected{{end}}>{{.Name}}</option>
  215. {{- end}}
  216. </select>
  217. </div>
  218. </div>
  219. <div class="form-group row mt-10">
  220. <label for="idSecondaryGroup" data-i18n="user.secondary_groups" class="col-md-3 col-form-label">
  221. Secondary groups
  222. </label>
  223. <div class="col-md-9">
  224. <select id="idSecondaryGroup" name="secondary_groups" class="form-select" data-control="i18n-select2" data-close-on-select="false" multiple>
  225. {{- range .Groups}}
  226. <option value="{{.Name}}" {{if $.User.HasSecondaryGroup .Name}}selected{{end}}>{{.Name}}</option>
  227. {{- end}}
  228. </select>
  229. </div>
  230. </div>
  231. <div class="form-group row mt-10">
  232. <label for="idMembershipGroup" data-i18n="user.membership_groups" class="col-md-3 col-form-label">
  233. Membership groups
  234. </label>
  235. <div class="col-md-9">
  236. <select id="idMembershipGroup" name="membership_groups" class="form-select" data-control="i18n-select2" data-close-on-select="false" multiple>
  237. {{- range .Groups}}
  238. <option value="{{.Name}}" {{if $.User.HasMembershipGroup .Name}}selected{{end}}>{{.Name}}</option>
  239. {{- end}}
  240. </select>
  241. </div>
  242. </div>
  243. </div>
  244. </div>
  245. {{- end}}
  246. {{- template "fshtml" .FsWrapper}}
  247. {{- if .VirtualFolders}}
  248. <div class="card mt-10 {{if .LoggedUser.Filters.Preferences.HideVirtualFolders}}d-none{{end}}">
  249. <div class="card-header bg-light">
  250. <h3 data-i18n="title.folders" class="card-title section-title-inner">Virtual folders</h3>
  251. </div>
  252. <div class="card-body">
  253. <div id="virtual_folders">
  254. {{- template "infomsg-no-mb" "user.virtual_folders_help"}}
  255. <div class="form-group">
  256. <div data-repeater-list="virtual_folders">
  257. {{- range $idx, $val := .User.VirtualFolders}}
  258. <div data-repeater-item>
  259. <div data-repeater-item>
  260. <div class="form-group row">
  261. <div class="col-md-3 mt-3 mt-md-8">
  262. <input data-i18n="[placeholder]virtual_folders.mount_path" type="text" class="form-control" name="vfolder_path" value="{{$val.VirtualPath}}" />
  263. </div>
  264. <div class="col-md-3 mt-3 mt-md-8">
  265. <select name="vfolder_name" data-i18n="[data-placeholder]general.folder_placeholder" class="form-select select-repetear" data-placeholder="Select a folder" data-allow-clear="true">
  266. <option value=""></option>
  267. {{- range $.VirtualFolders}}
  268. <option value="{{.Name}}" {{- if eq $val.Name .Name}} selected{{- end}}>{{.Name}}</option>
  269. {{- end}}
  270. </select>
  271. </div>
  272. <div class="col-md-3 mt-3 mt-md-8">
  273. <input type="text" class="form-control" name="vfolder_quota_size" value="{{HumanizeBytes $val.QuotaSize}}" />
  274. <div class="form-text" data-i18n="virtual_folders.quota_size"></div>
  275. </div>
  276. <div class="col-md-2 mt-3 mt-md-8">
  277. <input type="number" min="-1" class="form-control" name="vfolder_quota_files" value="{{$val.QuotaFiles}}" />
  278. <div class="form-text" data-i18n="virtual_folders.quota_files"></div>
  279. </div>
  280. <div class="col-md-1 mt-3 mt-md-8">
  281. <a href="#" data-repeater-delete
  282. class="btn btn-light-danger ps-5 pe-4">
  283. <i class="ki-duotone ki-trash fs-2">
  284. <span class="path1"></span>
  285. <span class="path2"></span>
  286. <span class="path3"></span>
  287. <span class="path4"></span>
  288. <span class="path5"></span>
  289. </i>
  290. </a>
  291. </div>
  292. </div>
  293. </div>
  294. </div>
  295. {{- else}}
  296. <div data-repeater-item>
  297. <div class="form-group row">
  298. <div class="col-md-3 mt-3 mt-md-8">
  299. <input data-i18n="[placeholder]virtual_folders.mount_path" type="text" class="form-control" name="vfolder_path" value="" />
  300. </div>
  301. <div class="col-md-3 mt-3 mt-md-8">
  302. <select name="vfolder_name" data-i18n="[data-placeholder]general.folder_placeholder" class="form-select select-repetear" data-placeholder="Select a folder" data-allow-clear="true">
  303. <option value=""></option>
  304. {{- range .VirtualFolders}}
  305. <option value="{{.Name}}">{{.Name}}</option>
  306. {{- end}}
  307. </select>
  308. </div>
  309. <div class="col-md-3 mt-3 mt-md-8">
  310. <input type="text" class="form-control" name="vfolder_quota_size" value="" />
  311. <div class="form-text" data-i18n="virtual_folders.quota_size"></div>
  312. </div>
  313. <div class="col-md-2 mt-3 mt-md-8">
  314. <input type="number" min="-1" class="form-control" name="vfolder_quota_files" value="" />
  315. <div class="form-text" data-i18n="virtual_folders.quota_files"></div>
  316. </div>
  317. <div class="col-md-1 mt-3 mt-md-8">
  318. <a href="#" data-repeater-delete
  319. class="btn btn-light-danger ps-5 pe-4">
  320. <i class="ki-duotone ki-trash fs-2">
  321. <span class="path1"></span>
  322. <span class="path2"></span>
  323. <span class="path3"></span>
  324. <span class="path4"></span>
  325. <span class="path5"></span>
  326. </i>
  327. </a>
  328. </div>
  329. </div>
  330. </div>
  331. {{- end}}
  332. </div>
  333. </div>
  334. <div class="form-group mt-5">
  335. <a href="#" data-repeater-create class="btn btn-light-primary">
  336. <i class="ki-duotone ki-plus fs-3"></i>
  337. <span data-i18n="general.add">Add</span>
  338. </a>
  339. </div>
  340. </div>
  341. </div>
  342. </div>
  343. {{- end}}
  344. <div class="accordion shadow-sm mt-10 {{- if eq .LoggedUser.Filters.Preferences.VisibleUserPageSections 0}} d-none{{- end}}" id="accordionUser">
  345. <div class="accordion-item {{- if .LoggedUser.Filters.Preferences.HideProfile}} d-none{{- end}}">
  346. <h2 class="accordion-header" id="headingProfile">
  347. <button class="accordion-button section-title-inner text-primary collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseProfile" aria-expanded="true" aria-controls="collapseProfile">
  348. <span data-i18n="title.profile">Profile</span>
  349. </button>
  350. </h2>
  351. <div id="collapseProfile" class="accordion-collapse collapse" aria-labelledby="headingProfile" data-bs-parent="#accordionUser">
  352. <div class="accordion-body">
  353. <div class="form-group row">
  354. <label for="idStatus" data-i18n="general.status" class="col-md-3 col-form-label">Status</label>
  355. <div class="col-md-9">
  356. <select id="idStatus" name="status" class="form-select" data-control="i18n-select2" data-hide-search="true">
  357. <option data-i18n="general.active" value="1" {{- if eq .User.Status 1 }} selected{{- end}}>Active</option>
  358. <option data-i18n="general.inactive" value="0" {{- if eq .User.Status 0 }} selected{{- end}}>Inactive</option>
  359. </select>
  360. </div>
  361. </div>
  362. <div class="form-group row mt-10">
  363. <label for="id_expiration" data-i18n="general.expiration" class="col-md-3 col-form-label">Expiration</label>
  364. <div class="col-md-9 d-flex">
  365. <input data-i18n="[placeholder]general.expiration_help" id="id_expiration" class="form-control" placeholder="Pick an expiration date" />
  366. <button class="btn btn-icon btn-light-danger ms-2 d-none" id="id_expiration_clear">
  367. <i class="ki-solid ki-cross fs-1"></i>
  368. </button>
  369. </div>
  370. </div>
  371. <div class="form-group row mt-10">
  372. <label for="idEmail" data-i18n="general.email" class="col-md-3 col-form-label">Email</label>
  373. <div class="col-md-9">
  374. <input id="idEmail" type="email" class="form-control" placeholder="" name="email" value="{{.User.Email}}"
  375. maxlength="255" autocomplete="off" spellcheck="false" />
  376. </div>
  377. </div>
  378. {{- template "user_group_profile" .User.Filters}}
  379. <div class="form-group row mt-10">
  380. <label for="idDescription" data-i18n="general.description" class="col-md-3 col-form-label">Description</label>
  381. <div class="col-md-9">
  382. <input id="idDescription" type="text" class="form-control" name="description" value="{{.User.Description}}" maxlength="255">
  383. </div>
  384. </div>
  385. <div class="form-group row mt-10">
  386. <label for="idAdditionalInfo" data-i18n="general.additional_info" class="col-md-3 col-form-label">Additional info</label>
  387. <div class="col-md-9">
  388. <textarea id="idAdditionalInfo" class="form-control" name="additional_info" rows="3">{{.User.AdditionalInfo}}</textarea>
  389. </div>
  390. </div>
  391. </div>
  392. </div>
  393. </div>
  394. <div class="accordion-item {{- if .LoggedUser.Filters.Preferences.HideACLs}} d-none{{- end}}">
  395. <h2 class="accordion-header" id="headingPermissions">
  396. <button class="accordion-button section-title-inner text-primary collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapsePermissions" aria-expanded="true" aria-controls="collapsePermissions">
  397. <span data-i18n="general.acls">ACLs</span>
  398. </button>
  399. </h2>
  400. <div id="collapsePermissions" class="accordion-collapse collapse" aria-labelledby="headingPermissions" data-bs-parent="#accordionUser">
  401. <div class="accordion-body">
  402. <div class="form-group row">
  403. <label for="idPermissions" data-i18n="general.permissions" class="col-md-3 col-form-label">
  404. Permissions
  405. </label>
  406. <div class="col-md-9">
  407. <select id="idPermissions" name="permissions" class="form-select" data-control="i18n-select2" data-hide-search="true" data-close-on-select="false" multiple>
  408. {{- range $validPerm := .ValidPerms}}
  409. <option value="{{$validPerm}}" {{- range $perm :=$.RootDirPerms }}{{- if eq $perm $validPerm}} selected{{- end}}{{- end}}>{{$validPerm}}</option>
  410. {{- end}}
  411. </select>
  412. </div>
  413. </div>
  414. <div class="card mt-10">
  415. <div class="card-header bg-light">
  416. <h3 data-i18n="filters.directory_permissions" class="card-title section-title-inner">Per-directory permissions</h3>
  417. </div>
  418. <div class="card-body">
  419. <div id="directory_permissions">
  420. {{- template "infomsg-no-mb" "filters.directory_permissions_help"}}
  421. <div class="form-group">
  422. <div data-repeater-list="directory_permissions">
  423. {{- range $idx, $dirPerms := .User.GetSubDirPermissions -}}
  424. <div data-repeater-item>
  425. <div class="form-group row">
  426. <div class="col-md-6 mt-3 mt-md-8">
  427. <input data-i18n="[placeholder]filters.directory_path_help" type="text" class="form-control" name="sub_perm_path" value="{{$dirPerms.Path}}" />
  428. </div>
  429. <div class="col-md-5 mt-3 mt-md-8">
  430. <select name="sub_perm_permissions" data-i18n="[data-placeholder]general.permissions" class="form-select select-repetear" data-hide-search="true" data-close-on-select="false" multiple>
  431. {{- range $validPerm := $.ValidPerms}}
  432. <option value="{{$validPerm}}" {{- range $perm := $dirPerms.Permissions }}{{- if eq $perm $validPerm}} selected{{- end}}{{- end}}>{{$validPerm}}</option>
  433. {{- end}}
  434. </select>
  435. </div>
  436. <div class="col-md-1 mt-3 mt-md-8">
  437. <a href="#" data-repeater-delete
  438. class="btn btn-light-danger ps-5 pe-4">
  439. <i class="ki-duotone ki-trash fs-2">
  440. <span class="path1"></span>
  441. <span class="path2"></span>
  442. <span class="path3"></span>
  443. <span class="path4"></span>
  444. <span class="path5"></span>
  445. </i>
  446. </a>
  447. </div>
  448. </div>
  449. </div>
  450. {{- else}}
  451. <div data-repeater-item>
  452. <div class="form-group row">
  453. <div class="col-md-6 mt-3 mt-md-8">
  454. <input data-i18n="[placeholder]filters.directory_path_help" type="text" class="form-control" name="sub_perm_path" value="" />
  455. </div>
  456. <div class="col-md-5 mt-3 mt-md-8">
  457. <select name="sub_perm_permissions" data-i18n="[data-placeholder]general.permissions" class="form-select select-repetear" data-hide-search="true" data-close-on-select="false" multiple>
  458. {{- range $validPerm := .ValidPerms}}
  459. <option value="{{$validPerm}}">{{$validPerm}}</option>
  460. {{- end}}
  461. </select>
  462. </div>
  463. <div class="col-md-1 mt-3 mt-md-8">
  464. <a href="#" data-repeater-delete
  465. class="btn btn-light-danger ps-5 pe-4">
  466. <i class="ki-duotone ki-trash fs-2">
  467. <span class="path1"></span>
  468. <span class="path2"></span>
  469. <span class="path3"></span>
  470. <span class="path4"></span>
  471. <span class="path5"></span>
  472. </i>
  473. </a>
  474. </div>
  475. </div>
  476. </div>
  477. {{- end}}
  478. </div>
  479. </div>
  480. <div class="form-group mt-5">
  481. <a href="#" data-repeater-create class="btn btn-light-primary">
  482. <i class="ki-duotone ki-plus fs-3"></i>
  483. <span data-i18n="general.add">Add</span>
  484. </a>
  485. </div>
  486. </div>
  487. </div>
  488. </div>
  489. {{- template "user_group_perms" .User.Filters}}
  490. {{- template "user_group_access_time" .User.Filters}}
  491. <div class="form-group row mt-10">
  492. <label for="idMaxSessions" data-i18n="filters.max_sessions" class="col-md-3 col-form-label">Max sessions</label>
  493. <div class="col-md-9">
  494. <input id="idMaxSessions" type="number" min="0" class="form-control" name="max_sessions" value="{{.User.MaxSessions}}" aria-describedby="idMaxSessionsHelp" />
  495. <div id="idMaxSessionsHelp" class="form-text" data-i18n="filters.max_sessions_help"></div>
  496. </div>
  497. </div>
  498. <div class="form-group row mt-10">
  499. <label for="idProtocols" data-i18n="filters.denied_protocols" class="col-md-3 col-form-label">
  500. Denied protocols
  501. </label>
  502. <div class="col-md-9">
  503. <select id="idProtocols" name="denied_protocols" class="form-select" data-control="i18n-select2" data-close-on-select="false" multiple>
  504. {{- range $protocol := .ValidProtocols}}
  505. <option value="{{$protocol}}" {{- range $p :=$.User.Filters.DeniedProtocols }}{{- if eq $p $protocol}} selected{{- end}}{{- end}}>{{$protocol}}</option>
  506. {{- end}}
  507. </select>
  508. </div>
  509. </div>
  510. <div class="form-group row mt-10">
  511. <label for="idLoginMethods" data-i18n="filters.denied_login_methods" class="col-md-3 col-form-label">
  512. Denied login methods
  513. </label>
  514. <div class="col-md-9">
  515. <select id="idLoginMethods" name="denied_login_methods" class="form-select" data-control="i18n-select2" data-close-on-select="false" multiple aria-describedby="idLoginMethodsHelp">
  516. {{- range $method := .ValidLoginMethods}}
  517. <option value="{{$method}}" {{- range $m :=$.User.Filters.DeniedLoginMethods }}{{- if eq $m $method}} selected{{- end}}{{- end}}>{{$method}}</option>
  518. {{- end}}
  519. </select>
  520. <div id="idLoginMethodsHelp" data-i18n="filters.denied_login_methods_help" class="form-text">
  521. </div>
  522. </div>
  523. </div>
  524. <div class="form-group row mt-10">
  525. <label for="idTwoFactorProtocols" data-i18n="2fa.require_for" class="col-md-3 col-form-label">
  526. Require 2FA for
  527. </label>
  528. <div class="col-md-9">
  529. <select id="idTwoFactorProtocols" name="required_two_factor_protocols" class="form-select" data-control="i18n-select2" data-close-on-select="false" multiple>
  530. {{- range $protocol := .TwoFactorProtocols}}
  531. <option value="{{$protocol}}" {{- range $p :=$.User.Filters.TwoFactorAuthProtocols }}{{- if eq $p $protocol}} selected{{- end}}{{- end}}>{{$protocol}}</option>
  532. {{end}}
  533. </select>
  534. </div>
  535. </div>
  536. <div class="form-group row mt-10">
  537. <label for="idWebClient" data-i18n="filters.web_client_options" class="col-md-3 col-form-label">
  538. Web client/REST API
  539. </label>
  540. <div class="col-md-9">
  541. <select id="idWebClient" name="web_client_options" class="form-select" data-control="i18n-select2" data-close-on-select="false" multiple>
  542. {{- range $option := .WebClientOptions}}
  543. <option value="{{$option}}" {{- range $p :=$.User.Filters.WebClient }}{{- if eq $p $option}}selected{{- end}}{{- end}}>{{$option}}</option>
  544. {{- end}}
  545. </select>
  546. </div>
  547. </div>
  548. <div class="form-group row mt-10">
  549. <label for="idDeniedIP" data-i18n="general.denied_ip_mask" class="col-md-3 col-form-label">Denied IP/Mask</label>
  550. <div class="col-md-9">
  551. <textarea class="form-control" id="idDeniedIP" name="denied_ip" aria-describedby="idDeniedIPHelp"
  552. rows="3">{{.User.GetDeniedIPAsString}}</textarea>
  553. <div id="idDeniedIPHelp" class="form-text" data-i18n="general.ip_mask_help"></div>
  554. </div>
  555. </div>
  556. <div class="form-group row mt-10">
  557. <label for="idAllowedIP" data-i18n="general.allowed_ip_mask" class="col-md-3 col-form-label">Allowed IP/Mask</label>
  558. <div class="col-md-9">
  559. <textarea class="form-control" id="idAllowedIP" name="allowed_ip" aria-describedby="idAllowedIPHelp"
  560. rows="3">{{.User.GetAllowedIPAsString}}</textarea>
  561. <div id="idAllowedIPHelp" class="form-text" data-i18n="general.ip_mask_help"></div>
  562. </div>
  563. </div>
  564. </div>
  565. </div>
  566. </div>
  567. <div class="accordion-item {{- if .LoggedUser.Filters.Preferences.HideDiskQuotaAndBandwidthLimits}} d-none{{- end}}">
  568. <h2 class="accordion-header" id="headingQuota">
  569. <button class="accordion-button section-title-inner text-primary collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseQuota" aria-expanded="true" aria-controls="collapseQuota">
  570. <span data-i18n="general.quota_limits">Disk quota and bandwidth limits</span>
  571. </button>
  572. </h2>
  573. <div id="collapseQuota" class="accordion-collapse collapse" aria-labelledby="headingQuota" data-bs-parent="#accordionUser">
  574. <div class="accordion-body">
  575. {{template "user_group_quota" .User}}
  576. </div>
  577. </div>
  578. </div>
  579. <div class="accordion-item {{- if .LoggedUser.Filters.Preferences.HideAdvancedSettings}} d-none{{- end}}">
  580. <h2 class="accordion-header" id="headingAdvanced">
  581. <button class="accordion-button section-title-inner text-primary collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseAdvanced" aria-expanded="true" aria-controls="collapseAdvanced">
  582. <span data-i18n="general.advanced_settings">Advanced settings</span>
  583. </button>
  584. </h2>
  585. <div id="collapseAdvanced" class="accordion-collapse collapse" aria-labelledby="headingAdvanced" data-bs-parent="#accordionUser">
  586. <div class="accordion-body">
  587. <div class="card mt-10">
  588. <div class="card-header bg-light">
  589. <h3 data-i18n="user.tls_certs" class="card-title section-title-inner">TLS certificates</h3>
  590. </div>
  591. <div class="card-body">
  592. <div id="tls_certs">
  593. <div class="form-group">
  594. <div data-repeater-list="tls_certs">
  595. {{- range $idx, $val := .User.Filters.TLSCerts}}
  596. <div data-repeater-item>
  597. <div class="form-group row">
  598. <div class="col-md-9 mt-3 mt-md-8">
  599. <textarea data-i18n="[placeholder]user.tls_cert_help" class="form-control" name="tls_cert" rows="4">{{$val}}</textarea>
  600. </div>
  601. <div class="col-md-3 mt-3 mt-md-8">
  602. <a href="#" data-repeater-delete
  603. class="btn btn-light-danger">
  604. <i class="ki-duotone ki-trash fs-5">
  605. <span class="path1"></span>
  606. <span class="path2"></span>
  607. <span class="path3"></span>
  608. <span class="path4"></span>
  609. <span class="path5"></span>
  610. </i>
  611. <span data-i18n="general.delete">Delete</span>
  612. </a>
  613. </div>
  614. </div>
  615. </div>
  616. {{- else}}
  617. <div data-repeater-item>
  618. <div class="form-group row">
  619. <div class="col-md-9 mt-3 mt-md-8">
  620. <textarea data-i18n="[placeholder]user.tls_cert_help" class="form-control" name="tls_cert" rows="4"></textarea>
  621. </div>
  622. <div class="col-md-3 mt-3 mt-md-8">
  623. <a href="#" data-repeater-delete
  624. class="btn btn-light-danger">
  625. <i class="ki-duotone ki-trash fs-5">
  626. <span class="path1"></span>
  627. <span class="path2"></span>
  628. <span class="path3"></span>
  629. <span class="path4"></span>
  630. <span class="path5"></span>
  631. </i>
  632. <span data-i18n="general.delete">Delete</span>
  633. </a>
  634. </div>
  635. </div>
  636. </div>
  637. {{- end}}
  638. </div>
  639. </div>
  640. <div class="form-group mt-5">
  641. <a href="#" data-repeater-create class="btn btn-light-primary">
  642. <i class="ki-duotone ki-plus fs-3"></i>
  643. <span data-i18n="general.add">Add</span>
  644. </a>
  645. </div>
  646. </div>
  647. </div>
  648. </div>
  649. {{template "user_group_advanced" .User.Filters}}
  650. <div class="form-group row mt-10 {{if not .User.HasExternalAuth}}d-none{{end}}">
  651. <label for="idExtAuthCacheTime" data-i18n="filters.external_auth_cache_time" class="col-md-3 col-form-label">External auth cache time</label>
  652. <div class="col-md-9">
  653. <input id="idExtAuthCacheTime" type="number" min="0" class="form-control" name="external_auth_cache_time" value="{{.User.Filters.ExternalAuthCacheTime}}" aria-describedby="idExtAuthCacheTimeHelp" />
  654. <div id="idExtAuthCacheTimeHelp" class="form-text" data-i18n="filters.external_auth_cache_time_help"></div>
  655. </div>
  656. </div>
  657. <div class="form-group row mt-10 {{if not .CanImpersonate}}d-none{{end}}">
  658. <label for="idUID" class="col-md-3 col-form-label">UID</label>
  659. <div class="col-md-3">
  660. <input id="idUID" type="number" min="0" max="2147483647" class="form-control" name="uid" value="{{.User.UID}}" />
  661. </div>
  662. <div class="col-md-1"></div>
  663. <label for="idGID" class="col-md-2 col-form-label">GID</label>
  664. <div class="col-md-3">
  665. <input id="idGID" type="number" min="0" max="2147483647" class="form-control" name="gid" value="{{.User.GID}}" />
  666. </div>
  667. </div>
  668. </div>
  669. </div>
  670. </div>
  671. </div>
  672. {{- if eq .Mode 2}}
  673. <div class="form-group row align-items-center mt-10">
  674. <label data-i18n="user.disconnect" class="col-md-3 col-form-label" for="idDisconnect">Disconnect the user after the update</label>
  675. <div class="col-md-9">
  676. <div class="form-check form-switch form-check-custom form-check-solid">
  677. <input class="form-check-input" type="checkbox" id="idDisconnect" name="disconnect"/>
  678. <label data-i18n="user.disconnect_help" class="form-check-label fw-semibold text-gray-800" for="idDisconnect">
  679. This way you force the user to login again, if connected, and so to use the new configuration
  680. </label>
  681. </div>
  682. </div>
  683. </div>
  684. {{- end}}
  685. <div class="d-flex justify-content-end mt-12">
  686. <input type="hidden" name="expiration_date" id="hidden_start_datetime" value="">
  687. <input type="hidden" name="_form_token" value="{{.CSRFToken}}">
  688. {{- if eq .Mode 3}}
  689. <button type="submit" id="form_generate_submit" class="btn btn-secondary px-10 me-10" name="form_action" value="export_from_template">
  690. <span data-i18n="user.submit_export" class="indicator-label">
  691. Generate and export users
  692. </span>
  693. <span data-i18n="general.wait" class="indicator-progress">
  694. Please wait...
  695. <span class="spinner-border spinner-border-sm align-middle ms-2"></span>
  696. </span>
  697. </button>
  698. {{- end}}
  699. <button type="submit" id="form_submit" class="btn btn-primary px-10" name="form_action" value="submit">
  700. <span {{if eq .Mode 3}}data-i18n="user.submit_generate"{{else}}data-i18n="general.submit"{{end}} class="indicator-label">
  701. Submit
  702. </span>
  703. <span data-i18n="general.wait" class="indicator-progress">
  704. Please wait...
  705. <span class="spinner-border spinner-border-sm align-middle ms-2"></span>
  706. </span>
  707. </button>
  708. </div>
  709. </form>
  710. </div>
  711. </div>
  712. {{- end}}
  713. {{- define "extra_js"}}
  714. <script {{- if .CSPNonce}} nonce="{{.CSPNonce}}"{{- end}} src="{{.StaticURL}}/assets/plugins/custom/formrepeater/formrepeater.bundle.js"></script>
  715. <script {{- if .CSPNonce}} nonce="{{.CSPNonce}}"{{- end}} src="{{.StaticURL}}/assets/plugins/custom/flatpickr/l10n/it.js"></script>
  716. <script type="text/javascript" {{- if .CSPNonce}} nonce="{{.CSPNonce}}"{{- end}}>
  717. function onFilesystemChanged(val){
  718. $('.form-group.fsconfig').hide();
  719. $('.form-group.fsconfig-'+val).show();
  720. }
  721. $(document).on("i18nload", function(){
  722. onFilesystemChanged('{{.User.FsConfig.Provider.Name}}');
  723. $('#idFilesystem').on("change", function(){
  724. onFilesystemChanged(this.value);
  725. });
  726. });
  727. $(document).on("i18nshow", function(){
  728. //{{- if eq .Mode 3}}
  729. initRepeater('#template_users');
  730. //{{- else}}
  731. initRepeater('#public_keys');
  732. //{{- end}}
  733. initRepeater('#virtual_folders');
  734. initRepeater('#directory_permissions');
  735. initRepeater('#directory_patterns');
  736. initRepeater('#src_bandwidth_limits');
  737. initRepeater('#tls_certs');
  738. initRepeater('#access_time_restrictions');
  739. initRepeaterItems();
  740. //{{- if .Error}}
  741. //{{- if ne .LoggedUser.Filters.Preferences.VisibleUserPageSections 0}}
  742. $('#accordionUser .collapse').removeAttr("data-bs-parent").collapse('show');
  743. //{{- end}}
  744. //{{- end}}
  745. const picker = $('#id_expiration').flatpickr({
  746. enableTime: false,
  747. time_24hr: true,
  748. formatDate: (date, format, locale) => {
  749. return $.t('general.datetime', {
  750. val: new Date(date),
  751. formatParams: {
  752. val: { year: 'numeric', month: 'numeric', day: 'numeric' },
  753. }
  754. });
  755. },
  756. defaultHour: 23,
  757. defaultMinute: 59,
  758. locale: i18next.resolvedLanguage,
  759. onChange: function(selectedDates, dateStr, instance) {
  760. if (selectedDates.length > 0){
  761. $('#id_expiration_clear').removeClass("d-none");
  762. } else {
  763. $('#id_expiration_clear').addClass("d-none");
  764. }
  765. }
  766. });
  767. //{{ if gt .User.ExpirationDate 0 }}
  768. let input_dt = moment('{{.User.ExpirationDate }}', 'x').format('YYYY-MM-DD');
  769. picker.setDate(input_dt, true);
  770. //{{ end }}
  771. $('#id_expiration_clear').on("click", function(e){
  772. e.preventDefault();
  773. picker.clear();
  774. });
  775. $("#user_form").submit(function (event) {
  776. $('#hidden_start_datetime').val("");
  777. let dt = picker.selectedDates;
  778. if (dt.length > 0) {
  779. let d = dt[0];
  780. if (d) {
  781. let dateString = moment.utc(d).format('YYYY-MM-DD HH:mm:ss');
  782. $('#hidden_start_datetime').val(dateString);
  783. }
  784. }
  785. //{{- if ne .Mode 3}}
  786. let submitButton = document.querySelector('#form_submit');
  787. submitButton.setAttribute('data-kt-indicator', 'on');
  788. submitButton.disabled = true;
  789. //{{- end}}
  790. });
  791. });
  792. </script>
  793. {{- end}}