Browse Source

Fix Chrome dialog auto-dismiss with Bootstrap modals

- Add modal-alerts.js library with Bootstrap modal replacements
- Replace all 15 confirm() calls with showConfirm/showDeleteConfirm
- Auto-override window.alert() for all alert() calls
- Fixes Chrome 132+ aggressively blocking native dialogs
pull/8128/head
Chris Lu 4 days ago
parent
commit
74c7b10bc7
  1. 302
      weed/admin/static/js/modal-alerts.js
  2. 4
      weed/admin/view/app/cluster_ec_shards.templ
  3. 2
      weed/admin/view/app/cluster_ec_shards_templ.go
  4. 4
      weed/admin/view/app/cluster_ec_volumes.templ
  5. 2
      weed/admin/view/app/cluster_ec_volumes_templ.go
  6. 4
      weed/admin/view/app/collection_details.templ
  7. 2
      weed/admin/view/app/collection_details_templ.go
  8. 4
      weed/admin/view/app/file_browser.templ
  9. 2
      weed/admin/view/app/file_browser_templ.go
  10. 4
      weed/admin/view/app/maintenance_config.templ
  11. 4
      weed/admin/view/app/maintenance_config_schema.templ
  12. 2
      weed/admin/view/app/maintenance_config_schema_templ.go
  13. 2
      weed/admin/view/app/maintenance_config_templ.go
  14. 4
      weed/admin/view/app/maintenance_workers.templ
  15. 2
      weed/admin/view/app/maintenance_workers_templ.go
  16. 10
      weed/admin/view/app/object_store_users.templ
  17. 2
      weed/admin/view/app/object_store_users_templ.go
  18. 6
      weed/admin/view/app/policies.templ
  19. 2
      weed/admin/view/app/policies_templ.go
  20. 4
      weed/admin/view/app/service_accounts.templ
  21. 2
      weed/admin/view/app/service_accounts_templ.go
  22. 4
      weed/admin/view/app/task_config.templ
  23. 4
      weed/admin/view/app/task_config_schema.templ
  24. 2
      weed/admin/view/app/task_config_schema_templ.go
  25. 2
      weed/admin/view/app/task_config_templ.go
  26. 4
      weed/admin/view/app/task_config_templ.templ
  27. 2
      weed/admin/view/app/task_config_templ_templ.go
  28. 4
      weed/admin/view/app/task_detail.templ
  29. 2
      weed/admin/view/app/task_detail_templ.go
  30. 2
      weed/admin/view/layout/layout.templ
  31. 8
      weed/admin/view/layout/layout_templ.go

302
weed/admin/static/js/modal-alerts.js

@ -0,0 +1,302 @@
/**
* Modal Alerts - Bootstrap Modal replacement for native alert() and confirm()
* Fixes Chrome auto-dismiss issue with native dialogs
*
* Usage:
* showAlert('Message', 'success');
* showConfirm('Delete this?', function() { });
*/
(function() {
'use strict';
// Create and inject modal HTML into page if not already present
function ensureModalsExist() {
if (document.getElementById('globalAlertModal')) {
return; // Already exists
}
const modalsHTML = `
<!-- Global Alert Modal -->
<div class="modal fade" id="globalAlertModal" tabindex="-1" aria-labelledby="globalAlertModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header" id="globalAlertModalHeader">
<h5 class="modal-title" id="globalAlertModalLabel">
<i class="fas fa-info-circle me-2" id="globalAlertModalIcon"></i>
<span id="globalAlertModalTitle">Notice</span>
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="globalAlertModalBody">
<!-- Message will be inserted here -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">OK</button>
</div>
</div>
</div>
</div>
<!-- Global Confirm Modal -->
<div class="modal fade" id="globalConfirmModal" tabindex="-1" aria-labelledby="globalConfirmModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-warning">
<h5 class="modal-title" id="globalConfirmModalLabel">
<i class="fas fa-question-circle me-2"></i>Confirm Action
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="globalConfirmModalBody">
<!-- Message will be inserted here -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" id="globalConfirmCancelBtn">Cancel</button>
<button type="button" class="btn btn-primary" id="globalConfirmOkBtn">OK</button>
</div>
</div>
</div>
</div>
<!-- Global Delete Confirm Modal -->
<div class="modal fade" id="globalDeleteModal" tabindex="-1" aria-labelledby="globalDeleteModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-danger text-white">
<h5 class="modal-title" id="globalDeleteModalLabel">
<i class="fas fa-exclamation-triangle me-2"></i>Confirm Delete
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p class="mb-2" id="globalDeleteModalMessage">Are you sure you want to delete this item?</p>
<p class="mb-0"><strong id="globalDeleteModalItemName"></strong></p>
<p class="text-muted small mt-2 mb-0">This action cannot be undone.</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger" id="globalDeleteConfirmBtn">
<i class="fas fa-trash me-1"></i>Delete
</button>
</div>
</div>
</div>
</div>
`;
// Inject modals at end of body
document.body.insertAdjacentHTML('beforeend', modalsHTML);
}
/**
* Show an alert message using Bootstrap modal
* @param {string} message - The message to display
* @param {string} type - Type: 'success', 'error', 'warning', 'info' (default: 'info')
* @param {string} title - Optional custom title
*/
window.showAlert = function(message, type, title) {
ensureModalsExist();
const modal = document.getElementById('globalAlertModal');
const header = document.getElementById('globalAlertModalHeader');
const titleEl = document.getElementById('globalAlertModalTitle');
const bodyEl = document.getElementById('globalAlertModalBody');
const iconEl = document.getElementById('globalAlertModalIcon');
// Configuration for different types
const types = {
'success': {
title: 'Success',
icon: 'fa-check-circle',
headerClass: 'bg-success text-white',
btnClose: 'btn-close-white'
},
'error': {
title: 'Error',
icon: 'fa-exclamation-triangle',
headerClass: 'bg-danger text-white',
btnClose: 'btn-close-white'
},
'warning': {
title: 'Warning',
icon: 'fa-exclamation-circle',
headerClass: 'bg-warning text-dark',
btnClose: ''
},
'info': {
title: 'Notice',
icon: 'fa-info-circle',
headerClass: 'bg-info text-white',
btnClose: 'btn-close-white'
}
};
const config = types[type] || types['info'];
// Update header styling
header.className = 'modal-header ' + config.headerClass;
const closeBtn = header.querySelector('.btn-close');
closeBtn.className = 'btn-close ' + config.btnClose;
// Update icon
iconEl.className = 'fas ' + config.icon + ' me-2';
// Update title
titleEl.textContent = title || config.title;
// Update body - support HTML or text
if (message.includes('<')) {
bodyEl.innerHTML = message;
} else {
bodyEl.innerHTML = '<p class="mb-0">' + escapeHtml(message) + '</p>';
}
// Show modal
const bsModal = new bootstrap.Modal(modal);
bsModal.show();
};
/**
* Show a confirmation dialog using Bootstrap modal
* @param {string} message - The confirmation message
* @param {function} onConfirm - Callback function if user confirms
* @param {function} onCancel - Optional callback function if user cancels
* @param {string} title - Optional custom title
*/
window.showConfirm = function(message, onConfirm, onCancel, title) {
ensureModalsExist();
const modalEl = document.getElementById('globalConfirmModal');
const bodyEl = document.getElementById('globalConfirmModalBody');
const titleEl = document.getElementById('globalConfirmModalLabel').querySelector('span');
const okBtn = document.getElementById('globalConfirmOkBtn');
const cancelBtn = document.getElementById('globalConfirmCancelBtn');
// Set title if provided
if (title) {
if (titleEl) {
titleEl.textContent = title;
} else {
document.getElementById('globalConfirmModalLabel').insertAdjacentHTML('beforeend', '<span>' + escapeHtml(title) + '</span>');
}
}
// Set message
if (message.includes('<')) {
bodyEl.innerHTML = message;
} else {
bodyEl.innerHTML = '<p class="mb-0">' + escapeHtml(message) + '</p>';
}
// Remove old event listeners by cloning buttons
const newOkBtn = okBtn.cloneNode(true);
const newCancelBtn = cancelBtn.cloneNode(true);
okBtn.parentNode.replaceChild(newOkBtn, okBtn);
cancelBtn.parentNode.replaceChild(newCancelBtn, cancelBtn);
const modal = new bootstrap.Modal(modalEl);
// Add event listeners
newOkBtn.addEventListener('click', function() {
modal.hide();
if (typeof onConfirm === 'function') {
onConfirm();
}
});
newCancelBtn.addEventListener('click', function() {
modal.hide();
if (typeof onCancel === 'function') {
onCancel();
}
});
modal.show();
};
/**
* Show a delete confirmation dialog
* @param {string} itemName - Name of the item to delete
* @param {function} onConfirm - Callback function if user confirms deletion
* @param {string} message - Optional custom message (default: "Are you sure you want to delete this item?")
*/
window.showDeleteConfirm = function(itemName, onConfirm, message) {
ensureModalsExist();
const modalEl = document.getElementById('globalDeleteModal');
const messageEl = document.getElementById('globalDeleteModalMessage');
const itemNameEl = document.getElementById('globalDeleteModalItemName');
const confirmBtn = document.getElementById('globalDeleteConfirmBtn');
// Set custom message if provided
if (message) {
messageEl.textContent = message;
} else {
messageEl.textContent = 'Are you sure you want to delete this item?';
}
// Set item name
itemNameEl.textContent = itemName;
// Remove old event listener by cloning button
const newConfirmBtn = confirmBtn.cloneNode(true);
confirmBtn.parentNode.replaceChild(newConfirmBtn, confirmBtn);
const modal = new bootstrap.Modal(modalEl);
// Add new event listener
newConfirmBtn.addEventListener('click', function() {
modal.hide();
if (typeof onConfirm === 'function') {
onConfirm();
}
});
modal.show();
};
/**
* Escape HTML to prevent XSS
*/
function escapeHtml(text) {
const map = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
};
return text.replace(/[&<>"']/g, function(m) { return map[m]; });
}
// Auto-initialize on DOMContentLoaded
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', ensureModalsExist);
} else {
ensureModalsExist();
}
/**
* AUTOMATIC OVERRIDE of native alert()
* This makes ALL existing alert() calls automatically use Bootstrap modals
*/
window.alert = function(message) {
// Auto-detect message type from content
let type = 'info';
const msgLower = (message || '').toLowerCase();
if (msgLower.includes('success') || msgLower.includes('created') || msgLower.includes('updated') || msgLower.includes('saved')) {
type = 'success';
} else if (msgLower.includes('error') || msgLower.includes('failed') || msgLower.includes('invalid') || msgLower.includes('cannot')) {
type = 'error';
} else if (msgLower.includes('warning') || msgLower.includes('please') || msgLower.includes('required')) {
type = 'warning';
}
showAlert(message, type);
};
console.log('Modal Alerts library loaded - native alert() overridden');
console.log('For confirm(), use showConfirm() or showDeleteConfirm() instead of native confirm()');
})();

4
weed/admin/view/app/cluster_ec_shards.templ

@ -387,7 +387,7 @@ templ ClusterEcShards(data dash.ClusterEcShardsData) {
// Get data from the button element (not the icon inside it) // Get data from the button element (not the icon inside it)
const button = event.target.closest('button'); const button = event.target.closest('button');
const volumeId = button.getAttribute('data-volume-id'); const volumeId = button.getAttribute('data-volume-id');
if (confirm(`Are you sure you want to repair missing shards for volume ${volumeId}?`)) {
showConfirm(`Are you sure you want to repair missing shards for volume ${volumeId}?`, function() {
fetch(`/api/storage/volumes/${volumeId}/repair`, { fetch(`/api/storage/volumes/${volumeId}/repair`, {
method: 'POST', method: 'POST',
headers: { headers: {
@ -406,7 +406,7 @@ templ ClusterEcShards(data dash.ClusterEcShardsData) {
.catch(error => { .catch(error => {
alert('Error: ' + error.message); alert('Error: ' + error.message);
}); });
}
});
} }
</script> </script>
} }

2
weed/admin/view/app/cluster_ec_shards_templ.go

@ -663,7 +663,7 @@ func ClusterEcShards(data dash.ClusterEcShardsData) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "<!-- JavaScript --><script>\n function sortBy(field) {\n const currentSort = \"{data.SortBy}\";\n const currentOrder = \"{data.SortOrder}\";\n let newOrder = 'asc';\n \n if (currentSort === field && currentOrder === 'asc') {\n newOrder = 'desc';\n }\n \n updateUrl({\n sortBy: field,\n sortOrder: newOrder,\n page: 1\n });\n }\n\n function goToPage(event) {\n // Get data from the link element (not any child elements)\n const link = event.target.closest('a');\n const page = link.getAttribute('data-page');\n updateUrl({ page: page });\n }\n\n function changePageSize() {\n const pageSize = document.getElementById('pageSizeSelect').value;\n updateUrl({ pageSize: pageSize, page: 1 });\n }\n\n function updateUrl(params) {\n const url = new URL(window.location);\n Object.keys(params).forEach(key => {\n if (params[key]) {\n url.searchParams.set(key, params[key]);\n } else {\n url.searchParams.delete(key);\n }\n });\n window.location.href = url.toString();\n }\n\n function exportEcShards() {\n const url = new URL('/api/storage/ec-shards/export', window.location.origin);\n const params = new URLSearchParams(window.location.search);\n params.forEach((value, key) => {\n url.searchParams.set(key, value);\n });\n window.open(url.toString(), '_blank');\n }\n\n function showShardDetails(event) {\n // Get data from the button element (not the icon inside it)\n const button = event.target.closest('button');\n const volumeId = button.getAttribute('data-volume-id');\n \n // Navigate to the EC volume details page\n window.location.href = `/storage/ec-volumes/${volumeId}`;\n }\n\n function repairVolume(event) {\n // Get data from the button element (not the icon inside it)\n const button = event.target.closest('button');\n const volumeId = button.getAttribute('data-volume-id');\n if (confirm(`Are you sure you want to repair missing shards for volume ${volumeId}?`)) {\n fetch(`/api/storage/volumes/${volumeId}/repair`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n }\n })\n .then(response => response.json())\n .then(data => {\n if (data.success) {\n alert('Repair initiated successfully');\n location.reload();\n } else {\n alert('Failed to initiate repair: ' + data.error);\n }\n })\n .catch(error => {\n alert('Error: ' + error.message);\n });\n }\n }\n </script>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "<!-- JavaScript --><script>\n function sortBy(field) {\n const currentSort = \"{data.SortBy}\";\n const currentOrder = \"{data.SortOrder}\";\n let newOrder = 'asc';\n \n if (currentSort === field && currentOrder === 'asc') {\n newOrder = 'desc';\n }\n \n updateUrl({\n sortBy: field,\n sortOrder: newOrder,\n page: 1\n });\n }\n\n function goToPage(event) {\n // Get data from the link element (not any child elements)\n const link = event.target.closest('a');\n const page = link.getAttribute('data-page');\n updateUrl({ page: page });\n }\n\n function changePageSize() {\n const pageSize = document.getElementById('pageSizeSelect').value;\n updateUrl({ pageSize: pageSize, page: 1 });\n }\n\n function updateUrl(params) {\n const url = new URL(window.location);\n Object.keys(params).forEach(key => {\n if (params[key]) {\n url.searchParams.set(key, params[key]);\n } else {\n url.searchParams.delete(key);\n }\n });\n window.location.href = url.toString();\n }\n\n function exportEcShards() {\n const url = new URL('/api/storage/ec-shards/export', window.location.origin);\n const params = new URLSearchParams(window.location.search);\n params.forEach((value, key) => {\n url.searchParams.set(key, value);\n });\n window.open(url.toString(), '_blank');\n }\n\n function showShardDetails(event) {\n // Get data from the button element (not the icon inside it)\n const button = event.target.closest('button');\n const volumeId = button.getAttribute('data-volume-id');\n \n // Navigate to the EC volume details page\n window.location.href = `/storage/ec-volumes/${volumeId}`;\n }\n\n function repairVolume(event) {\n // Get data from the button element (not the icon inside it)\n const button = event.target.closest('button');\n const volumeId = button.getAttribute('data-volume-id');\n showConfirm(`Are you sure you want to repair missing shards for volume ${volumeId}?`, function() {\n fetch(`/api/storage/volumes/${volumeId}/repair`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n }\n })\n .then(response => response.json())\n .then(data => {\n if (data.success) {\n alert('Repair initiated successfully');\n location.reload();\n } else {\n alert('Failed to initiate repair: ' + data.error);\n }\n })\n .catch(error => {\n alert('Error: ' + error.message);\n });\n });\n }\n </script>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }

4
weed/admin/view/app/cluster_ec_volumes.templ

@ -378,7 +378,7 @@ templ ClusterEcVolumes(data dash.ClusterEcVolumesData) {
function repairVolume(event) { function repairVolume(event) {
const volumeId = event.target.closest('button').getAttribute('data-volume-id'); const volumeId = event.target.closest('button').getAttribute('data-volume-id');
if (confirm(`Are you sure you want to repair missing shards for volume ${volumeId}?`)) {
showConfirm(`Are you sure you want to repair missing shards for volume ${volumeId}?`, function() {
fetch(`/api/storage/ec-volumes/${volumeId}/repair`, { fetch(`/api/storage/ec-volumes/${volumeId}/repair`, {
method: 'POST', method: 'POST',
headers: { headers: {
@ -402,7 +402,7 @@ templ ClusterEcVolumes(data dash.ClusterEcVolumesData) {
.catch(error => { .catch(error => {
alert('Error: ' + error.message); alert('Error: ' + error.message);
}); });
}
});
} }
</script> </script>
} }

2
weed/admin/view/app/cluster_ec_volumes_templ.go

@ -757,7 +757,7 @@ func ClusterEcVolumes(data dash.ClusterEcVolumesData) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "<!-- JavaScript --><script>\n function sortBy(field) {\n const currentSort = new URLSearchParams(window.location.search).get('sort_by');\n const currentOrder = new URLSearchParams(window.location.search).get('sort_order') || 'asc';\n \n let newOrder = 'asc';\n if (currentSort === field && currentOrder === 'asc') {\n newOrder = 'desc';\n }\n \n updateUrl({\n sort_by: field,\n sort_order: newOrder,\n page: 1\n });\n }\n\n function goToPage(event) {\n event.preventDefault();\n const page = event.target.closest('a').getAttribute('data-page');\n updateUrl({ page: page });\n }\n\n function changePageSize(newPageSize) {\n updateUrl({ page_size: newPageSize, page: 1 });\n }\n\n function updateUrl(params) {\n const url = new URL(window.location);\n Object.keys(params).forEach(key => {\n if (params[key] != null) {\n url.searchParams.set(key, params[key]);\n } else {\n url.searchParams.delete(key);\n }\n });\n window.location.href = url.toString();\n }\n\n function showVolumeDetails(event) {\n const volumeId = event.target.closest('button').getAttribute('data-volume-id');\n window.location.href = `/storage/ec-volumes/${volumeId}`;\n }\n\n function repairVolume(event) {\n const volumeId = event.target.closest('button').getAttribute('data-volume-id');\n if (confirm(`Are you sure you want to repair missing shards for volume ${volumeId}?`)) {\n fetch(`/api/storage/ec-volumes/${volumeId}/repair`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n }\n })\n .then(response => {\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n return response.json();\n })\n .then(data => {\n if (data && data.success) {\n alert('Repair initiated successfully');\n location.reload();\n } else {\n alert('Failed to initiate repair: ' + (data && data.error ? data.error : 'Unknown error'));\n }\n })\n .catch(error => {\n alert('Error: ' + error.message);\n });\n }\n }\n </script>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "<!-- JavaScript --><script>\n function sortBy(field) {\n const currentSort = new URLSearchParams(window.location.search).get('sort_by');\n const currentOrder = new URLSearchParams(window.location.search).get('sort_order') || 'asc';\n \n let newOrder = 'asc';\n if (currentSort === field && currentOrder === 'asc') {\n newOrder = 'desc';\n }\n \n updateUrl({\n sort_by: field,\n sort_order: newOrder,\n page: 1\n });\n }\n\n function goToPage(event) {\n event.preventDefault();\n const page = event.target.closest('a').getAttribute('data-page');\n updateUrl({ page: page });\n }\n\n function changePageSize(newPageSize) {\n updateUrl({ page_size: newPageSize, page: 1 });\n }\n\n function updateUrl(params) {\n const url = new URL(window.location);\n Object.keys(params).forEach(key => {\n if (params[key] != null) {\n url.searchParams.set(key, params[key]);\n } else {\n url.searchParams.delete(key);\n }\n });\n window.location.href = url.toString();\n }\n\n function showVolumeDetails(event) {\n const volumeId = event.target.closest('button').getAttribute('data-volume-id');\n window.location.href = `/storage/ec-volumes/${volumeId}`;\n }\n\n function repairVolume(event) {\n const volumeId = event.target.closest('button').getAttribute('data-volume-id');\n showConfirm(`Are you sure you want to repair missing shards for volume ${volumeId}?`, function() {\n fetch(`/api/storage/ec-volumes/${volumeId}/repair`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n }\n })\n .then(response => {\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n return response.json();\n })\n .then(data => {\n if (data && data.success) {\n alert('Repair initiated successfully');\n location.reload();\n } else {\n alert('Failed to initiate repair: ' + (data && data.error ? data.error : 'Unknown error'));\n }\n })\n .catch(error => {\n alert('Error: ' + error.message);\n });\n });\n }\n </script>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }

4
weed/admin/view/app/collection_details.templ

@ -372,10 +372,10 @@ templ CollectionDetails(data dash.CollectionDetailsData) {
// Repair EC Volume // Repair EC Volume
function repairEcVolume(event) { function repairEcVolume(event) {
const volumeId = event.target.closest('button').getAttribute('data-volume-id'); const volumeId = event.target.closest('button').getAttribute('data-volume-id');
if (confirm(`Are you sure you want to repair missing shards for EC volume ${volumeId}?`)) {
showConfirm(`Are you sure you want to repair missing shards for EC volume ${volumeId}?`, function() {
// TODO: Implement repair functionality // TODO: Implement repair functionality
alert('Repair functionality will be implemented soon.'); alert('Repair functionality will be implemented soon.');
}
});
} }
</script> </script>
} }

2
weed/admin/view/app/collection_details_templ.go

@ -575,7 +575,7 @@ func CollectionDetails(data dash.CollectionDetailsData) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "<script>\n\t\t// Sorting functionality\n\t\tfunction sortBy(field) {\n\t\t\tconst currentSort = new URLSearchParams(window.location.search).get('sort_by');\n\t\t\tconst currentOrder = new URLSearchParams(window.location.search).get('sort_order') || 'asc';\n\t\t\t\n\t\t\tlet newOrder = 'asc';\n\t\t\tif (currentSort === field && currentOrder === 'asc') {\n\t\t\t\tnewOrder = 'desc';\n\t\t\t}\n\t\t\t\n\t\t\tconst url = new URL(window.location);\n\t\t\turl.searchParams.set('sort_by', field);\n\t\t\turl.searchParams.set('sort_order', newOrder);\n\t\t\turl.searchParams.set('page', '1'); // Reset to first page\n\t\t\twindow.location.href = url.toString();\n\t\t}\n\n\t\t// Pagination functionality\n\t\tfunction goToPage(event) {\n\t\t\tevent.preventDefault();\n\t\t\tconst page = event.target.closest('a').getAttribute('data-page');\n\t\t\tconst url = new URL(window.location);\n\t\t\turl.searchParams.set('page', page);\n\t\t\twindow.location.href = url.toString();\n\t\t}\n\n\t\t// Page size functionality\n\t\tfunction changePageSize(newPageSize) {\n\t\t\tconst url = new URL(window.location);\n\t\t\turl.searchParams.set('page_size', newPageSize);\n\t\t\turl.searchParams.set('page', '1'); // Reset to first page when changing page size\n\t\t\twindow.location.href = url.toString();\n\t\t}\n\n\t\t// Volume details\n\t\tfunction showVolumeDetails(event) {\n\t\t\tconst volumeId = event.target.closest('button').getAttribute('data-volume-id');\n\t\t\tconst server = event.target.closest('button').getAttribute('data-server');\n\t\t\twindow.location.href = `/storage/volumes/${volumeId}/${server}`;\n\t\t}\n\n\t\t// EC Volume details\n\t\tfunction showEcVolumeDetails(event) {\n\t\t\tconst volumeId = event.target.closest('button').getAttribute('data-volume-id');\n\t\t\twindow.location.href = `/storage/ec-volumes/${volumeId}`;\n\t\t}\n\n\t\t// Repair EC Volume\n\t\tfunction repairEcVolume(event) {\n\t\t\tconst volumeId = event.target.closest('button').getAttribute('data-volume-id');\n\t\t\tif (confirm(`Are you sure you want to repair missing shards for EC volume ${volumeId}?`)) {\n\t\t\t\t// TODO: Implement repair functionality\n\t\t\t\talert('Repair functionality will be implemented soon.');\n\t\t\t}\n\t\t}\n\t</script>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "<script>\n\t\t// Sorting functionality\n\t\tfunction sortBy(field) {\n\t\t\tconst currentSort = new URLSearchParams(window.location.search).get('sort_by');\n\t\t\tconst currentOrder = new URLSearchParams(window.location.search).get('sort_order') || 'asc';\n\t\t\t\n\t\t\tlet newOrder = 'asc';\n\t\t\tif (currentSort === field && currentOrder === 'asc') {\n\t\t\t\tnewOrder = 'desc';\n\t\t\t}\n\t\t\t\n\t\t\tconst url = new URL(window.location);\n\t\t\turl.searchParams.set('sort_by', field);\n\t\t\turl.searchParams.set('sort_order', newOrder);\n\t\t\turl.searchParams.set('page', '1'); // Reset to first page\n\t\t\twindow.location.href = url.toString();\n\t\t}\n\n\t\t// Pagination functionality\n\t\tfunction goToPage(event) {\n\t\t\tevent.preventDefault();\n\t\t\tconst page = event.target.closest('a').getAttribute('data-page');\n\t\t\tconst url = new URL(window.location);\n\t\t\turl.searchParams.set('page', page);\n\t\t\twindow.location.href = url.toString();\n\t\t}\n\n\t\t// Page size functionality\n\t\tfunction changePageSize(newPageSize) {\n\t\t\tconst url = new URL(window.location);\n\t\t\turl.searchParams.set('page_size', newPageSize);\n\t\t\turl.searchParams.set('page', '1'); // Reset to first page when changing page size\n\t\t\twindow.location.href = url.toString();\n\t\t}\n\n\t\t// Volume details\n\t\tfunction showVolumeDetails(event) {\n\t\t\tconst volumeId = event.target.closest('button').getAttribute('data-volume-id');\n\t\t\tconst server = event.target.closest('button').getAttribute('data-server');\n\t\t\twindow.location.href = `/storage/volumes/${volumeId}/${server}`;\n\t\t}\n\n\t\t// EC Volume details\n\t\tfunction showEcVolumeDetails(event) {\n\t\t\tconst volumeId = event.target.closest('button').getAttribute('data-volume-id');\n\t\t\twindow.location.href = `/storage/ec-volumes/${volumeId}`;\n\t\t}\n\n\t\t// Repair EC Volume\n\t\tfunction repairEcVolume(event) {\n\t\t\tconst volumeId = event.target.closest('button').getAttribute('data-volume-id');\n showConfirm(`Are you sure you want to repair missing shards for EC volume ${volumeId}?`, function() {\n\t\t\t\t// TODO: Implement repair functionality\n\t\t\t\talert('Repair functionality will be implemented soon.');\n\t\t\t});\n\t\t}\n\t</script>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }

4
weed/admin/view/app/file_browser.templ

@ -365,9 +365,9 @@ templ FileBrowser(data dash.FileBrowserData) {
showFileProperties(path); showFileProperties(path);
break; break;
case 'delete': case 'delete':
if (confirm('Are you sure you want to delete "' + path + '"?')) {
showDeleteConfirm(path, function() {
deleteFile(path); deleteFile(path);
}
}, 'Are you sure you want to delete this file?');
break; break;
} }
}); });

2
weed/admin/view/app/file_browser_templ.go
File diff suppressed because it is too large
View File

4
weed/admin/view/app/maintenance_config.templ

@ -251,7 +251,7 @@ templ MaintenanceConfig(data *maintenance.MaintenanceConfigData) {
} }
function resetToDefaults() { function resetToDefaults() {
if (confirm('Are you sure you want to reset to default configuration? This will overwrite your current settings.')) {
showConfirm('Are you sure you want to reset to default configuration? This will overwrite your current settings.', function() {
// Reset form to defaults (matching DefaultMaintenanceConfig values) // Reset form to defaults (matching DefaultMaintenanceConfig values)
document.getElementById('enabled').checked = false; document.getElementById('enabled').checked = false;
document.getElementById('scanInterval').value = '30'; document.getElementById('scanInterval').value = '30';
@ -261,7 +261,7 @@ templ MaintenanceConfig(data *maintenance.MaintenanceConfigData) {
document.getElementById('maxRetries').value = '3'; document.getElementById('maxRetries').value = '3';
document.getElementById('retryDelay').value = '15'; document.getElementById('retryDelay').value = '15';
document.getElementById('taskRetention').value = '7'; document.getElementById('taskRetention').value = '7';
}
});
} }
</script> </script>
} }

4
weed/admin/view/app/maintenance_config_schema.templ

@ -179,7 +179,7 @@ templ MaintenanceConfigSchema(data *maintenance.MaintenanceConfigData, schema *m
} }
function resetToDefaults() { function resetToDefaults() {
if (confirm('Are you sure you want to reset to default configuration? This will overwrite your current settings.')) {
showConfirm('Are you sure you want to reset to default configuration? This will overwrite your current settings.', function() {
fetch('/maintenance/config/defaults', { fetch('/maintenance/config/defaults', {
method: 'POST', method: 'POST',
headers: { headers: {
@ -199,7 +199,7 @@ templ MaintenanceConfigSchema(data *maintenance.MaintenanceConfigData, schema *m
console.error('Error:', error); console.error('Error:', error);
alert('Error resetting configuration: ' + error.message); alert('Error resetting configuration: ' + error.message);
}); });
}
});
} }
</script> </script>
} }

2
weed/admin/view/app/maintenance_config_schema_templ.go
File diff suppressed because it is too large
View File

2
weed/admin/view/app/maintenance_config_templ.go

@ -273,7 +273,7 @@ func MaintenanceConfig(data *maintenance.MaintenanceConfigData) templ.Component
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</p></div></div></div></div></div></div></div></div><script>\n function saveConfiguration() {\n // First, get current configuration to preserve existing values\n fetch('/api/maintenance/config')\n .then(response => response.json())\n .then(currentConfig => {\n // Update only the fields from the form\n const updatedConfig = {\n ...currentConfig.config, // Preserve existing config\n enabled: document.getElementById('enabled').checked,\n scan_interval_seconds: parseInt(document.getElementById('scanInterval').value) * 60, // Convert to seconds\n worker_timeout_seconds: parseInt(document.getElementById('workerTimeout').value) * 60, // Convert to seconds\n task_timeout_seconds: parseInt(document.getElementById('taskTimeout').value) * 3600, // Convert to seconds\n retry_delay_seconds: parseInt(document.getElementById('retryDelay').value) * 60, // Convert to seconds\n max_retries: parseInt(document.getElementById('maxRetries').value),\n task_retention_seconds: parseInt(document.getElementById('taskRetention').value) * 24 * 3600, // Convert to seconds\n policy: {\n ...currentConfig.config.policy, // Preserve existing policy\n global_max_concurrent: parseInt(document.getElementById('globalMaxConcurrent').value)\n }\n };\n\n // Send the updated configuration\n return fetch('/api/maintenance/config', {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(updatedConfig)\n });\n })\n .then(response => response.json())\n .then(data => {\n if (data.success) {\n alert('Configuration saved successfully');\n location.reload(); // Reload to show updated values\n } else {\n alert('Failed to save configuration: ' + (data.error || 'Unknown error'));\n }\n })\n .catch(error => {\n alert('Error: ' + error.message);\n });\n }\n\n function resetToDefaults() {\n if (confirm('Are you sure you want to reset to default configuration? This will overwrite your current settings.')) {\n // Reset form to defaults (matching DefaultMaintenanceConfig values)\n document.getElementById('enabled').checked = false;\n document.getElementById('scanInterval').value = '30';\n document.getElementById('workerTimeout').value = '5';\n document.getElementById('taskTimeout').value = '2';\n document.getElementById('globalMaxConcurrent').value = '4';\n document.getElementById('maxRetries').value = '3';\n document.getElementById('retryDelay').value = '15';\n document.getElementById('taskRetention').value = '7';\n }\n }\n </script>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</p></div></div></div></div></div></div></div></div><script>\n function saveConfiguration() {\n // First, get current configuration to preserve existing values\n fetch('/api/maintenance/config')\n .then(response => response.json())\n .then(currentConfig => {\n // Update only the fields from the form\n const updatedConfig = {\n ...currentConfig.config, // Preserve existing config\n enabled: document.getElementById('enabled').checked,\n scan_interval_seconds: parseInt(document.getElementById('scanInterval').value) * 60, // Convert to seconds\n worker_timeout_seconds: parseInt(document.getElementById('workerTimeout').value) * 60, // Convert to seconds\n task_timeout_seconds: parseInt(document.getElementById('taskTimeout').value) * 3600, // Convert to seconds\n retry_delay_seconds: parseInt(document.getElementById('retryDelay').value) * 60, // Convert to seconds\n max_retries: parseInt(document.getElementById('maxRetries').value),\n task_retention_seconds: parseInt(document.getElementById('taskRetention').value) * 24 * 3600, // Convert to seconds\n policy: {\n ...currentConfig.config.policy, // Preserve existing policy\n global_max_concurrent: parseInt(document.getElementById('globalMaxConcurrent').value)\n }\n };\n\n // Send the updated configuration\n return fetch('/api/maintenance/config', {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(updatedConfig)\n });\n })\n .then(response => response.json())\n .then(data => {\n if (data.success) {\n alert('Configuration saved successfully');\n location.reload(); // Reload to show updated values\n } else {\n alert('Failed to save configuration: ' + (data.error || 'Unknown error'));\n }\n })\n .catch(error => {\n alert('Error: ' + error.message);\n });\n }\n\n function resetToDefaults() {\n showConfirm('Are you sure you want to reset to default configuration? This will overwrite your current settings.', function() {\n // Reset form to defaults (matching DefaultMaintenanceConfig values)\n document.getElementById('enabled').checked = false;\n document.getElementById('scanInterval').value = '30';\n document.getElementById('workerTimeout').value = '5';\n document.getElementById('taskTimeout').value = '2';\n document.getElementById('globalMaxConcurrent').value = '4';\n document.getElementById('maxRetries').value = '3';\n document.getElementById('retryDelay').value = '15';\n document.getElementById('taskRetention').value = '7';\n });\n }\n </script>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }

4
weed/admin/view/app/maintenance_workers.templ

@ -302,7 +302,7 @@ templ MaintenanceWorkers(data *dash.MaintenanceWorkersData) {
function pauseWorker(event) { function pauseWorker(event) {
const workerID = event.target.closest('button').getAttribute('data-worker-id'); const workerID = event.target.closest('button').getAttribute('data-worker-id');
if (confirm('Are you sure you want to pause this worker?')) {
showConfirm('Are you sure you want to pause this worker?', function() {
fetch('/api/maintenance/workers/' + workerID + '/pause', { fetch('/api/maintenance/workers/' + workerID + '/pause', {
method: 'POST' method: 'POST'
}) })
@ -318,7 +318,7 @@ templ MaintenanceWorkers(data *dash.MaintenanceWorkersData) {
console.error('Error pausing worker:', error); console.error('Error pausing worker:', error);
alert('Failed to pause worker'); alert('Failed to pause worker');
}); });
}
});
} }
function formatDuration(nanoseconds) { function formatDuration(nanoseconds) {

2
weed/admin/view/app/maintenance_workers_templ.go
File diff suppressed because it is too large
View File

10
weed/admin/view/app/object_store_users.templ

@ -802,7 +802,7 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
// Delete user function // Delete user function
async function deleteUser(username) { async function deleteUser(username) {
if (confirm(`Are you sure you want to delete user "${username}"? This action cannot be undone.`)) {
showDeleteConfirm(username, async function() {
try { try {
const response = await fetch(`/api/users/${username}`, { const response = await fetch(`/api/users/${username}`, {
method: 'DELETE' method: 'DELETE'
@ -819,6 +819,9 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
console.error('Error deleting user:', error); console.error('Error deleting user:', error);
showErrorMessage('Failed to delete user: ' + error.message); showErrorMessage('Failed to delete user: ' + error.message);
} }
}, 'Are you sure you want to delete this user? This action cannot be undone.');
}
}
} }
} }
@ -1102,7 +1105,7 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
// Delete access key // Delete access key
async function deleteAccessKey(username, accessKey) { async function deleteAccessKey(username, accessKey) {
if (confirm('Are you sure you want to delete this access key?')) {
showDeleteConfirm(accessKey, async function() {
try { try {
const response = await fetch(`/api/users/${username}/access-keys/${accessKey}`, { const response = await fetch(`/api/users/${username}/access-keys/${accessKey}`, {
method: 'DELETE' method: 'DELETE'
@ -1121,6 +1124,9 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
console.error('Error deleting access key:', error); console.error('Error deleting access key:', error);
showErrorMessage('Failed to delete access key: ' + error.message); showErrorMessage('Failed to delete access key: ' + error.message);
} }
}, 'Are you sure you want to delete this access key?');
}
}
} }
} }

2
weed/admin/view/app/object_store_users_templ.go
File diff suppressed because it is too large
View File

6
weed/admin/view/app/policies.templ

@ -635,7 +635,7 @@ templ Policies(data dash.PoliciesData) {
} }
function deletePolicy(policyName) { function deletePolicy(policyName) {
if (confirm('Are you sure you want to delete policy "' + policyName + '"?')) {
showDeleteConfirm(policyName, function() {
fetch('/api/object-store/policies/' + encodeURIComponent(policyName), { fetch('/api/object-store/policies/' + encodeURIComponent(policyName), {
method: 'DELETE' method: 'DELETE'
}) })
@ -643,7 +643,7 @@ templ Policies(data dash.PoliciesData) {
.then(data => { .then(data => {
if (data.success) { if (data.success) {
alert('Policy deleted successfully!'); alert('Policy deleted successfully!');
location.reload(); // Refresh the page
location.reload();
} else { } else {
alert('Error deleting policy: ' + (data.error || 'Unknown error')); alert('Error deleting policy: ' + (data.error || 'Unknown error'));
} }
@ -652,7 +652,7 @@ templ Policies(data dash.PoliciesData) {
console.error('Error:', error); console.error('Error:', error);
alert('Error deleting policy: ' + error.message); alert('Error deleting policy: ' + error.message);
}); });
}
}, 'Are you sure you want to delete this policy?');
} }
</script> </script>
} }

2
weed/admin/view/app/policies_templ.go
File diff suppressed because it is too large
View File

4
weed/admin/view/app/service_accounts.templ

@ -373,7 +373,7 @@ templ ServiceAccounts(data dash.ServiceAccountsData) {
} }
async function deleteSA(id) { async function deleteSA(id) {
if (confirm('Are you sure you want to delete this service account? This action cannot be undone.')) {
showDeleteConfirm(id, async function() {
try { try {
const response = await fetch(`/api/service-accounts/${id}`, { const response = await fetch(`/api/service-accounts/${id}`, {
method: 'DELETE' method: 'DELETE'
@ -390,7 +390,7 @@ templ ServiceAccounts(data dash.ServiceAccountsData) {
console.error('Error deleting service account:', error); console.error('Error deleting service account:', error);
showErrorMessage('Failed to delete: ' + error.message); showErrorMessage('Failed to delete: ' + error.message);
} }
}
}, 'Are you sure you want to delete this service account? This action cannot be undone.');
} }
async function handleCreateServiceAccount() { async function handleCreateServiceAccount() {

2
weed/admin/view/app/service_accounts_templ.go
File diff suppressed because it is too large
View File

4
weed/admin/view/app/task_config.templ

@ -104,13 +104,13 @@ templ TaskConfig(data *maintenance.TaskConfigData) {
<script> <script>
function resetForm() { function resetForm() {
if (confirm('Are you sure you want to reset all settings to their default values?')) {
showConfirm('Are you sure you want to reset all settings to their default values?', function() {
// Find all form inputs and reset them // Find all form inputs and reset them
const form = document.querySelector('form'); const form = document.querySelector('form');
if (form) { if (form) {
form.reset(); form.reset();
} }
}
});
} }
// Auto-save form data to localStorage for recovery // Auto-save form data to localStorage for recovery

4
weed/admin/view/app/task_config_schema.templ

@ -127,7 +127,7 @@ templ TaskConfigSchema(data *maintenance.TaskConfigData, schema *tasks.TaskConfi
<script> <script>
function resetToDefaults() { function resetToDefaults() {
if (confirm('Are you sure you want to reset to default configuration? This will overwrite your current settings.')) {
showConfirm('Are you sure you want to reset to default configuration? This will overwrite your current settings.', function() {
// Reset form fields to their default values // Reset form fields to their default values
const form = document.getElementById('taskConfigForm'); const form = document.getElementById('taskConfigForm');
const schemaFields = window.taskConfigSchema ? window.taskConfigSchema.fields : {}; const schemaFields = window.taskConfigSchema ? window.taskConfigSchema.fields : {};
@ -154,7 +154,7 @@ templ TaskConfigSchema(data *maintenance.TaskConfigData, schema *tasks.TaskConfi
} }
} }
}); });
}
});
} }
function convertSecondsToTaskIntervalValueUnit(totalSeconds) { function convertSecondsToTaskIntervalValueUnit(totalSeconds) {

2
weed/admin/view/app/task_config_schema_templ.go

@ -170,7 +170,7 @@ func TaskConfigSchema(data *maintenance.TaskConfigData, schema *tasks.TaskConfig
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</div></div></div></div></div></div><script>\n function resetToDefaults() {\n if (confirm('Are you sure you want to reset to default configuration? This will overwrite your current settings.')) {\n // Reset form fields to their default values\n const form = document.getElementById('taskConfigForm');\n const schemaFields = window.taskConfigSchema ? window.taskConfigSchema.fields : {};\n \n Object.keys(schemaFields).forEach(fieldName => {\n const field = schemaFields[fieldName];\n const element = document.getElementById(fieldName);\n \n if (element && field.default_value !== undefined) {\n if (field.input_type === 'checkbox') {\n element.checked = field.default_value;\n } else if (field.input_type === 'interval') {\n // Handle interval fields with value and unit\n const valueElement = document.getElementById(fieldName + '_value');\n const unitElement = document.getElementById(fieldName + '_unit');\n if (valueElement && unitElement && field.default_value) {\n const defaultSeconds = field.default_value;\n const { value, unit } = convertSecondsToTaskIntervalValueUnit(defaultSeconds);\n valueElement.value = value;\n unitElement.value = unit;\n }\n } else {\n element.value = field.default_value;\n }\n }\n });\n }\n }\n\n function convertSecondsToTaskIntervalValueUnit(totalSeconds) {\n if (totalSeconds === 0) {\n return { value: 0, unit: 'minutes' };\n }\n\n // Check if it's evenly divisible by days\n if (totalSeconds % (24 * 3600) === 0) {\n return { value: totalSeconds / (24 * 3600), unit: 'days' };\n }\n\n // Check if it's evenly divisible by hours\n if (totalSeconds % 3600 === 0) {\n return { value: totalSeconds / 3600, unit: 'hours' };\n }\n\n // Default to minutes\n return { value: totalSeconds / 60, unit: 'minutes' };\n }\n\n // Store schema data for JavaScript access (moved to after div is created)\n </script><!-- Hidden element to store schema data --><div data-task-schema=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</div></div></div></div></div></div><script>\n function resetToDefaults() {\n showConfirm('Are you sure you want to reset to default configuration? This will overwrite your current settings.', function() {\n // Reset form fields to their default values\n const form = document.getElementById('taskConfigForm');\n const schemaFields = window.taskConfigSchema ? window.taskConfigSchema.fields : {};\n \n Object.keys(schemaFields).forEach(fieldName => {\n const field = schemaFields[fieldName];\n const element = document.getElementById(fieldName);\n \n if (element && field.default_value !== undefined) {\n if (field.input_type === 'checkbox') {\n element.checked = field.default_value;\n } else if (field.input_type === 'interval') {\n // Handle interval fields with value and unit\n const valueElement = document.getElementById(fieldName + '_value');\n const unitElement = document.getElementById(fieldName + '_unit');\n if (valueElement && unitElement && field.default_value) {\n const defaultSeconds = field.default_value;\n const { value, unit } = convertSecondsToTaskIntervalValueUnit(defaultSeconds);\n valueElement.value = value;\n unitElement.value = unit;\n }\n } else {\n element.value = field.default_value;\n }\n }\n });\n });\n }\n\n function convertSecondsToTaskIntervalValueUnit(totalSeconds) {\n if (totalSeconds === 0) {\n return { value: 0, unit: 'minutes' };\n }\n\n // Check if it's evenly divisible by days\n if (totalSeconds % (24 * 3600) === 0) {\n return { value: totalSeconds / (24 * 3600), unit: 'days' };\n }\n\n // Check if it's evenly divisible by hours\n if (totalSeconds % 3600 === 0) {\n return { value: totalSeconds / 3600, unit: 'hours' };\n }\n\n // Default to minutes\n return { value: totalSeconds / 60, unit: 'minutes' };\n }\n\n // Store schema data for JavaScript access (moved to after div is created)\n </script><!-- Hidden element to store schema data --><div data-task-schema=\"")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }

2
weed/admin/view/app/task_config_templ.go

@ -163,7 +163,7 @@ func TaskConfig(data *maintenance.TaskConfigData) templ.Component {
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</p></div></div></div></div></div></div></div><script>\n function resetForm() {\n if (confirm('Are you sure you want to reset all settings to their default values?')) {\n // Find all form inputs and reset them\n const form = document.querySelector('form');\n if (form) {\n form.reset();\n }\n }\n }\n\n // Auto-save form data to localStorage for recovery\n document.addEventListener('DOMContentLoaded', function() {\n const form = document.querySelector('form');\n if (form) {\n const taskType = '{string(data.TaskType)}';\n const storageKey = 'taskConfig_' + taskType;\n\n // Load saved data\n const savedData = localStorage.getItem(storageKey);\n if (savedData) {\n try {\n const data = JSON.parse(savedData);\n Object.keys(data).forEach(key => {\n const input = form.querySelector(`[name=\"${key}\"]`);\n if (input) {\n if (input.type === 'checkbox') {\n input.checked = data[key];\n } else {\n input.value = data[key];\n }\n }\n });\n } catch (e) {\n console.warn('Failed to load saved configuration:', e);\n }\n }\n\n // Save data on input change\n form.addEventListener('input', function() {\n const formData = new FormData(form);\n const data = {};\n for (let [key, value] of formData.entries()) {\n data[key] = value;\n }\n localStorage.setItem(storageKey, JSON.stringify(data));\n });\n\n // Clear saved data on successful submit\n form.addEventListener('submit', function() {\n localStorage.removeItem(storageKey);\n });\n }\n });\n </script>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</p></div></div></div></div></div></div></div><script>\n function resetForm() {\n showConfirm('Are you sure you want to reset all settings to their default values?', function() {\n // Find all form inputs and reset them\n const form = document.querySelector('form');\n if (form) {\n form.reset();\n }\n });\n }\n\n // Auto-save form data to localStorage for recovery\n document.addEventListener('DOMContentLoaded', function() {\n const form = document.querySelector('form');\n if (form) {\n const taskType = '{string(data.TaskType)}';\n const storageKey = 'taskConfig_' + taskType;\n\n // Load saved data\n const savedData = localStorage.getItem(storageKey);\n if (savedData) {\n try {\n const data = JSON.parse(savedData);\n Object.keys(data).forEach(key => {\n const input = form.querySelector(`[name=\"${key}\"]`);\n if (input) {\n if (input.type === 'checkbox') {\n input.checked = data[key];\n } else {\n input.value = data[key];\n }\n }\n });\n } catch (e) {\n console.warn('Failed to load saved configuration:', e);\n }\n }\n\n // Save data on input change\n form.addEventListener('input', function() {\n const formData = new FormData(form);\n const data = {};\n for (let [key, value] of formData.entries()) {\n data[key] = value;\n }\n localStorage.setItem(storageKey, JSON.stringify(data));\n });\n\n // Clear saved data on successful submit\n form.addEventListener('submit', function() {\n localStorage.removeItem(storageKey);\n });\n }\n });\n </script>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }

4
weed/admin/view/app/task_config_templ.templ

@ -121,9 +121,9 @@ templ TaskConfigTempl(data *TaskConfigTemplData) {
// Reset form function // Reset form function
function resetForm() { function resetForm() {
if (confirm('Are you sure you want to reset all changes?')) {
showConfirm('Are you sure you want to reset all changes?', function() {
location.reload(); location.reload();
}
});
} }
// Test configuration function // Test configuration function

2
weed/admin/view/app/task_config_templ_templ.go

@ -101,7 +101,7 @@ func TaskConfigTempl(data *TaskConfigTemplData) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<!-- Form actions --><div class=\"row\"><div class=\"col-12\"><div class=\"card\"><div class=\"card-body\"><div class=\"d-flex justify-content-between\"><div><button type=\"submit\" class=\"btn btn-primary\"><i class=\"fas fa-save me-1\"></i> Save Configuration</button> <button type=\"button\" class=\"btn btn-outline-secondary ms-2\" onclick=\"resetForm()\"><i class=\"fas fa-undo me-1\"></i> Reset</button></div><div><button type=\"button\" class=\"btn btn-outline-info\" onclick=\"testConfiguration()\"><i class=\"fas fa-play me-1\"></i> Test Configuration</button></div></div></div></div></div></div></form></div><script>\n // Form validation\n (function() {\n 'use strict';\n window.addEventListener('load', function() {\n var forms = document.getElementsByClassName('needs-validation');\n var validation = Array.prototype.filter.call(forms, function(form) {\n form.addEventListener('submit', function(event) {\n if (form.checkValidity() === false) {\n event.preventDefault();\n event.stopPropagation();\n }\n form.classList.add('was-validated');\n }, false);\n });\n }, false);\n })();\n\n // Auto-save functionality\n let autoSaveTimeout;\n function autoSave() {\n clearTimeout(autoSaveTimeout);\n autoSaveTimeout = setTimeout(function() {\n const formData = new FormData(document.querySelector('form'));\n localStorage.setItem('task_config_' + '{data.TaskType}', JSON.stringify(Object.fromEntries(formData)));\n }, 1000);\n }\n\n // Add auto-save listeners to all form inputs\n document.addEventListener('DOMContentLoaded', function() {\n const form = document.querySelector('form');\n if (form) {\n form.addEventListener('input', autoSave);\n form.addEventListener('change', autoSave);\n }\n });\n\n // Reset form function\n function resetForm() {\n if (confirm('Are you sure you want to reset all changes?')) {\n location.reload();\n }\n }\n\n // Test configuration function\n function testConfiguration() {\n const formData = new FormData(document.querySelector('form'));\n \n // Show loading state\n const testBtn = document.querySelector('button[onclick=\"testConfiguration()\"]');\n const originalContent = testBtn.innerHTML;\n testBtn.innerHTML = '<i class=\"fas fa-spinner fa-spin me-1\"></i>Testing...';\n testBtn.disabled = true;\n \n fetch('/maintenance/config/{data.TaskType}/test', {\n method: 'POST',\n body: formData\n })\n .then(response => response.json())\n .then(data => {\n if (data.success) {\n alert('Configuration test successful!');\n } else {\n alert('Configuration test failed: ' + data.error);\n }\n })\n .catch(error => {\n alert('Test failed: ' + error);\n })\n .finally(() => {\n testBtn.innerHTML = originalContent;\n testBtn.disabled = false;\n });\n }\n </script>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "<!-- Form actions --><div class=\"row\"><div class=\"col-12\"><div class=\"card\"><div class=\"card-body\"><div class=\"d-flex justify-content-between\"><div><button type=\"submit\" class=\"btn btn-primary\"><i class=\"fas fa-save me-1\"></i> Save Configuration</button> <button type=\"button\" class=\"btn btn-outline-secondary ms-2\" onclick=\"resetForm()\"><i class=\"fas fa-undo me-1\"></i> Reset</button></div><div><button type=\"button\" class=\"btn btn-outline-info\" onclick=\"testConfiguration()\"><i class=\"fas fa-play me-1\"></i> Test Configuration</button></div></div></div></div></div></div></form></div><script>\n // Form validation\n (function() {\n 'use strict';\n window.addEventListener('load', function() {\n var forms = document.getElementsByClassName('needs-validation');\n var validation = Array.prototype.filter.call(forms, function(form) {\n form.addEventListener('submit', function(event) {\n if (form.checkValidity() === false) {\n event.preventDefault();\n event.stopPropagation();\n }\n form.classList.add('was-validated');\n }, false);\n });\n }, false);\n })();\n\n // Auto-save functionality\n let autoSaveTimeout;\n function autoSave() {\n clearTimeout(autoSaveTimeout);\n autoSaveTimeout = setTimeout(function() {\n const formData = new FormData(document.querySelector('form'));\n localStorage.setItem('task_config_' + '{data.TaskType}', JSON.stringify(Object.fromEntries(formData)));\n }, 1000);\n }\n\n // Add auto-save listeners to all form inputs\n document.addEventListener('DOMContentLoaded', function() {\n const form = document.querySelector('form');\n if (form) {\n form.addEventListener('input', autoSave);\n form.addEventListener('change', autoSave);\n }\n });\n\n // Reset form function\n function resetForm() {\n showConfirm('Are you sure you want to reset all changes?', function() {\n location.reload();\n });\n }\n\n // Test configuration function\n function testConfiguration() {\n const formData = new FormData(document.querySelector('form'));\n \n // Show loading state\n const testBtn = document.querySelector('button[onclick=\"testConfiguration()\"]');\n const originalContent = testBtn.innerHTML;\n testBtn.innerHTML = '<i class=\"fas fa-spinner fa-spin me-1\"></i>Testing...';\n testBtn.disabled = true;\n \n fetch('/maintenance/config/{data.TaskType}/test', {\n method: 'POST',\n body: formData\n })\n .then(response => response.json())\n .then(data => {\n if (data.success) {\n alert('Configuration test successful!');\n } else {\n alert('Configuration test failed: ' + data.error);\n }\n })\n .catch(error => {\n alert('Test failed: ' + error);\n })\n .finally(() => {\n testBtn.innerHTML = originalContent;\n testBtn.disabled = false;\n });\n }\n </script>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }

4
weed/admin/view/app/task_detail.templ

@ -1056,7 +1056,7 @@ templ TaskDetail(data *maintenance.TaskDetailData) {
} }
function cancelTask(taskId) { function cancelTask(taskId) {
if (confirm('Are you sure you want to cancel this task?')) {
showConfirm('Are you sure you want to cancel this task?', function() {
fetch(`/api/maintenance/tasks/${taskId}/cancel`, { fetch(`/api/maintenance/tasks/${taskId}/cancel`, {
method: 'POST', method: 'POST',
headers: { headers: {
@ -1076,7 +1076,7 @@ templ TaskDetail(data *maintenance.TaskDetailData) {
console.error('Error:', error); console.error('Error:', error);
alert('Error cancelling task'); alert('Error cancelling task');
}); });
}
});
} }
function refreshTaskLogs(taskId) { function refreshTaskLogs(taskId) {

2
weed/admin/view/app/task_detail_templ.go
File diff suppressed because it is too large
View File

2
weed/admin/view/layout/layout.templ

@ -357,6 +357,8 @@ templ Layout(c *gin.Context, content templ.Component) {
<!-- Bootstrap JS --> <!-- Bootstrap JS -->
<script src="/static/js/bootstrap.bundle.min.js"></script> <script src="/static/js/bootstrap.bundle.min.js"></script>
<!-- Modal Alerts JS (replaces native alert/confirm) -->
<script src="/static/js/modal-alerts.js"></script>
<!-- Custom JS --> <!-- Custom JS -->
<script src="/static/js/admin.js"></script> <script src="/static/js/admin.js"></script>
</body> </body>

8
weed/admin/view/layout/layout_templ.go

@ -517,7 +517,7 @@ func Layout(c *gin.Context, content templ.Component) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} }
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "</small></div></footer><!-- Bootstrap JS --><script src=\"/static/js/bootstrap.bundle.min.js\"></script><!-- Custom JS --><script src=\"/static/js/admin.js\"></script></body></html>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "</small></div></footer><!-- Bootstrap JS --><script src=\"/static/js/bootstrap.bundle.min.js\"></script><!-- Modal Alerts JS (replaces native alert/confirm) --><script src=\"/static/js/modal-alerts.js\"></script><!-- Custom JS --><script src=\"/static/js/admin.js\"></script></body></html>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
@ -553,7 +553,7 @@ func LoginForm(c *gin.Context, title string, errorMessage string) templ.Componen
var templ_7745c5c3_Var28 string var templ_7745c5c3_Var28 string
templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(title) templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/layout/layout.templ`, Line: 371, Col: 17}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/layout/layout.templ`, Line: 373, Col: 17}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -566,7 +566,7 @@ func LoginForm(c *gin.Context, title string, errorMessage string) templ.Componen
var templ_7745c5c3_Var29 string var templ_7745c5c3_Var29 string
templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(title) templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(title)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/layout/layout.templ`, Line: 385, Col: 57}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/layout/layout.templ`, Line: 387, Col: 57}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
@ -584,7 +584,7 @@ func LoginForm(c *gin.Context, title string, errorMessage string) templ.Componen
var templ_7745c5c3_Var30 string var templ_7745c5c3_Var30 string
templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(errorMessage) templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(errorMessage)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/layout/layout.templ`, Line: 392, Col: 45}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/layout/layout.templ`, Line: 394, Col: 45}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {

Loading…
Cancel
Save