upaas/static/js/app.js
sneak 3f9d83c436 Initial commit with server startup infrastructure
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
2025-12-29 15:46:03 +07:00

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
};
})();