Core infrastructure: - Uber fx dependency injection - Chi router with middleware stack - SQLite database with embedded migrations - Embedded templates and static assets - Structured logging with slog Features implemented: - Authentication (login, logout, session management, argon2id hashing) - App management (create, edit, delete, list) - Deployment pipeline (clone, build, deploy, health check) - Webhook processing for Gitea - Notifications (ntfy, Slack) - Environment variables, labels, volumes per app - SSH key generation for deploy keys Server startup: - Server.Run() starts HTTP server on configured port - Server.Shutdown() for graceful shutdown - SetupRoutes() wires all handlers with chi router
216 lines
5.9 KiB
JavaScript
216 lines
5.9 KiB
JavaScript
/**
|
|
* upaas - Frontend JavaScript utilities
|
|
* Vanilla JS, no dependencies
|
|
*/
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
/**
|
|
* Copy text to clipboard
|
|
* @param {string} text - Text to copy
|
|
* @param {HTMLElement} button - Button element to update feedback
|
|
*/
|
|
function copyToClipboard(text, button) {
|
|
const originalText = button.textContent;
|
|
const originalTitle = button.getAttribute('title');
|
|
|
|
navigator.clipboard.writeText(text).then(function() {
|
|
// Success feedback
|
|
button.textContent = 'Copied!';
|
|
button.classList.add('text-success-500');
|
|
|
|
setTimeout(function() {
|
|
button.textContent = originalText;
|
|
button.classList.remove('text-success-500');
|
|
if (originalTitle) {
|
|
button.setAttribute('title', originalTitle);
|
|
}
|
|
}, 2000);
|
|
}).catch(function(err) {
|
|
// Fallback for older browsers
|
|
console.error('Failed to copy:', err);
|
|
|
|
// Try fallback method
|
|
var textArea = document.createElement('textarea');
|
|
textArea.value = text;
|
|
textArea.style.position = 'fixed';
|
|
textArea.style.left = '-9999px';
|
|
document.body.appendChild(textArea);
|
|
textArea.select();
|
|
|
|
try {
|
|
document.execCommand('copy');
|
|
button.textContent = 'Copied!';
|
|
setTimeout(function() {
|
|
button.textContent = originalText;
|
|
}, 2000);
|
|
} catch (e) {
|
|
button.textContent = 'Failed';
|
|
setTimeout(function() {
|
|
button.textContent = originalText;
|
|
}, 2000);
|
|
}
|
|
|
|
document.body.removeChild(textArea);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initialize copy buttons
|
|
* Looks for elements with data-copy attribute
|
|
*/
|
|
function initCopyButtons() {
|
|
var copyButtons = document.querySelectorAll('[data-copy]');
|
|
|
|
copyButtons.forEach(function(button) {
|
|
button.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
var text = button.getAttribute('data-copy');
|
|
copyToClipboard(text, button);
|
|
});
|
|
});
|
|
|
|
// Also handle buttons that copy content from a sibling element
|
|
var copyTargetButtons = document.querySelectorAll('[data-copy-target]');
|
|
|
|
copyTargetButtons.forEach(function(button) {
|
|
button.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
var targetId = button.getAttribute('data-copy-target');
|
|
var target = document.getElementById(targetId);
|
|
if (target) {
|
|
var text = target.textContent || target.value;
|
|
copyToClipboard(text, button);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Confirm destructive actions
|
|
* Looks for forms with data-confirm attribute
|
|
*/
|
|
function initConfirmations() {
|
|
var confirmForms = document.querySelectorAll('form[data-confirm]');
|
|
|
|
confirmForms.forEach(function(form) {
|
|
form.addEventListener('submit', function(e) {
|
|
var message = form.getAttribute('data-confirm');
|
|
if (!confirm(message)) {
|
|
e.preventDefault();
|
|
}
|
|
});
|
|
});
|
|
|
|
// Also handle buttons with data-confirm
|
|
var confirmButtons = document.querySelectorAll('button[data-confirm]');
|
|
|
|
confirmButtons.forEach(function(button) {
|
|
button.addEventListener('click', function(e) {
|
|
var message = button.getAttribute('data-confirm');
|
|
if (!confirm(message)) {
|
|
e.preventDefault();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Toggle visibility of elements
|
|
* Looks for buttons with data-toggle attribute
|
|
*/
|
|
function initToggles() {
|
|
var toggleButtons = document.querySelectorAll('[data-toggle]');
|
|
|
|
toggleButtons.forEach(function(button) {
|
|
button.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
var targetId = button.getAttribute('data-toggle');
|
|
var target = document.getElementById(targetId);
|
|
if (target) {
|
|
target.classList.toggle('hidden');
|
|
|
|
// Update button text if data-toggle-text is provided
|
|
var toggleText = button.getAttribute('data-toggle-text');
|
|
if (toggleText) {
|
|
var currentText = button.textContent;
|
|
button.textContent = toggleText;
|
|
button.setAttribute('data-toggle-text', currentText);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Auto-dismiss alerts after a delay
|
|
* Looks for elements with data-auto-dismiss attribute
|
|
*/
|
|
function initAutoDismiss() {
|
|
var dismissElements = document.querySelectorAll('[data-auto-dismiss]');
|
|
|
|
dismissElements.forEach(function(element) {
|
|
var delay = parseInt(element.getAttribute('data-auto-dismiss'), 10) || 5000;
|
|
|
|
setTimeout(function() {
|
|
element.style.transition = 'opacity 0.3s ease-out';
|
|
element.style.opacity = '0';
|
|
|
|
setTimeout(function() {
|
|
element.remove();
|
|
}, 300);
|
|
}, delay);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Manual dismiss for alerts
|
|
* Looks for buttons with data-dismiss attribute
|
|
*/
|
|
function initDismissButtons() {
|
|
var dismissButtons = document.querySelectorAll('[data-dismiss]');
|
|
|
|
dismissButtons.forEach(function(button) {
|
|
button.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
var targetId = button.getAttribute('data-dismiss');
|
|
var target = targetId ? document.getElementById(targetId) : button.closest('.alert');
|
|
|
|
if (target) {
|
|
target.style.transition = 'opacity 0.3s ease-out';
|
|
target.style.opacity = '0';
|
|
|
|
setTimeout(function() {
|
|
target.remove();
|
|
}, 300);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Initialize all features when DOM is ready
|
|
*/
|
|
function init() {
|
|
initCopyButtons();
|
|
initConfirmations();
|
|
initToggles();
|
|
initAutoDismiss();
|
|
initDismissButtons();
|
|
}
|
|
|
|
// Run on DOM ready
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
} else {
|
|
init();
|
|
}
|
|
|
|
// Expose copyToClipboard globally for inline onclick handlers if needed
|
|
window.upaas = {
|
|
copyToClipboard: copyToClipboard
|
|
};
|
|
|
|
})();
|