user.html 65 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037
  1. {{template "base" .}}
  2. {{define "title"}}{{.Title}}{{end}}
  3. {{define "extra_css"}}
  4. <link href="{{.StaticURL}}/vendor/tempusdominus/css/tempusdominus-bootstrap-4.min.css" rel="stylesheet">
  5. {{end}}
  6. {{define "page_body"}}
  7. <!-- Page Heading -->
  8. <div class="card shadow mb-4">
  9. <div class="card-header py-3">
  10. <h6 class="m-0 font-weight-bold text-primary">{{.Title}}</h6>
  11. </div>
  12. <div class="card-body">
  13. {{if .Error}}
  14. <div class="card mb-4 border-left-warning">
  15. <div class="card-body text-form-error">{{.Error}}</div>
  16. </div>
  17. {{end}}
  18. {{if eq .Mode 3}}
  19. <div class="card mb-4 border-left-info">
  20. <div class="card-body">
  21. Create and save one or more new users or generate a data provider independent JSON file to import.
  22. <br>
  23. The following placeholders are supported:
  24. <br><br>
  25. <ul>
  26. <li><span class="text-success">%username%</span> will be replaced with the specified username</li>
  27. <li><span class="text-success">%password%</span> will be replaced with the specified password</li>
  28. </ul>
  29. The generated users file can be imported from the "Maintenance" section.
  30. {{if .User.Username}}
  31. <br>
  32. Please note that no credentials were copied from user "{{.User.Username}}", you have to set them explicitly.
  33. {{end}}
  34. </div>
  35. </div>
  36. {{end}}
  37. <form id="user_form" enctype="multipart/form-data" action="{{.CurrentURL}}" method="POST" autocomplete="off" {{if eq .Mode 3}}target="_blank"{{end}}>
  38. {{if eq .Mode 3}}
  39. <div class="card bg-light mb-3">
  40. <div class="card-header">
  41. <b>Users</b>
  42. </div>
  43. <div class="card-body">
  44. <h6 class="card-title mb-4">For each user set the username and at least one of the password and public key</h6>
  45. <div class="form-group row">
  46. <div class="col-md-12 form_field_tpl_users_outer">
  47. <div class="row form_field_tpl_user_outer_row">
  48. <div class="form-group col-md-3">
  49. <input type="text" class="form-control" id="idTplUsername0" name="tpl_username" placeholder="Username" maxlength="255">
  50. </div>
  51. <div class="form-group col-md-3">
  52. <input type="password" class="form-control" id="idTplPassword0" name="tpl_password" placeholder="Password" maxlength="255">
  53. </div>
  54. <div class="form-group col-md-5">
  55. <textarea class="form-control" id="idTplPublicKey0" name="tpl_public_keys" rows="5"
  56. placeholder="Paste your public key here"></textarea>
  57. </div>
  58. <div class="form-group col-md-1">
  59. <button class="btn btn-circle btn-danger remove_tpl_user_btn_frm_field">
  60. <i class="fas fa-trash"></i>
  61. </button>
  62. </div>
  63. </div>
  64. </div>
  65. </div>
  66. <div class="row mx-1">
  67. <button type="button" class="btn btn-secondary add_new_tpl_user_field_btn">
  68. <i class="fas fa-plus"></i> Add new user
  69. </button>
  70. </div>
  71. </div>
  72. </div>
  73. <input type="hidden" name="username" id="idUsername" value="{{.User.Username}}">
  74. {{else}}
  75. <div class="form-group row">
  76. <label for="idUsername" class="col-sm-2 col-form-label">Username</label>
  77. <div class="col-sm-10">
  78. <input type="text" class="form-control" id="idUsername" name="username" placeholder=""
  79. value="{{.User.Username}}" maxlength="255" autocomplete="nope" required {{if ge .Mode 2}}readonly{{end}}>
  80. </div>
  81. </div>
  82. {{end}}
  83. {{if ne .Mode 3}}
  84. <div class="form-group row">
  85. <label for="idPassword" class="col-sm-2 col-form-label">Password</label>
  86. <div class="col-sm-10">
  87. <input type="password" class="form-control" id="idPassword" name="password" value="{{.User.Password}}" placeholder="">
  88. </div>
  89. </div>
  90. <div class="card bg-light mb-3">
  91. <div class="card-header">
  92. <b>Public keys</b>
  93. </div>
  94. <div class="card-body">
  95. <div class="form-group row">
  96. <div class="col-md-12 form_field_pk_outer">
  97. {{range $idx, $val := .User.PublicKeys}}
  98. <div class="row form_field_pk_outer_row">
  99. <div class="form-group col-md-11">
  100. <textarea class="form-control" id="idPublicKey{{$idx}}" name="public_keys" rows="3"
  101. placeholder="Paste your public key here">{{$val}}</textarea>
  102. </div>
  103. <div class="form-group col-md-1">
  104. <button class="btn btn-circle btn-danger remove_pk_btn_frm_field">
  105. <i class="fas fa-trash"></i>
  106. </button>
  107. </div>
  108. </div>
  109. {{else}}
  110. <div class="row form_field_pk_outer_row">
  111. <div class="form-group col-md-11">
  112. <textarea class="form-control" id="idPublicKey0" name="public_keys" rows="3"
  113. placeholder="Paste your public key here"></textarea>
  114. </div>
  115. <div class="form-group col-md-1">
  116. <button class="btn btn-circle btn-danger remove_pk_btn_frm_field">
  117. <i class="fas fa-trash"></i>
  118. </button>
  119. </div>
  120. </div>
  121. {{end}}
  122. </div>
  123. </div>
  124. <div class="row mx-1">
  125. <button type="button" class="btn btn-secondary add_new_pk_field_btn">
  126. <i class="fas fa-plus"></i> Add new public key
  127. </button>
  128. </div>
  129. </div>
  130. </div>
  131. {{end}}
  132. {{template "fshtml" .FsWrapper}}
  133. {{if .VirtualFolders}}
  134. <div class="card bg-light mb-3">
  135. <div class="card-header">
  136. <b>Virtual folders</b>
  137. </div>
  138. <div class="card-body">
  139. <h6 class="card-title mb-4">Quota -1 means included within user quota, 0 unlimited. Don't set -1 for shared folders</h6>
  140. <div class="form-group row">
  141. <div class="col-md-12 form_field_vfolders_outer">
  142. {{range $idx, $val := .User.VirtualFolders}}
  143. <div class="row form_field_vfolder_outer_row">
  144. <div class="form-group col-md-3">
  145. <input type="text" class="form-control" id="idVolderPath{{$idx}}" name="vfolder_path" placeholder="mount path, i.e. /vfolder" value="{{$val.VirtualPath}}" maxlength="255">
  146. </div>
  147. <div class="form-group col-md-3">
  148. <select class="form-control" id="idVfolderName{{$idx}}" name="vfolder_name">
  149. <option value=""></option>
  150. {{range $.VirtualFolders}}
  151. <option value="{{.Name}}" {{if eq $val.Name .Name}}selected{{end}}>{{.Name}}</option>
  152. {{end}}
  153. </select>
  154. </div>
  155. <div class="form-group col-md-3">
  156. <input type="number" class="form-control" id="idVfolderQuotaSize{{$idx}}" name="vfolder_quota_size"
  157. value="{{$val.QuotaSize}}" min="-1" aria-describedby="vqsHelpBlock{{$idx}}">
  158. <small id="vqsHelpBlock{{$idx}}" class="form-text text-muted">
  159. Quota size (bytes)
  160. </small>
  161. </div>
  162. <div class="form-group col-md-2">
  163. <input type="number" class="form-control" id="idVfolderQuotaFiles{{$idx}}" name="vfolder_quota_files"
  164. value="{{$val.QuotaFiles}}" min="-1" aria-describedby="vqfHelpBlock{{$idx}}">
  165. <small id="vqfHelpBlock{{$idx}}" class="form-text text-muted">
  166. Quota files
  167. </small>
  168. </div>
  169. <div class="form-group col-md-1">
  170. <button class="btn btn-circle btn-danger remove_vfolder_btn_frm_field">
  171. <i class="fas fa-trash"></i>
  172. </button>
  173. </div>
  174. </div>
  175. {{else}}
  176. <div class="row form_field_vfolder_outer_row">
  177. <div class="form-group col-md-3">
  178. <input type="text" class="form-control" id="idVolderPath0" name="vfolder_path" placeholder="mount path, i.e. /vfolder" value="" maxlength="255">
  179. </div>
  180. <div class="form-group col-md-3">
  181. <select class="form-control" id="idVfolderName0" name="vfolder_name">
  182. <option value=""></option>
  183. {{range .VirtualFolders}}
  184. <option value="{{.Name}}">{{.Name}}</option>
  185. {{end}}
  186. </select>
  187. </div>
  188. <div class="form-group col-md-3">
  189. <input type="number" class="form-control" id="idVfolderQuotaSize0" name="vfolder_quota_size"
  190. value="" min="-1" aria-describedby="vqsHelpBlock0">
  191. <small id="vqsHelpBlock0" class="form-text text-muted">
  192. Quota size (bytes)
  193. </small>
  194. </div>
  195. <div class="form-group col-md-2">
  196. <input type="number" class="form-control" id="idVfolderQuotaFiles0" name="vfolder_quota_files"
  197. value="" min="-1" aria-describedby="vqfHelpBlock0">
  198. <small id="vqfHelpBlock0" class="form-text text-muted">
  199. Quota files
  200. </small>
  201. </div>
  202. <div class="form-group col-md-1">
  203. <button class="btn btn-circle btn-danger remove_vfolder_btn_frm_field">
  204. <i class="fas fa-trash"></i>
  205. </button>
  206. </div>
  207. </div>
  208. {{end}}
  209. </div>
  210. </div>
  211. <div class="row mx-1">
  212. <button type="button" class="btn btn-secondary add_new_vfolder_field_btn">
  213. <i class="fas fa-plus"></i> Add new virtual folder
  214. </button>
  215. </div>
  216. </div>
  217. </div>
  218. {{end}}
  219. <div class="accordion" id="accordionUser">
  220. <div class="card">
  221. <div class="card-header" id="headingProfile">
  222. <h2 class="mb-0">
  223. <button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse"
  224. data-target="#collapseProfile" aria-expanded="true" aria-controls="collapseProfile">
  225. <h6 class="m-0 font-weight-bold text-primary">Profile</h6>
  226. </button>
  227. </h2>
  228. </div>
  229. <div id="collapseProfile" class="collapse" aria-labelledby="headingProfile" data-parent="#accordionUser">
  230. <div class="card-body">
  231. <div class="form-group row">
  232. <label for="idStatus" class="col-sm-2 col-form-label">Status</label>
  233. <div class="col-sm-10">
  234. <select class="form-control" id="idStatus" name="status">
  235. <option value="1" {{if eq .User.Status 1 }}selected{{end}}>Active</option>
  236. <option value="0" {{if eq .User.Status 0 }}selected{{end}}>Inactive</option>
  237. </select>
  238. </div>
  239. </div>
  240. <div class="form-group row">
  241. <label for="idExpirationDate" class="col-sm-2 col-form-label">Expiration Date</label>
  242. <div class="col-sm-10 input-group date" id="expirationDatePicker" data-target-input="nearest">
  243. <input type="text" class="form-control datetimepicker-input" id="idExpirationDate"
  244. data-target="#expirationDatePicker">
  245. <div class="input-group-append" data-target="#expirationDatePicker" data-toggle="datetimepicker">
  246. <div class="input-group-text"><i class="fas fa-calendar"></i></div>
  247. </div>
  248. </div>
  249. </div>
  250. <div class="form-group row">
  251. <label for="idEmail" class="col-sm-2 col-form-label">Email</label>
  252. <div class="col-sm-10">
  253. <input type="text" class="form-control" id="idEmail" name="email" placeholder=""
  254. value="{{.User.Email}}" maxlength="255" autocomplete="nope">
  255. </div>
  256. </div>
  257. <div class="form-group">
  258. <div class="form-check">
  259. <input type="checkbox" class="form-check-input" id="idAllowAPIKeyAuth" name="allow_api_key_auth"
  260. {{if .User.Filters.AllowAPIKeyAuth}}checked{{end}} aria-describedby="allowAPIKeyAuthHelpBlock">
  261. <label for="idAllowAPIKeyAuth" class="form-check-label">Allow API key authentication</label>
  262. <small id="allowAPIKeyAuthHelpBlock" class="form-text text-muted">
  263. Allow to impersonate this user, in REST API, with an API key
  264. </small>
  265. </div>
  266. </div>
  267. <div class="form-group row">
  268. <label for="idDescription" class="col-sm-2 col-form-label">Description</label>
  269. <div class="col-sm-10">
  270. <input type="text" class="form-control" id="idDescription" name="description" placeholder=""
  271. value="{{.User.Description}}" maxlength="255" aria-describedby="descriptionHelpBlock">
  272. <small id="descriptionHelpBlock" class="form-text text-muted">
  273. Optional description, for example the user full name
  274. </small>
  275. </div>
  276. </div>
  277. <div class="form-group row">
  278. <label for="idAdditionalInfo" class="col-sm-2 col-form-label">Additional info</label>
  279. <div class="col-sm-10">
  280. <textarea class="form-control" id="idAdditionalInfo" name="additional_info" rows="3"
  281. aria-describedby="additionalInfoHelpBlock">{{.User.AdditionalInfo}}</textarea>
  282. <small id="additionalInfoHelpBlock" class="form-text text-muted">
  283. Free form text field
  284. </small>
  285. </div>
  286. </div>
  287. </div>
  288. </div>
  289. </div>
  290. <div class="card">
  291. <div class="card-header" id="headingPermissions">
  292. <h2 class="mb-0">
  293. <button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse"
  294. data-target="#collapsePermissions" aria-expanded="true" aria-controls="collapsePermissions">
  295. <h6 class="m-0 font-weight-bold text-primary">ACLs</h6>
  296. </button>
  297. </h2>
  298. </div>
  299. <div id="collapsePermissions" class="collapse" aria-labelledby="headingPermissions" data-parent="#accordionUser">
  300. <div class="card-body">
  301. <div class="form-group row">
  302. <label for="idPermissions" class="col-sm-2 col-form-label">Permissions</label>
  303. <div class="col-sm-10">
  304. <select class="form-control" id="idPermissions" name="permissions" required multiple>
  305. {{range $validPerm := .ValidPerms}}
  306. <option value="{{$validPerm}}" {{range $perm :=$.RootDirPerms }}{{if eq $perm $validPerm}}selected{{end}}{{end}}>{{$validPerm}}</option>
  307. {{end}}
  308. </select>
  309. </div>
  310. </div>
  311. <div class="card bg-light mb-3">
  312. <div class="card-header">
  313. <b>Per-directory permissions</b>
  314. </div>
  315. <div class="card-body">
  316. <div class="form-group row">
  317. <div class="col-md-12 form_field_dirperms_outer">
  318. {{range $idx, $dirPerms := .User.GetSubDirPermissions -}}
  319. <div class="row form_field_dirperms_outer_row">
  320. <div class="form-group col-md-8">
  321. <input type="text" class="form-control" id="idSubDirPermsPath{{$idx}}" name="sub_perm_path{{$idx}}" placeholder="directory path, i.e. /dir" value="{{$dirPerms.Path}}" maxlength="255">
  322. </div>
  323. <div class="form-group col-md-3">
  324. <select class="form-control" id="idSubDirPermissions{{$idx}}" name="sub_perm_permissions{{$idx}}" multiple>
  325. {{range $validPerm := $.ValidPerms}}
  326. <option value="{{$validPerm}}" {{range $perm := $dirPerms.Permissions }}{{if eq $perm $validPerm}}selected{{end}}{{end}}>{{$validPerm}}</option>
  327. {{end}}
  328. </select>
  329. </div>
  330. <div class="form-group col-md-1">
  331. <button class="btn btn-circle btn-danger remove_dirperms_btn_frm_field">
  332. <i class="fas fa-trash"></i>
  333. </button>
  334. </div>
  335. </div>
  336. {{else}}
  337. <div class="row form_field_dirperms_outer_row">
  338. <div class="form-group col-md-8">
  339. <input type="text" class="form-control" id="idSubDirPermsPath0" name="sub_perm_path0" placeholder="directory path, i.e. /dir" value="" maxlength="255">
  340. </div>
  341. <div class="form-group col-md-3">
  342. <select class="form-control" id="idSubDirPermissions0" name="sub_perm_permissions0" multiple>
  343. {{range $validPerm := .ValidPerms}}
  344. <option value="{{$validPerm}}">{{$validPerm}}</option>
  345. {{end}}
  346. </select>
  347. </div>
  348. <div class="form-group col-md-1">
  349. <button class="btn btn-circle btn-danger remove_dirperms_btn_frm_field">
  350. <i class="fas fa-trash"></i>
  351. </button>
  352. </div>
  353. </div>
  354. {{end}}
  355. </div>
  356. </div>
  357. <div class="row mx-1">
  358. <button type="button" class="btn btn-secondary add_new_dirperms_field_btn">
  359. <i class="fas fa-plus"></i> Add new directory permissions
  360. </button>
  361. </div>
  362. </div>
  363. </div>
  364. <div class="card bg-light mb-3">
  365. <div class="card-header">
  366. <b>Per-directory pattern restrictions</b>
  367. </div>
  368. <div class="card-body">
  369. <h6 class="card-title mb-4">Comma separated denied or allowed files/directories, based on shell patterns.</h6>
  370. <p class="card-text">Denied entries are visible in directory listing by default, you can hide them by setting the "Hidden" policy, but please be aware that this may cause performance issues for large directories</p>
  371. <div class="form-group row">
  372. <div class="col-md-12 form_field_patterns_outer">
  373. {{range $idx, $pattern := .User.GetFlatFilePatterns -}}
  374. <div class="row form_field_patterns_outer_row">
  375. <div class="form-group col-md-3">
  376. <input type="text" class="form-control" id="idPatternPath{{$idx}}" name="pattern_path{{$idx}}" placeholder="directory path, i.e. /dir" value="{{$pattern.Path}}" maxlength="255">
  377. </div>
  378. <div class="form-group col-md-4">
  379. <input type="text" class="form-control" id="idPatterns{{$idx}}" name="patterns{{$idx}}" placeholder="*.zip,?.txt" value="{{$pattern.GetCommaSeparatedPatterns}}" maxlength="255">
  380. </div>
  381. <div class="form-group col-md-2">
  382. <select class="form-control" id="idPatternType{{$idx}}" name="pattern_type{{$idx}}">
  383. <option value="denied" {{if $pattern.IsDenied}}selected{{end}}>Denied</option>
  384. <option value="allowed" {{if $pattern.IsAllowed}}selected{{end}}>Allowed</option>
  385. </select>
  386. </div>
  387. <div class="form-group col-md-2">
  388. <select class="form-control" id="idPatternPolicy{{$idx}}" name="pattern_policy{{$idx}}">
  389. <option value="0" {{if eq $pattern.DenyPolicy 0}}selected{{end}}>Visible</option>
  390. <option value="1" {{if eq $pattern.DenyPolicy 1}}selected{{end}}>Hidden</option>
  391. </select>
  392. </div>
  393. <div class="form-group col-md-1">
  394. <button class="btn btn-circle btn-danger remove_pattern_btn_frm_field">
  395. <i class="fas fa-trash"></i>
  396. </button>
  397. </div>
  398. </div>
  399. {{else}}
  400. <div class="row form_field_patterns_outer_row">
  401. <div class="form-group col-md-3">
  402. <input type="text" class="form-control" id="idPatternPath0" name="pattern_path0" placeholder="directory path, i.e. /dir" value="" maxlength="255">
  403. </div>
  404. <div class="form-group col-md-4">
  405. <input type="text" class="form-control" id="idPatterns0" name="patterns0" placeholder="*.zip,?.txt" value="" maxlength="255">
  406. </div>
  407. <div class="form-group col-md-2">
  408. <select class="form-control" id="idPatternType0" name="pattern_type0">
  409. <option value="denied">Denied</option>
  410. <option value="allowed">Allowed</option>
  411. </select>
  412. </div>
  413. <div class="form-group col-md-2">
  414. <select class="form-control" id="idPatternPolicy0" name="pattern_policy0">
  415. <option value="0">Visible</option>
  416. <option value="1">Hidden</option>
  417. </select>
  418. </div>
  419. <div class="form-group col-md-1">
  420. <button class="btn btn-circle btn-danger remove_pattern_btn_frm_field">
  421. <i class="fas fa-trash"></i>
  422. </button>
  423. </div>
  424. </div>
  425. {{end}}
  426. </div>
  427. </div>
  428. <div class="row mx-1">
  429. <button type="button" class="btn btn-secondary add_new_pattern_field_btn">
  430. <i class="fas fa-plus"></i> Add new file pattern
  431. </button>
  432. </div>
  433. </div>
  434. </div>
  435. <div class="form-group row">
  436. <label for="idMaxSessions" class="col-sm-2 col-form-label">Max sessions</label>
  437. <div class="col-sm-10">
  438. <input type="number" class="form-control" id="idMaxSessions" name="max_sessions" placeholder=""
  439. value="{{.User.MaxSessions}}" min="0" aria-describedby="sessionsHelpBlock">
  440. <small id="sessionsHelpBlock" class="form-text text-muted">
  441. Maximun number of concurrent sessions. 0 means no limit
  442. </small>
  443. </div>
  444. </div>
  445. <div class="form-group row">
  446. <label for="idProtocols" class="col-sm-2 col-form-label">Denied protocols</label>
  447. <div class="col-sm-10">
  448. <select class="form-control" id="idProtocols" name="denied_protocols" multiple>
  449. {{range $protocol := .ValidProtocols}}
  450. <option value="{{$protocol}}" {{range $p :=$.User.Filters.DeniedProtocols }}{{if eq $p $protocol}}selected{{end}}{{end}}>{{$protocol}}
  451. </option>
  452. {{end}}
  453. </select>
  454. </div>
  455. </div>
  456. <div class="form-group row">
  457. <label for="idLoginMethods" class="col-sm-2 col-form-label">Denied login methods</label>
  458. <div class="col-sm-10">
  459. <select class="form-control" id="idLoginMethods" name="ssh_login_methods" multiple>
  460. {{range $method := .ValidLoginMethods}}
  461. <option value="{{$method}}" {{range $m :=$.User.Filters.DeniedLoginMethods }}{{if eq $m $method}}selected{{end}}{{end}}>{{$method}}
  462. </option>
  463. {{end}}
  464. </select>
  465. </div>
  466. </div>
  467. <div class="form-group row">
  468. <label for="idWebClient" class="col-sm-2 col-form-label">Web client/REST API</label>
  469. <div class="col-sm-10">
  470. <select class="form-control" id="idWebClient" name="web_client_options" multiple>
  471. {{range $option := .WebClientOptions}}
  472. <option value="{{$option}}" {{range $p :=$.User.Filters.WebClient }}{{if eq $p $option}}selected{{end}}{{end}}>{{$option}}
  473. </option>
  474. {{end}}
  475. </select>
  476. </div>
  477. </div>
  478. <div class="form-group row">
  479. <label for="idDeniedIP" class="col-sm-2 col-form-label">Denied IP/Mask</label>
  480. <div class="col-sm-10">
  481. <textarea class="form-control" id="idDeniedIP" name="denied_ip" rows="3" placeholder=""
  482. aria-describedby="deniedIPHelpBlock">{{.User.GetDeniedIPAsString}}</textarea>
  483. <small id="deniedIPHelpBlock" class="form-text text-muted">
  484. Comma separated IP/Mask in CIDR format, for example "192.168.1.0/24,10.8.0.100/32"
  485. </small>
  486. </div>
  487. </div>
  488. <div class="form-group row">
  489. <label for="idAllowedIP" class="col-sm-2 col-form-label">Allowed IP/Mask</label>
  490. <div class="col-sm-10">
  491. <textarea class="form-control" id="idAllowedIP" name="allowed_ip" rows="3" placeholder=""
  492. aria-describedby="allowedIPHelpBlock">{{.User.GetAllowedIPAsString}}</textarea>
  493. <small id="allowedIPHelpBlock" class="form-text text-muted">
  494. Comma separated IP/Mask in CIDR format, for example "192.168.1.0/24,10.8.0.100/32"
  495. </small>
  496. </div>
  497. </div>
  498. </div>
  499. </div>
  500. </div>
  501. <div class="card">
  502. <div class="card-header" id="headingQuota">
  503. <h2 class="mb-0">
  504. <button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse"
  505. data-target="#collapseQuota" aria-expanded="false" aria-controls="collapseQuota">
  506. <h6 class="m-0 font-weight-bold text-primary">Disk quota and bandwidth limits</h6>
  507. </button>
  508. </h2>
  509. </div>
  510. <div id="collapseQuota" class="collapse" aria-labelledby="headingTwo" data-parent="#accordionUser">
  511. <div class="card-body">
  512. <div class="form-group row">
  513. <label for="idQuotaSize" class="col-sm-2 col-form-label">Quota size (bytes)</label>
  514. <div class="col-sm-3">
  515. <input type="number" class="form-control" id="idQuotaSize" name="quota_size" placeholder=""
  516. value="{{.User.QuotaSize}}" min="0" aria-describedby="qsHelpBlock">
  517. <small id="qsHelpBlock" class="form-text text-muted">
  518. 0 means no limit
  519. </small>
  520. </div>
  521. <div class="col-sm-2"></div>
  522. <label for="idQuotaFiles" class="col-sm-2 col-form-label">Quota files</label>
  523. <div class="col-sm-3">
  524. <input type="number" class="form-control" id="idQuotaFiles" name="quota_files" placeholder=""
  525. value="{{.User.QuotaFiles}}" min="0" aria-describedby="qfHelpBlock">
  526. <small id="qfHelpBlock" class="form-text text-muted">
  527. 0 means no limit
  528. </small>
  529. </div>
  530. </div>
  531. <div class="form-group row">
  532. <label for="idMaxUploadSize" class="col-sm-2 col-form-label">Max file upload size (bytes)</label>
  533. <div class="col-sm-10">
  534. <input type="number" class="form-control" id="idMaxUploadSize" name="max_upload_file_size"
  535. placeholder="" value="{{.User.Filters.MaxUploadFileSize}}" min="0"
  536. aria-describedby="fqsHelpBlock">
  537. <small id="fqsHelpBlock" class="form-text text-muted">
  538. Maximum upload size for a single file. 0 means no limit
  539. </small>
  540. </div>
  541. </div>
  542. <div class="form-group row">
  543. <label for="idUploadBandwidth" class="col-sm-2 col-form-label">Bandwidth UL (KB/s)</label>
  544. <div class="col-sm-3">
  545. <input type="number" class="form-control" id="idUploadBandwidth" name="upload_bandwidth"
  546. placeholder="" value="{{.User.UploadBandwidth}}" min="0" aria-describedby="ulHelpBlock">
  547. <small id="ulHelpBlock" class="form-text text-muted">
  548. 0 means no limit
  549. </small>
  550. </div>
  551. <div class="col-sm-2"></div>
  552. <label for="idDownloadBandwidth" class="col-sm-2 col-form-label">Bandwidth DL (KB/s)</label>
  553. <div class="col-sm-3">
  554. <input type="number" class="form-control" id="idDownloadBandwidth" name="download_bandwidth"
  555. placeholder="" value="{{.User.DownloadBandwidth}}" min="0" aria-describedby="dlHelpBlock">
  556. <small id="dlHelpBlock" class="form-text text-muted">
  557. 0 means no limit
  558. </small>
  559. </div>
  560. </div>
  561. <div class="card bg-light mb-3">
  562. <div class="card-header">
  563. <b>Per-source bandwidth limits</b>
  564. </div>
  565. <div class="card-body">
  566. <div class="form-group row">
  567. <div class="col-md-12 form_field_bwlimits_outer">
  568. {{range $idx, $bwLimit := .User.Filters.BandwidthLimits -}}
  569. <div class="row form_field_bwlimits_outer_row">
  570. <div class="form-group col-md-8">
  571. <textarea class="form-control" id="idBandwidthLimitSources{{$idx}}" name="bandwidth_limit_sources{{$idx}}" rows="4" placeholder=""
  572. aria-describedby="bwLimitSourcesHelpBlock{{$idx}}">{{$bwLimit.GetSourcesAsString}}</textarea>
  573. <small id="bwLimitSourcesHelpBlock{{$idx}}" class="form-text text-muted">
  574. Comma separated IP/Mask in CIDR format, for example "192.168.1.0/24,10.8.0.100/32"
  575. </small>
  576. </div>
  577. <div class="col-md-3">
  578. <div class="form-group">
  579. <input type="number" class="form-control" id="idUploadBandwidthSource{{$idx}}" name="upload_bandwidth_source{{$idx}}"
  580. placeholder="" value="{{$bwLimit.UploadBandwidth}}" min="0" aria-describedby="ulHelpBlock{{$idx}}">
  581. <small id="ulHelpBlock{{$idx}}" class="form-text text-muted">
  582. UL (KB/s). 0 means no limit
  583. </small>
  584. </div>
  585. <div class="form-group">
  586. <input type="number" class="form-control" id="idDownloadBandwidthSource{{$idx}}" name="download_bandwidth_source{{$idx}}"
  587. placeholder="" value="{{$bwLimit.DownloadBandwidth}}" min="0" aria-describedby="dlHelpBlock{{$idx}}">
  588. <small id="dlHelpBlock{{$idx}}" class="form-text text-muted">
  589. DL (KB/s). 0 means no limit
  590. </small>
  591. </div>
  592. </div>
  593. <div class="form-group col-md-1">
  594. <button class="btn btn-circle btn-danger remove_bwlimit_btn_frm_field">
  595. <i class="fas fa-trash"></i>
  596. </button>
  597. </div>
  598. </div>
  599. {{else}}
  600. <div class="row form_field_bwlimits_outer_row">
  601. <div class="form-group col-md-8">
  602. <textarea class="form-control" id="idBandwidthLimitSources0" name="bandwidth_limit_sources0" rows="4" placeholder=""
  603. aria-describedby="bwLimitSourcesHelpBlock0"></textarea>
  604. <small id="bwLimitSourcesHelpBlock0" class="form-text text-muted">
  605. Comma separated IP/Mask in CIDR format, for example "192.168.1.0/24,10.8.0.100/32"
  606. </small>
  607. </div>
  608. <div class="col-md-3">
  609. <div class="form-group">
  610. <input type="number" class="form-control" id="idUploadBandwidthSource0" name="upload_bandwidth_source0"
  611. placeholder="" value="" min="0" aria-describedby="ulHelpBlock0">
  612. <small id="ulHelpBlock0" class="form-text text-muted">
  613. UL (KB/s). 0 means no limit
  614. </small>
  615. </div>
  616. <div class="form-group">
  617. <input type="number" class="form-control" id="idDownloadBandwidthSource0" name="download_bandwidth_source0"
  618. placeholder="" value="" min="0" aria-describedby="dlHelpBlock0">
  619. <small id="dlHelpBlock0" class="form-text text-muted">
  620. DL (KB/s). 0 means no limit
  621. </small>
  622. </div>
  623. </div>
  624. <div class="form-group col-md-1">
  625. <button class="btn btn-circle btn-danger remove_bwlimit_btn_frm_field">
  626. <i class="fas fa-trash"></i>
  627. </button>
  628. </div>
  629. </div>
  630. {{end}}
  631. </div>
  632. </div>
  633. <div class="row mx-1">
  634. <button type="button" class="btn btn-secondary add_new_bwlimit_field_btn">
  635. <i class="fas fa-plus"></i> Add new bandwidth limit
  636. </button>
  637. </div>
  638. </div>
  639. </div>
  640. </div>
  641. </div>
  642. </div>
  643. <div class="card">
  644. <div class="card-header" id="headingAdvanced">
  645. <h2 class="mb-0">
  646. <button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse"
  647. data-target="#collapseAdvanced" aria-expanded="false" aria-controls="collapseAdvanced">
  648. <h6 class="m-0 font-weight-bold text-primary">More</h6>
  649. </button>
  650. </h2>
  651. </div>
  652. <div id="collapseAdvanced" class="collapse" aria-labelledby="headingTwo" data-parent="#accordionUser">
  653. <div class="card-body">
  654. <div class="form-group row">
  655. <label for="idTLSUsername" class="col-sm-2 col-form-label">TLS username</label>
  656. <div class="col-sm-10">
  657. <select class="form-control" id="idTLSUsername" name="tls_username" aria-describedby="tlsUsernameHelpBlock">
  658. <option value="None" {{if eq .User.Filters.TLSUsername "None" }}selected{{end}}>None</option>
  659. <option value="CommonName" {{if eq .User.Filters.TLSUsername "CommonName" }}selected{{end}}>Common Name</option>
  660. </select>
  661. <small id="tlsUsernameHelpBlock" class="form-text text-muted">
  662. Defines the TLS certificate field to use as username. Ignored if mutual TLS is disabled
  663. </small>
  664. </div>
  665. </div>
  666. <div class="form-group row {{if not .CanImpersonate}}d-none{{end}}">
  667. <label for="idUID" class="col-sm-2 col-form-label">UID</label>
  668. <div class="col-sm-3">
  669. <input type="number" class="form-control" id="idUID" name="uid" placeholder="" value="{{.User.UID}}"
  670. min="0" max="2147483647">
  671. </div>
  672. <div class="col-sm-2"></div>
  673. <label for="idGID" class="col-sm-2 col-form-label">GID</label>
  674. <div class="col-sm-3">
  675. <input type="number" class="form-control" id="idGID" name="gid" placeholder="" value="{{.User.GID}}"
  676. min="0" max="2147483647">
  677. </div>
  678. </div>
  679. <div class="form-group">
  680. <div class="form-check">
  681. <input type="checkbox" class="form-check-input" id="idDisableFsChecks" name="disable_fs_checks"
  682. {{if .User.Filters.DisableFsChecks}}checked{{end}} aria-describedby="disableFsChecksHelpBlock">
  683. <label for="idDisableFsChecks" class="form-check-label">Disable filesystem checks</label>
  684. <small id="disableFsChecksHelpBlock" class="form-text text-muted">
  685. Disable checks for existence and automatic creation of home directory and virtual folders
  686. </small>
  687. </div>
  688. </div>
  689. <div class="form-group row">
  690. <label for="idHooks" class="col-sm-2 col-form-label">Hooks</label>
  691. <div class="col-sm-10">
  692. <select class="form-control" id="idHooks" name="hooks" multiple>
  693. <option value="external_auth_disabled" {{if .User.Filters.Hooks.ExternalAuthDisabled}}selected{{end}}>
  694. External auth disabled
  695. </option>
  696. <option value="pre_login_disabled" {{if .User.Filters.Hooks.PreLoginDisabled}}selected{{end}}>
  697. Pre-login disabled
  698. </option>
  699. <option value="check_password_disabled" {{if .User.Filters.Hooks.CheckPasswordDisabled}}selected{{end}}>
  700. Check password disabled
  701. </option>
  702. </select>
  703. </div>
  704. </div>
  705. </div>
  706. </div>
  707. </div>
  708. </div>
  709. <br>
  710. {{if eq .Mode 2}}
  711. <div class="form-group">
  712. <div class="form-check">
  713. <input type="checkbox" class="form-check-input" id="idDisconnect" name="disconnect"
  714. aria-describedby="disconnectHelpBlock">
  715. <label for="idDisconnect" class="form-check-label">Disconnect the user after the update</label>
  716. <small id="disconnectHelpBlock" class="form-text text-muted">
  717. This way you force the user to login again, if connected, and so to use the new configuration
  718. </small>
  719. </div>
  720. </div>
  721. {{end}}
  722. <input type="hidden" name="expiration_date" id="hidden_start_datetime" value="">
  723. <input type="hidden" name="_form_token" value="{{.CSRFToken}}">
  724. <div class="col-sm-12 text-right px-0">
  725. {{if eq .Mode 3}}
  726. <button type="submit" class="btn btn-secondary mt-3 px-5" name="form_action" value="export_from_template">Generate and export users</button>
  727. {{end}}
  728. <button type="submit" class="btn btn-primary mt-3 ml-3 px-5" name="form_action" value="submit">{{if eq .Mode 3}}Generate and save new users{{else}}Submit{{end}}</button>
  729. </div>
  730. </form>
  731. </div>
  732. </div>
  733. {{end}}
  734. {{define "extra_js"}}
  735. <script src="{{.StaticURL}}/vendor/moment/js/moment.min.js"></script>
  736. <script src="{{.StaticURL}}/vendor/tempusdominus/js/tempusdominus-bootstrap-4.min.js"></script>
  737. <script type="text/javascript">
  738. $(document).ready(function () {
  739. {{if .Error}}
  740. $('#accordionUser .collapse').removeAttr("data-parent").collapse('show');
  741. {{end}}
  742. $('#expirationDatePicker').datetimepicker({
  743. format: 'YYYY-MM-DD',
  744. buttons: {
  745. showClear: false,
  746. showClose: true,
  747. showToday: false
  748. }
  749. });
  750. {{ if gt .User.ExpirationDate 0 }}
  751. var input_dt = moment({{.User.ExpirationDate }}).format('YYYY-MM-DD');
  752. $('#idExpirationDate').val(input_dt);
  753. $('#expirationDatePicker').datetimepicker('viewDate', input_dt);
  754. {{ end }}
  755. $("#user_form").submit(function (event) {
  756. var dt = $('#idExpirationDate').val();
  757. if (dt) {
  758. var d = $('#expirationDatePicker').datetimepicker('viewDate');
  759. if (d) {
  760. var dateString = moment(d).format('YYYY-MM-DD HH:mm:ss');
  761. $('#hidden_start_datetime').val(dateString);
  762. } else {
  763. $('#hidden_start_datetime').val("");
  764. }
  765. } else {
  766. $('#hidden_start_datetime').val("");
  767. }
  768. return true;
  769. });
  770. onFilesystemChanged('{{.User.FsConfig.Provider.Name}}');
  771. $("body").on("click", ".add_new_pk_field_btn", function () {
  772. var index = $(".form_field_pk_outer").find(".form_field_pk_outer_row").length;
  773. while (document.getElementById("idPublicKey"+index) != null){
  774. index++;
  775. }
  776. $(".form_field_pk_outer").append(`
  777. <div class="row form_field_pk_outer_row">
  778. <div class="form-group col-md-11">
  779. <textarea class="form-control" id="idPublicKey${index}" name="public_keys" rows="3"
  780. placeholder="Paste your public key here"></textarea>
  781. </div>
  782. <div class="form-group col-md-1">
  783. <button class="btn btn-circle btn-danger remove_pk_btn_frm_field">
  784. <i class="fas fa-trash"></i>
  785. </button>
  786. </div>
  787. </div>
  788. `);
  789. });
  790. $("body").on("click", ".remove_pk_btn_frm_field", function () {
  791. $(this).closest(".form_field_pk_outer_row").remove();
  792. });
  793. $("body").on("click", ".add_new_dirperms_field_btn", function () {
  794. var index = $(".form_field_dirperms_outer").find(".form_field_dirperms_outer_row").length;
  795. while (document.getElementById("idSubDirPermsPath"+index) != null){
  796. index++;
  797. }
  798. $(".form_field_dirperms_outer").append(`
  799. <div class="row form_field_dirperms_outer_row">
  800. <div class="form-group col-md-8">
  801. <input type="text" class="form-control" id="idSubDirPermsPath${index}" name="sub_perm_path${index}" placeholder="directory path, i.e. /dir" value="" maxlength="255">
  802. </div>
  803. <div class="form-group col-md-3">
  804. <select class="form-control" id="idSubDirPermissions${index}" name="sub_perm_permissions${index}" multiple>
  805. </select>
  806. </div>
  807. <div class="form-group col-md-1">
  808. <button class="btn btn-circle btn-danger remove_dirperms_btn_frm_field">
  809. <i class="fas fa-trash"></i>
  810. </button>
  811. </div>
  812. </div>
  813. `);
  814. {{range .ValidPerms}}
  815. $("#idSubDirPermissions"+index).append($('<option>').val('{{.}}').text('{{.}}'));
  816. {{end}}
  817. });
  818. $("body").on("click", ".remove_dirperms_btn_frm_field", function () {
  819. $(this).closest(".form_field_dirperms_outer_row").remove();
  820. });
  821. $("body").on("click", ".add_new_vfolder_field_btn", function () {
  822. var index = $(".form_field_vfolders_outer").find(".form_field_vfolder_outer_row").length;
  823. while (document.getElementById("idVolderPath"+index) != null){
  824. index++;
  825. }
  826. $(".form_field_vfolders_outer").append(`
  827. <div class="row form_field_vfolder_outer_row">
  828. <div class="form-group col-md-3">
  829. <input type="text" class="form-control" id="idVolderPath${index}" name="vfolder_path" placeholder="mount path, i.e. /vfolder" value="" maxlength="255">
  830. </div>
  831. <div class="form-group col-md-3">
  832. <select class="form-control" id="idVfolderName${index}" name="vfolder_name">
  833. <option value=""></option>
  834. </select>
  835. </div>
  836. <div class="form-group col-md-3">
  837. <input type="number" class="form-control" id="idVfolderQuotaSize${index}" name="vfolder_quota_size"
  838. value="" min="-1" aria-describedby="vqsHelpBlock${index}">
  839. <small id="vqsHelpBlock${index}" class="form-text text-muted">
  840. Quota size (bytes)
  841. </small>
  842. </div>
  843. <div class="form-group col-md-2">
  844. <input type="number" class="form-control" id="idVfolderQuotaFiles${index}" name="vfolder_quota_files"
  845. value="" min="-1" aria-describedby="vqfHelpBlock${index}">
  846. <small id="vqfHelpBlock${index}" class="form-text text-muted">
  847. Quota files
  848. </small>
  849. </div>
  850. <div class="form-group col-md-1">
  851. <button class="btn btn-circle btn-danger remove_vfolder_btn_frm_field">
  852. <i class="fas fa-trash"></i>
  853. </button>
  854. </div>
  855. </div>
  856. `);
  857. {{range .VirtualFolders}}
  858. $("#idVfolderName"+index).append($('<option>').val('{{.Name}}').text('{{.Name}}'));
  859. {{end}}
  860. });
  861. $("body").on("click", ".remove_vfolder_btn_frm_field", function () {
  862. $(this).closest(".form_field_vfolder_outer_row").remove();
  863. });
  864. $("body").on("click", ".add_new_bwlimit_field_btn", function () {
  865. var index = $(".form_field_bwlimits_outer").find(".form_field_bwlimits_outer_row").length;
  866. while (document.getElementById("idBandwidthLimitSources"+index) != null){
  867. index++;
  868. }
  869. $(".form_field_bwlimits_outer").append(`
  870. <div class="row form_field_bwlimits_outer_row">
  871. <div class="form-group col-md-8">
  872. <textarea class="form-control" id="idBandwidthLimitSources0" name="bandwidth_limit_sources${index}" rows="4" placeholder=""
  873. aria-describedby="bwLimitSourcesHelpBlock${index}"></textarea>
  874. <small id="bwLimitSourcesHelpBlock${index}" class="form-text text-muted">
  875. Comma separated IP/Mask in CIDR format, for example "192.168.1.0/24,10.8.0.100/32"
  876. </small>
  877. </div>
  878. <div class="col-md-3">
  879. <div class="form-group">
  880. <input type="number" class="form-control" id="idUploadBandwidthSource${index}" name="upload_bandwidth_source${index}"
  881. placeholder="" value="" min="0" aria-describedby="ulHelpBlock${index}">
  882. <small id="ulHelpBlock${index}" class="form-text text-muted">
  883. UL (KB/s). 0 means no limit
  884. </small>
  885. </div>
  886. <div class="form-group">
  887. <input type="number" class="form-control" id="idDownloadBandwidthSource${index}" name="download_bandwidth_source${index}"
  888. placeholder="" value="" min="0" aria-describedby="dlHelpBlock${index}">
  889. <small id="dlHelpBlock${index}" class="form-text text-muted">
  890. DL (KB/s). 0 means no limit
  891. </small>
  892. </div>
  893. </div>
  894. <div class="form-group col-md-1">
  895. <button class="btn btn-circle btn-danger remove_bwlimit_btn_frm_field">
  896. <i class="fas fa-trash"></i>
  897. </button>
  898. </div>
  899. </div>
  900. `);
  901. });
  902. $("body").on("click", ".remove_bwlimit_btn_frm_field", function () {
  903. $(this).closest(".form_field_bwlimits_outer_row").remove();
  904. });
  905. $("body").on("click", ".add_new_pattern_field_btn", function () {
  906. var index = $(".form_field_patterns_outer").find(".form_field_patterns_outer_row").length;
  907. while (document.getElementById("idPatternPath"+index) != null){
  908. index++;
  909. }
  910. $(".form_field_patterns_outer").append(`
  911. <div class="row form_field_patterns_outer_row">
  912. <div class="form-group col-md-3">
  913. <input type="text" class="form-control" id="idPatternPath${index}" name="pattern_path${index}" placeholder="directory path, i.e. /dir" value="" maxlength="255">
  914. </div>
  915. <div class="form-group col-md-4">
  916. <input type="text" class="form-control" id="idPatterns${index}" name="patterns${index}" placeholder="*.zip,?.txt" value="" maxlength="255">
  917. </div>
  918. <div class="form-group col-md-2">
  919. <select class="form-control" id="idPatternType${index}" name="pattern_type${index}">
  920. <option value="denied">Denied</option>
  921. <option value="allowed">Allowed</option>
  922. </select>
  923. </div>
  924. <div class="form-group col-md-2">
  925. <select class="form-control" id="idPatternPolicy${index}" name="pattern_policy${index}">
  926. <option value="0">Visible</option>
  927. <option value="1">Hidden</option>
  928. </select>
  929. </div>
  930. <div class="form-group col-md-1">
  931. <button class="btn btn-circle btn-danger remove_pattern_btn_frm_field">
  932. <i class="fas fa-trash"></i>
  933. </button>
  934. </div>
  935. </div>
  936. `);
  937. });
  938. $("body").on("click", ".remove_pattern_btn_frm_field", function () {
  939. $(this).closest(".form_field_patterns_outer_row").remove();
  940. });
  941. $("body").on("click", ".add_new_tpl_user_field_btn", function () {
  942. var index = $(".form_field_tpl_users_outer").find(".form_field_tpl_user_outer_row").length;
  943. while (document.getElementById("idTplUsername"+index) != null){
  944. index++;
  945. }
  946. $(".form_field_tpl_users_outer").append(`
  947. <div class="row form_field_tpl_user_outer_row">
  948. <div class="form-group col-md-3">
  949. <input type="text" class="form-control" id="idTplUsername${index}" name="tpl_username" placeholder="Username" maxlength="255">
  950. </div>
  951. <div class="form-group col-md-3">
  952. <input type="password" class="form-control" id="idTplPassword${index}" name="tpl_password" placeholder="Password" maxlength="255">
  953. </div>
  954. <div class="form-group col-md-5">
  955. <textarea class="form-control" id="idTplPublicKey${index}" name="tpl_public_keys" rows="5"
  956. placeholder="Paste your public key here"></textarea>
  957. </div>
  958. <div class="form-group col-md-1">
  959. <button class="btn btn-circle btn-danger remove_tpl_user_btn_frm_field">
  960. <i class="fas fa-trash"></i>
  961. </button>
  962. </div>
  963. </div>
  964. `);
  965. });
  966. $("body").on("click", ".remove_tpl_user_btn_frm_field", function () {
  967. $(this).closest(".form_field_tpl_user_outer_row").remove();
  968. });
  969. });
  970. {{template "fsjs"}}
  971. </script>
  972. {{end}}