maintenance_config_schema.templ 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. package app
  2. import (
  3. "fmt"
  4. "github.com/seaweedfs/seaweedfs/weed/admin/maintenance"
  5. "github.com/seaweedfs/seaweedfs/weed/admin/config"
  6. "github.com/seaweedfs/seaweedfs/weed/admin/view/components"
  7. )
  8. templ MaintenanceConfigSchema(data *maintenance.MaintenanceConfigData, schema *maintenance.MaintenanceConfigSchema) {
  9. <div class="container-fluid">
  10. <div class="row mb-4">
  11. <div class="col-12">
  12. <div class="d-flex justify-content-between align-items-center">
  13. <h2 class="mb-0">
  14. <i class="fas fa-cogs me-2"></i>
  15. Maintenance Configuration
  16. </h2>
  17. <div class="btn-group">
  18. <a href="/maintenance/tasks" class="btn btn-outline-primary">
  19. <i class="fas fa-tasks me-1"></i>
  20. View Tasks
  21. </a>
  22. </div>
  23. </div>
  24. </div>
  25. </div>
  26. <div class="row">
  27. <div class="col-12">
  28. <div class="card">
  29. <div class="card-header">
  30. <h5 class="mb-0">System Settings</h5>
  31. </div>
  32. <div class="card-body">
  33. <form id="maintenanceConfigForm">
  34. <!-- Dynamically render all schema fields in order -->
  35. for _, field := range schema.Fields {
  36. @ConfigField(field, data.Config)
  37. }
  38. <div class="d-flex gap-2">
  39. <button type="button" class="btn btn-primary" onclick="saveConfiguration()">
  40. <i class="fas fa-save me-1"></i>
  41. Save Configuration
  42. </button>
  43. <button type="button" class="btn btn-secondary" onclick="resetToDefaults()">
  44. <i class="fas fa-undo me-1"></i>
  45. Reset to Defaults
  46. </button>
  47. </div>
  48. </form>
  49. </div>
  50. </div>
  51. </div>
  52. </div>
  53. <!-- Task Configuration Cards -->
  54. <div class="row mt-4">
  55. <div class="col-md-4">
  56. <div class="card">
  57. <div class="card-header">
  58. <h5 class="mb-0">
  59. <i class="fas fa-broom me-2"></i>
  60. Volume Vacuum
  61. </h5>
  62. </div>
  63. <div class="card-body">
  64. <p class="card-text">Reclaims disk space by removing deleted files from volumes.</p>
  65. <a href="/maintenance/config/vacuum" class="btn btn-primary">Configure</a>
  66. </div>
  67. </div>
  68. </div>
  69. <div class="col-md-4">
  70. <div class="card">
  71. <div class="card-header">
  72. <h5 class="mb-0">
  73. <i class="fas fa-balance-scale me-2"></i>
  74. Volume Balance
  75. </h5>
  76. </div>
  77. <div class="card-body">
  78. <p class="card-text">Redistributes volumes across servers to optimize storage utilization.</p>
  79. <a href="/maintenance/config/balance" class="btn btn-primary">Configure</a>
  80. </div>
  81. </div>
  82. </div>
  83. <div class="col-md-4">
  84. <div class="card">
  85. <div class="card-header">
  86. <h5 class="mb-0">
  87. <i class="fas fa-shield-alt me-2"></i>
  88. Erasure Coding
  89. </h5>
  90. </div>
  91. <div class="card-body">
  92. <p class="card-text">Converts volumes to erasure coded format for improved durability.</p>
  93. <a href="/maintenance/config/erasure_coding" class="btn btn-primary">Configure</a>
  94. </div>
  95. </div>
  96. </div>
  97. </div>
  98. </div>
  99. <script>
  100. function saveConfiguration() {
  101. const form = document.getElementById('maintenanceConfigForm');
  102. const formData = new FormData(form);
  103. // Convert form data to JSON, handling interval fields specially
  104. const config = {};
  105. for (let [key, value] of formData.entries()) {
  106. if (key.endsWith('_value')) {
  107. // This is an interval value part
  108. const baseKey = key.replace('_value', '');
  109. const unitKey = baseKey + '_unit';
  110. const unitValue = formData.get(unitKey);
  111. if (unitValue) {
  112. // Convert to seconds based on unit
  113. const numValue = parseInt(value) || 0;
  114. let seconds = numValue;
  115. switch(unitValue) {
  116. case 'minutes':
  117. seconds = numValue * 60;
  118. break;
  119. case 'hours':
  120. seconds = numValue * 3600;
  121. break;
  122. case 'days':
  123. seconds = numValue * 24 * 3600;
  124. break;
  125. }
  126. config[baseKey] = seconds;
  127. }
  128. } else if (key.endsWith('_unit')) {
  129. // Skip unit keys - they're handled with their corresponding value
  130. continue;
  131. } else {
  132. // Regular field
  133. if (form.querySelector(`[name="${key}"]`).type === 'checkbox') {
  134. config[key] = form.querySelector(`[name="${key}"]`).checked;
  135. } else {
  136. const numValue = parseFloat(value);
  137. config[key] = isNaN(numValue) ? value : numValue;
  138. }
  139. }
  140. }
  141. fetch('/api/maintenance/config', {
  142. method: 'PUT',
  143. headers: {
  144. 'Content-Type': 'application/json',
  145. },
  146. body: JSON.stringify(config)
  147. })
  148. .then(response => {
  149. if (response.status === 401) {
  150. alert('Authentication required. Please log in first.');
  151. window.location.href = '/login';
  152. return;
  153. }
  154. return response.json();
  155. })
  156. .then(data => {
  157. if (!data) return; // Skip if redirected to login
  158. if (data.success) {
  159. alert('Configuration saved successfully!');
  160. location.reload();
  161. } else {
  162. alert('Error saving configuration: ' + (data.error || 'Unknown error'));
  163. }
  164. })
  165. .catch(error => {
  166. console.error('Error:', error);
  167. alert('Error saving configuration: ' + error.message);
  168. });
  169. }
  170. function resetToDefaults() {
  171. if (confirm('Are you sure you want to reset to default configuration? This will overwrite your current settings.')) {
  172. fetch('/maintenance/config/defaults', {
  173. method: 'POST',
  174. headers: {
  175. 'Content-Type': 'application/json',
  176. }
  177. })
  178. .then(response => response.json())
  179. .then(data => {
  180. if (data.success) {
  181. alert('Configuration reset to defaults!');
  182. location.reload();
  183. } else {
  184. alert('Error resetting configuration: ' + (data.error || 'Unknown error'));
  185. }
  186. })
  187. .catch(error => {
  188. console.error('Error:', error);
  189. alert('Error resetting configuration: ' + error.message);
  190. });
  191. }
  192. }
  193. </script>
  194. }
  195. // ConfigField renders a single configuration field based on schema with typed value lookup
  196. templ ConfigField(field *config.Field, config *maintenance.MaintenanceConfig) {
  197. if field.InputType == "interval" {
  198. <!-- Interval field with number input + unit dropdown -->
  199. <div class="mb-3">
  200. <label for={ field.JSONName } class="form-label">
  201. { field.DisplayName }
  202. if field.Required {
  203. <span class="text-danger">*</span>
  204. }
  205. </label>
  206. <div class="input-group">
  207. <input
  208. type="number"
  209. class="form-control"
  210. id={ field.JSONName + "_value" }
  211. name={ field.JSONName + "_value" }
  212. value={ fmt.Sprintf("%.0f", components.ConvertInt32SecondsToDisplayValue(getMaintenanceInt32Field(config, field.JSONName))) }
  213. step="1"
  214. min="1"
  215. if field.Required {
  216. required
  217. }
  218. />
  219. <select
  220. class="form-select"
  221. id={ field.JSONName + "_unit" }
  222. name={ field.JSONName + "_unit" }
  223. style="max-width: 120px;"
  224. if field.Required {
  225. required
  226. }
  227. >
  228. <option
  229. value="minutes"
  230. if components.GetInt32DisplayUnit(getMaintenanceInt32Field(config, field.JSONName)) == "minutes" {
  231. selected
  232. }
  233. >
  234. Minutes
  235. </option>
  236. <option
  237. value="hours"
  238. if components.GetInt32DisplayUnit(getMaintenanceInt32Field(config, field.JSONName)) == "hours" {
  239. selected
  240. }
  241. >
  242. Hours
  243. </option>
  244. <option
  245. value="days"
  246. if components.GetInt32DisplayUnit(getMaintenanceInt32Field(config, field.JSONName)) == "days" {
  247. selected
  248. }
  249. >
  250. Days
  251. </option>
  252. </select>
  253. </div>
  254. if field.Description != "" {
  255. <div class="form-text text-muted">{ field.Description }</div>
  256. }
  257. </div>
  258. } else if field.InputType == "checkbox" {
  259. <!-- Checkbox field -->
  260. <div class="mb-3">
  261. <div class="form-check form-switch">
  262. <input
  263. class="form-check-input"
  264. type="checkbox"
  265. id={ field.JSONName }
  266. name={ field.JSONName }
  267. if getMaintenanceBoolField(config, field.JSONName) {
  268. checked
  269. }
  270. />
  271. <label class="form-check-label" for={ field.JSONName }>
  272. <strong>{ field.DisplayName }</strong>
  273. </label>
  274. </div>
  275. if field.Description != "" {
  276. <div class="form-text text-muted">{ field.Description }</div>
  277. }
  278. </div>
  279. } else {
  280. <!-- Number field -->
  281. <div class="mb-3">
  282. <label for={ field.JSONName } class="form-label">
  283. { field.DisplayName }
  284. if field.Required {
  285. <span class="text-danger">*</span>
  286. }
  287. </label>
  288. <input
  289. type="number"
  290. class="form-control"
  291. id={ field.JSONName }
  292. name={ field.JSONName }
  293. value={ fmt.Sprintf("%d", getMaintenanceInt32Field(config, field.JSONName)) }
  294. placeholder={ field.Placeholder }
  295. if field.MinValue != nil {
  296. min={ fmt.Sprintf("%v", field.MinValue) }
  297. }
  298. if field.MaxValue != nil {
  299. max={ fmt.Sprintf("%v", field.MaxValue) }
  300. }
  301. step={ getNumberStep(field) }
  302. if field.Required {
  303. required
  304. }
  305. />
  306. if field.Description != "" {
  307. <div class="form-text text-muted">{ field.Description }</div>
  308. }
  309. </div>
  310. }
  311. }
  312. // Helper functions for form field types
  313. func getNumberStep(field *config.Field) string {
  314. if field.Type == config.FieldTypeFloat {
  315. return "0.01"
  316. }
  317. return "1"
  318. }
  319. // Typed field getters for MaintenanceConfig - no interface{} needed
  320. func getMaintenanceInt32Field(config *maintenance.MaintenanceConfig, fieldName string) int32 {
  321. if config == nil {
  322. return 0
  323. }
  324. switch fieldName {
  325. case "scan_interval_seconds":
  326. return config.ScanIntervalSeconds
  327. case "worker_timeout_seconds":
  328. return config.WorkerTimeoutSeconds
  329. case "task_timeout_seconds":
  330. return config.TaskTimeoutSeconds
  331. case "retry_delay_seconds":
  332. return config.RetryDelaySeconds
  333. case "max_retries":
  334. return config.MaxRetries
  335. case "cleanup_interval_seconds":
  336. return config.CleanupIntervalSeconds
  337. case "task_retention_seconds":
  338. return config.TaskRetentionSeconds
  339. case "global_max_concurrent":
  340. if config.Policy != nil {
  341. return config.Policy.GlobalMaxConcurrent
  342. }
  343. return 0
  344. default:
  345. return 0
  346. }
  347. }
  348. func getMaintenanceBoolField(config *maintenance.MaintenanceConfig, fieldName string) bool {
  349. if config == nil {
  350. return false
  351. }
  352. switch fieldName {
  353. case "enabled":
  354. return config.Enabled
  355. default:
  356. return false
  357. }
  358. }
  359. // Helper function to convert schema to JSON for JavaScript
  360. templ schemaToJSON(schema *maintenance.MaintenanceConfigSchema) {
  361. {`{}`}
  362. }