group.html 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  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. {{- template "infomsg" "group.help"}}
  20. {{- template "errmsg" .Error}}
  21. <form id="group_form" enctype="multipart/form-data" action="{{.CurrentURL}}" method="POST" autocomplete="off">
  22. <div class="form-group row">
  23. <label for="idGroupName" data-i18n="general.name" class="col-md-3 col-form-label">Name</label>
  24. <div class="col-md-9">
  25. <input id="idGroupName" type="text" placeholder="" name="name" value="{{.Group.Name}}" maxlength="255" autocomplete="off"
  26. spellcheck="false" required {{if eq .Mode 2}}class="form-control-plaintext readonly-input" readonly{{else}}class="form-control"{{end}} />
  27. </div>
  28. </div>
  29. <div class="form-group row mt-10">
  30. <label for="idDescription" data-i18n="general.description" class="col-md-3 col-form-label">Description</label>
  31. <div class="col-md-9">
  32. <input id="idDescription" type="text" class="form-control" name="description" value="{{.Group.Description}}" maxlength="255">
  33. </div>
  34. </div>
  35. {{- template "fshtml" .FsWrapper}}
  36. {{- if .VirtualFolders}}
  37. <div class="card mt-10 {{if .LoggedUser.Filters.Preferences.HideVirtualFolders}}d-none{{end}}">
  38. <div class="card-header bg-light">
  39. <h3 data-i18n="title.folders" class="card-title section-title-inner">Virtual folders</h3>
  40. </div>
  41. <div class="card-body">
  42. <div id="virtual_folders">
  43. {{- template "infomsg-no-mb" "user.virtual_folders_help"}}
  44. <div class="form-group">
  45. <div data-repeater-list="virtual_folders">
  46. {{- range $idx, $val := .Group.VirtualFolders}}
  47. <div data-repeater-item>
  48. <div data-repeater-item>
  49. <div class="form-group row">
  50. <div class="col-md-3 mt-3 mt-md-8">
  51. <input data-i18n="[placeholder]virtual_folders.mount_path" type="text" class="form-control" name="vfolder_path" value="{{$val.VirtualPath}}" />
  52. </div>
  53. <div class="col-md-3 mt-3 mt-md-8">
  54. <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">
  55. <option value=""></option>
  56. {{- range $.VirtualFolders}}
  57. <option value="{{.Name}}" {{- if eq $val.Name .Name}} selected{{- end}}>{{.Name}}</option>
  58. {{- end}}
  59. </select>
  60. </div>
  61. <div class="col-md-3 mt-3 mt-md-8">
  62. <input type="text" class="form-control" name="vfolder_quota_size" value="{{HumanizeBytes $val.QuotaSize}}" />
  63. <div class="form-text" data-i18n="virtual_folders.quota_size"></div>
  64. </div>
  65. <div class="col-md-2 mt-3 mt-md-8">
  66. <input type="number" min="-1" class="form-control" name="vfolder_quota_files" value="{{$val.QuotaFiles}}" />
  67. <div class="form-text" data-i18n="virtual_folders.quota_files"></div>
  68. </div>
  69. <div class="col-md-1 mt-3 mt-md-8">
  70. <a href="#" data-repeater-delete
  71. class="btn btn-light-danger ps-5 pe-4">
  72. <i class="ki-duotone ki-trash fs-2">
  73. <span class="path1"></span>
  74. <span class="path2"></span>
  75. <span class="path3"></span>
  76. <span class="path4"></span>
  77. <span class="path5"></span>
  78. </i>
  79. </a>
  80. </div>
  81. </div>
  82. </div>
  83. </div>
  84. {{- else}}
  85. <div data-repeater-item>
  86. <div class="form-group row">
  87. <div class="col-md-3 mt-3 mt-md-8">
  88. <input data-i18n="[placeholder]virtual_folders.mount_path" type="text" class="form-control" name="vfolder_path" value="" />
  89. </div>
  90. <div class="col-md-3 mt-3 mt-md-8">
  91. <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">
  92. <option value=""></option>
  93. {{- range .VirtualFolders}}
  94. <option value="{{.Name}}">{{.Name}}</option>
  95. {{- end}}
  96. </select>
  97. </div>
  98. <div class="col-md-3 mt-3 mt-md-8">
  99. <input type="text" class="form-control" name="vfolder_quota_size" value="" />
  100. <div class="form-text" data-i18n="virtual_folders.quota_size"></div>
  101. </div>
  102. <div class="col-md-2 mt-3 mt-md-8">
  103. <input type="number" min="-1" class="form-control" name="vfolder_quota_files" value="" />
  104. <div class="form-text" data-i18n="virtual_folders.quota_files"></div>
  105. </div>
  106. <div class="col-md-1 mt-3 mt-md-8">
  107. <a href="#" data-repeater-delete
  108. class="btn btn-light-danger ps-5 pe-4">
  109. <i class="ki-duotone ki-trash fs-2">
  110. <span class="path1"></span>
  111. <span class="path2"></span>
  112. <span class="path3"></span>
  113. <span class="path4"></span>
  114. <span class="path5"></span>
  115. </i>
  116. </a>
  117. </div>
  118. </div>
  119. </div>
  120. {{- end}}
  121. </div>
  122. </div>
  123. <div class="form-group mt-5">
  124. <a href="#" data-repeater-create class="btn btn-light-primary">
  125. <i class="ki-duotone ki-plus fs-3"></i>
  126. <span data-i18n="general.add">Add</span>
  127. </a>
  128. </div>
  129. </div>
  130. </div>
  131. </div>
  132. {{- end}}
  133. <div class="accordion shadow-sm mt-10" id="accordionUser">
  134. <div class="accordion-item">
  135. <h2 class="accordion-header" id="headingPermissions">
  136. <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">
  137. <span data-i18n="general.acls">ACLs</span>
  138. </button>
  139. </h2>
  140. <div id="collapsePermissions" class="accordion-collapse collapse" aria-labelledby="headingPermissions" data-bs-parent="#accordionUser">
  141. <div class="accordion-body">
  142. <div class="card mt-10">
  143. <div class="card-header bg-light">
  144. <h3 data-i18n="filters.directory_permissions" class="card-title section-title-inner">Per-directory permissions</h3>
  145. </div>
  146. <div class="card-body">
  147. <div id="directory_permissions">
  148. {{- template "infomsg-no-mb" "filters.directory_permissions_help"}}
  149. <div class="form-group">
  150. <div data-repeater-list="directory_permissions">
  151. {{- range $idx, $dirPerms := .Group.GetPermissions -}}
  152. <div data-repeater-item>
  153. <div class="form-group row">
  154. <div class="col-md-6 mt-3 mt-md-8">
  155. <input data-i18n="[placeholder]filters.directory_path_help" type="text" class="form-control" name="sub_perm_path" value="{{$dirPerms.Path}}" />
  156. </div>
  157. <div class="col-md-5 mt-3 mt-md-8">
  158. <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>
  159. {{- range $validPerm := $.ValidPerms}}
  160. <option value="{{$validPerm}}" {{- range $perm := $dirPerms.Permissions }}{{- if eq $perm $validPerm}} selected{{- end}}{{- end}}>{{$validPerm}}</option>
  161. {{- end}}
  162. </select>
  163. </div>
  164. <div class="col-md-1 mt-3 mt-md-8">
  165. <a href="#" data-repeater-delete
  166. class="btn btn-light-danger ps-5 pe-4">
  167. <i class="ki-duotone ki-trash fs-2">
  168. <span class="path1"></span>
  169. <span class="path2"></span>
  170. <span class="path3"></span>
  171. <span class="path4"></span>
  172. <span class="path5"></span>
  173. </i>
  174. </a>
  175. </div>
  176. </div>
  177. </div>
  178. {{- else}}
  179. <div data-repeater-item>
  180. <div class="form-group row">
  181. <div class="col-md-6 mt-3 mt-md-8">
  182. <input data-i18n="[placeholder]filters.directory_path_help" type="text" class="form-control" name="sub_perm_path" value="" />
  183. </div>
  184. <div class="col-md-5 mt-3 mt-md-8">
  185. <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>
  186. {{- range $validPerm := .ValidPerms}}
  187. <option value="{{$validPerm}}">{{$validPerm}}</option>
  188. {{- end}}
  189. </select>
  190. </div>
  191. <div class="col-md-1 mt-3 mt-md-8">
  192. <a href="#" data-repeater-delete
  193. class="btn btn-light-danger ps-5 pe-4">
  194. <i class="ki-duotone ki-trash fs-2">
  195. <span class="path1"></span>
  196. <span class="path2"></span>
  197. <span class="path3"></span>
  198. <span class="path4"></span>
  199. <span class="path5"></span>
  200. </i>
  201. </a>
  202. </div>
  203. </div>
  204. </div>
  205. {{- end}}
  206. </div>
  207. </div>
  208. <div class="form-group mt-5">
  209. <a href="#" data-repeater-create class="btn btn-light-primary">
  210. <i class="ki-duotone ki-plus fs-3"></i>
  211. <span data-i18n="general.add">Add</span>
  212. </a>
  213. </div>
  214. </div>
  215. </div>
  216. </div>
  217. {{- template "user_group_perms" .Group.UserSettings.Filters}}
  218. {{- template "user_group_access_time" .Group.UserSettings.Filters}}
  219. <div class="form-group row mt-10">
  220. <label for="idMaxSessions" data-i18n="filters.max_sessions" class="col-md-3 col-form-label">Max sessions</label>
  221. <div class="col-md-9">
  222. <input id="idMaxSessions" type="number" min="0" class="form-control" name="max_sessions" value="{{.Group.UserSettings.MaxSessions}}" aria-describedby="idMaxSessionsHelp" />
  223. <div id="idMaxSessionsHelp" class="form-text" data-i18n="filters.max_sessions_help"></div>
  224. </div>
  225. </div>
  226. <div class="form-group row mt-10">
  227. <label for="idProtocols" data-i18n="filters.denied_protocols" class="col-md-3 col-form-label">
  228. Denied protocols
  229. </label>
  230. <div class="col-md-9">
  231. <select id="idProtocols" name="denied_protocols" class="form-select" data-control="i18n-select2" data-close-on-select="false" multiple>
  232. {{- range $protocol := .ValidProtocols}}
  233. <option value="{{$protocol}}" {{- range $p :=$.Group.UserSettings.Filters.DeniedProtocols }}{{- if eq $p $protocol}} selected{{- end}}{{- end}}>{{$protocol}}</option>
  234. {{- end}}
  235. </select>
  236. </div>
  237. </div>
  238. <div class="form-group row mt-10">
  239. <label for="idLoginMethods" data-i18n="filters.denied_login_methods" class="col-md-3 col-form-label">
  240. Denied login methods
  241. </label>
  242. <div class="col-md-9">
  243. <select id="idLoginMethods" name="denied_login_methods" class="form-select" data-control="i18n-select2" data-close-on-select="false" multiple aria-describedby="idLoginMethodsHelp">
  244. {{- range $method := .ValidLoginMethods}}
  245. <option value="{{$method}}" {{- range $m :=$.Group.UserSettings.Filters.DeniedLoginMethods }}{{- if eq $m $method}} selected{{- end}}{{- end}}>{{$method}}</option>
  246. {{- end}}
  247. </select>
  248. <div id="idLoginMethodsHelp" data-i18n="filters.denied_login_methods_help" class="form-text">
  249. </div>
  250. </div>
  251. </div>
  252. <div class="form-group row mt-10">
  253. <label for="idTwoFactorProtocols" data-i18n="2fa.require_for" class="col-md-3 col-form-label">
  254. Require 2FA for
  255. </label>
  256. <div class="col-md-9">
  257. <select id="idTwoFactorProtocols" name="required_two_factor_protocols" class="form-select" data-control="i18n-select2" data-close-on-select="false" multiple>
  258. {{- range $protocol := .TwoFactorProtocols}}
  259. <option value="{{$protocol}}" {{- range $p :=$.Group.UserSettings.Filters.TwoFactorAuthProtocols }}{{- if eq $p $protocol}} selected{{- end}}{{- end}}>{{$protocol}}</option>
  260. {{end}}
  261. </select>
  262. </div>
  263. </div>
  264. <div class="form-group row mt-10">
  265. <label for="idWebClient" data-i18n="filters.web_client_options" class="col-md-3 col-form-label">
  266. Web client/REST API
  267. </label>
  268. <div class="col-md-9">
  269. <select id="idWebClient" name="web_client_options" class="form-select" data-control="i18n-select2" data-close-on-select="false" multiple>
  270. {{- range $option := .WebClientOptions}}
  271. <option value="{{$option}}" {{- range $p :=$.Group.UserSettings.Filters.WebClient }}{{- if eq $p $option}}selected{{- end}}{{- end}}>{{$option}}</option>
  272. {{- end}}
  273. </select>
  274. </div>
  275. </div>
  276. <div class="form-group row mt-10">
  277. <label for="idDeniedIP" data-i18n="general.denied_ip_mask" class="col-md-3 col-form-label">Denied IP/Mask</label>
  278. <div class="col-md-9">
  279. <textarea class="form-control" id="idDeniedIP" name="denied_ip" aria-describedby="idDeniedIPHelp"
  280. rows="3">{{.Group.GetDeniedIPAsString}}</textarea>
  281. <div id="idDeniedIPHelp" class="form-text" data-i18n="general.ip_mask_help"></div>
  282. </div>
  283. </div>
  284. <div class="form-group row mt-10">
  285. <label for="idAllowedIP" data-i18n="general.allowed_ip_mask" class="col-md-3 col-form-label">Allowed IP/Mask</label>
  286. <div class="col-md-9">
  287. <textarea class="form-control" id="idAllowedIP" name="allowed_ip" aria-describedby="idAllowedIPHelp"
  288. rows="3">{{.Group.GetAllowedIPAsString}}</textarea>
  289. <div id="idAllowedIPHelp" class="form-text" data-i18n="general.ip_mask_help"></div>
  290. </div>
  291. </div>
  292. </div>
  293. </div>
  294. </div>
  295. <div class="accordion-item">
  296. <h2 class="accordion-header" id="headingQuota">
  297. <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">
  298. <span data-i18n="general.quota_limits">Disk quota and bandwidth limits</span>
  299. </button>
  300. </h2>
  301. <div id="collapseQuota" class="accordion-collapse collapse" aria-labelledby="headingQuota" data-bs-parent="#accordionUser">
  302. <div class="accordion-body">
  303. {{- template "user_group_quota" .Group.UserSettings}}
  304. </div>
  305. </div>
  306. </div>
  307. <div class="accordion-item">
  308. <h2 class="accordion-header" id="headingAdvanced">
  309. <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">
  310. <span data-i18n="general.advanced_settings">Advanced settings</span>
  311. </button>
  312. </h2>
  313. <div id="collapseAdvanced" class="accordion-collapse collapse" aria-labelledby="headingAdvanced" data-bs-parent="#accordionUser">
  314. <div class="accordion-body">
  315. <div class="form-group row mt-10">
  316. <label for="idExpiresIn" data-i18n="user.expires_in" class="col-md-3 col-form-label">Expires in</label>
  317. <div class="col-md-9">
  318. <input id="idExpiresIn" type="number" min="0" class="form-control" name="expires_in" value="{{.Group.UserSettings.ExpiresIn}}" aria-describedby="idExpiresIn" />
  319. <div id="idExpiresInHelp" class="form-text" data-i18n="user.expires_in_help"></div>
  320. </div>
  321. </div>
  322. {{- template "user_group_profile" .Group.UserSettings.Filters}}
  323. {{- template "user_group_advanced" .Group.UserSettings.Filters}}
  324. <div class="form-group row mt-10 {{if not .Group.HasExternalAuth}}d-none{{end}}">
  325. <label for="idExtAuthCacheTime" data-i18n="filters.external_auth_cache_time" class="col-md-3 col-form-label">External auth cache time</label>
  326. <div class="col-md-9">
  327. <input id="idExtAuthCacheTime" type="number" min="0" class="form-control" name="external_auth_cache_time" value="{{.Group.UserSettings.Filters.ExternalAuthCacheTime}}" aria-describedby="idExtAuthCacheTimeHelp" />
  328. <div id="idExtAuthCacheTimeHelp" class="form-text" data-i18n="filters.external_auth_cache_time_help"></div>
  329. </div>
  330. </div>
  331. </div>
  332. </div>
  333. </div>
  334. </div>
  335. <div class="d-flex justify-content-end mt-12">
  336. <input type="hidden" name="_form_token" value="{{.CSRFToken}}">
  337. <button type="submit" id="form_submit" class="btn btn-primary px-10" name="form_action" value="submit">
  338. <span data-i18n="general.submit" class="indicator-label">
  339. Submit
  340. </span>
  341. <span data-i18n="general.wait" class="indicator-progress">
  342. Please wait...
  343. <span class="spinner-border spinner-border-sm align-middle ms-2"></span>
  344. </span>
  345. </button>
  346. </div>
  347. </form>
  348. </div>
  349. </div>
  350. {{- end}}
  351. {{- define "extra_js"}}
  352. <script {{- if .CSPNonce}} nonce="{{.CSPNonce}}"{{- end}} src="{{.StaticURL}}/assets/plugins/custom/formrepeater/formrepeater.bundle.js"></script>
  353. <script type="text/javascript" {{- if .CSPNonce}} nonce="{{.CSPNonce}}"{{- end}}>
  354. function onFilesystemChanged(val){
  355. $('.form-group.fsconfig').hide();
  356. $('.form-group.fsconfig-'+val).show();
  357. }
  358. $(document).on("i18nload", function(){
  359. initRepeater('#virtual_folders');
  360. initRepeater('#directory_permissions');
  361. initRepeater('#directory_patterns');
  362. initRepeater('#src_bandwidth_limits');
  363. initRepeater('#access_time_restrictions');
  364. initRepeaterItems();
  365. //{{- if .Error}}
  366. $('#accordionUser .collapse').removeAttr("data-bs-parent").collapse('show');
  367. //{{- end}}
  368. onFilesystemChanged('{{.Group.UserSettings.FsConfig.Provider.Name}}');
  369. $('#idFilesystem').on("change", function(){
  370. onFilesystemChanged(this.value);
  371. });
  372. });
  373. $(document).on("i18nshow", function(){
  374. $('#group_form').submit(function (event) {
  375. let submitButton = document.querySelector('#form_submit');
  376. submitButton.setAttribute('data-kt-indicator', 'on');
  377. submitButton.disabled = true;
  378. });
  379. });
  380. </script>
  381. {{- end}}