mirror of
https://github.com/yggdrasil-network/yggdrasil-go.git
synced 2025-08-24 16:05:07 +03:00
Implement modal system for adding peers and logout confirmation in WebUI
This commit is contained in:
parent
fcb5efd753
commit
19710fbc19
7 changed files with 789 additions and 32 deletions
|
@ -239,29 +239,60 @@ function createPeerElement(peer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new peer
|
* Add a new peer with modal form
|
||||||
*/
|
*/
|
||||||
async function addPeer() {
|
async function addPeer() {
|
||||||
const uri = prompt('Enter peer URI:\nExamples:\n• tcp://example.com:54321\n• tls://peer.yggdrasil.network:443');
|
showModal({
|
||||||
if (!uri || uri.trim() === '') {
|
title: 'add_peer',
|
||||||
showWarning('Peer URI is required');
|
content: 'add_peer_modal_description',
|
||||||
return;
|
size: 'medium',
|
||||||
}
|
inputs: [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'peer_uri',
|
||||||
|
label: 'peer_uri_label',
|
||||||
|
placeholder: 'peer_uri_placeholder',
|
||||||
|
required: true,
|
||||||
|
help: 'peer_uri_help'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: 'modal_cancel',
|
||||||
|
type: 'secondary',
|
||||||
|
action: 'close'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'add_peer_btn',
|
||||||
|
type: 'primary',
|
||||||
|
callback: async (formData) => {
|
||||||
|
const uri = formData.peer_uri?.trim();
|
||||||
|
|
||||||
// Basic URI validation
|
if (!uri) {
|
||||||
if (!uri.includes('://')) {
|
showWarning('Peer URI is required');
|
||||||
showError('Invalid URI format. Must include protocol (tcp://, tls://, etc.)');
|
return false; // Don't close modal
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
// Basic URI validation
|
||||||
showInfo('Adding peer...');
|
if (!uri.includes('://')) {
|
||||||
await window.yggAPI.addPeer(uri.trim());
|
showError('Invalid URI format. Must include protocol (tcp://, tls://, etc.)');
|
||||||
showSuccess(`Peer added successfully: ${uri.trim()}`);
|
return false; // Don't close modal
|
||||||
await loadPeers(); // Refresh peer list
|
}
|
||||||
} catch (error) {
|
|
||||||
showError('Failed to add peer: ' + error.message);
|
try {
|
||||||
}
|
showInfo('Adding peer...');
|
||||||
|
await window.yggAPI.addPeer(uri);
|
||||||
|
showSuccess(`Peer added successfully: ${uri}`);
|
||||||
|
await loadPeers(); // Refresh peer list
|
||||||
|
return true; // Close modal
|
||||||
|
} catch (error) {
|
||||||
|
showError('Failed to add peer: ' + error.message);
|
||||||
|
return false; // Don't close modal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
<script src="static/api.js"></script>
|
<script src="static/api.js"></script>
|
||||||
<script src="static/main.js"></script>
|
<script src="static/main.js"></script>
|
||||||
<script src="static/app.js"></script>
|
<script src="static/app.js"></script>
|
||||||
|
<script src="static/modal.js"></script>
|
||||||
<script src="static/lang/ru.js"></script>
|
<script src="static/lang/ru.js"></script>
|
||||||
<script src="static/lang/en.js"></script>
|
<script src="static/lang/en.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
@ -146,6 +147,21 @@
|
||||||
<!-- Notifications container -->
|
<!-- Notifications container -->
|
||||||
<div class="notifications-container" id="notifications-container"></div>
|
<div class="notifications-container" id="notifications-container"></div>
|
||||||
|
|
||||||
|
<!-- Modal System -->
|
||||||
|
<div class="modal-overlay" id="modal-overlay">
|
||||||
|
<div class="modal-container" id="modal-container">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3 class="modal-title" id="modal-title">Modal Title</h3>
|
||||||
|
<button class="modal-close-btn" id="modal-close-btn" onclick="closeModal()">✕</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-content" id="modal-content">
|
||||||
|
<!-- Modal content will be injected here -->
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer" id="modal-footer">
|
||||||
|
<!-- Modal buttons will be injected here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
|
@ -78,5 +78,25 @@ window.translations.en = {
|
||||||
'peer_quality_good': 'Good',
|
'peer_quality_good': 'Good',
|
||||||
'peer_quality_fair': 'Fair',
|
'peer_quality_fair': 'Fair',
|
||||||
'peer_quality_poor': 'Poor',
|
'peer_quality_poor': 'Poor',
|
||||||
'peer_quality_unknown': 'Unknown'
|
'peer_quality_unknown': 'Unknown',
|
||||||
|
|
||||||
|
// Modal translations
|
||||||
|
'modal_close': 'Close',
|
||||||
|
'modal_cancel': 'Cancel',
|
||||||
|
'modal_ok': 'OK',
|
||||||
|
'modal_confirm': 'Confirmation',
|
||||||
|
'modal_confirm_yes': 'Yes',
|
||||||
|
'modal_confirm_message': 'Are you sure?',
|
||||||
|
'modal_alert': 'Alert',
|
||||||
|
'modal_input': 'Input',
|
||||||
|
'modal_error': 'Error',
|
||||||
|
'modal_success': 'Success',
|
||||||
|
'modal_warning': 'Warning',
|
||||||
|
'modal_info': 'Information',
|
||||||
|
|
||||||
|
// Add peer modal
|
||||||
|
'add_peer_modal_description': 'Enter peer URI to connect to a new network node',
|
||||||
|
'peer_uri_label': 'Peer URI',
|
||||||
|
'peer_uri_placeholder': 'tcp://example.com:54321',
|
||||||
|
'peer_uri_help': 'Examples: tcp://example.com:54321, tls://peer.yggdrasil.network:443'
|
||||||
};
|
};
|
|
@ -78,5 +78,25 @@ window.translations.ru = {
|
||||||
'peer_quality_good': 'Хорошее',
|
'peer_quality_good': 'Хорошее',
|
||||||
'peer_quality_fair': 'Приемлемое',
|
'peer_quality_fair': 'Приемлемое',
|
||||||
'peer_quality_poor': 'Плохое',
|
'peer_quality_poor': 'Плохое',
|
||||||
'peer_quality_unknown': 'Неизвестно'
|
'peer_quality_unknown': 'Неизвестно',
|
||||||
|
|
||||||
|
// Modal translations
|
||||||
|
'modal_close': 'Закрыть',
|
||||||
|
'modal_cancel': 'Отмена',
|
||||||
|
'modal_ok': 'ОК',
|
||||||
|
'modal_confirm': 'Подтверждение',
|
||||||
|
'modal_confirm_yes': 'Да',
|
||||||
|
'modal_confirm_message': 'Вы уверены?',
|
||||||
|
'modal_alert': 'Уведомление',
|
||||||
|
'modal_input': 'Ввод данных',
|
||||||
|
'modal_error': 'Ошибка',
|
||||||
|
'modal_success': 'Успешно',
|
||||||
|
'modal_warning': 'Предупреждение',
|
||||||
|
'modal_info': 'Информация',
|
||||||
|
|
||||||
|
// Add peer modal
|
||||||
|
'add_peer_modal_description': 'Введите URI пира для подключения к новому узлу сети',
|
||||||
|
'peer_uri_label': 'URI пира',
|
||||||
|
'peer_uri_placeholder': 'tcp://example.com:54321',
|
||||||
|
'peer_uri_help': 'Примеры: tcp://example.com:54321, tls://peer.yggdrasil.network:443'
|
||||||
};
|
};
|
|
@ -170,17 +170,24 @@ function showSection(sectionName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logout function (placeholder)
|
* Logout function with modal confirmation
|
||||||
*/
|
*/
|
||||||
function logout() {
|
function logout() {
|
||||||
if (confirm('Are you sure you want to logout?')) {
|
showConfirmModal({
|
||||||
// Clear stored preferences
|
title: 'modal_confirm',
|
||||||
localStorage.removeItem('yggdrasil-language');
|
message: 'logout_confirm',
|
||||||
localStorage.removeItem('yggdrasil-theme');
|
confirmText: 'modal_confirm_yes',
|
||||||
|
cancelText: 'modal_cancel',
|
||||||
|
type: 'danger',
|
||||||
|
onConfirm: () => {
|
||||||
|
// Clear stored preferences
|
||||||
|
localStorage.removeItem('yggdrasil-language');
|
||||||
|
localStorage.removeItem('yggdrasil-theme');
|
||||||
|
|
||||||
// Redirect or refresh
|
// Redirect or refresh
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notification system
|
// Notification system
|
||||||
|
|
415
src/webui/static/modal.js
Normal file
415
src/webui/static/modal.js
Normal file
|
@ -0,0 +1,415 @@
|
||||||
|
/**
|
||||||
|
* Modal System for Yggdrasil Web Interface
|
||||||
|
* Provides flexible modal dialogs with multiple action buttons and input forms
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Global modal state
|
||||||
|
let currentModal = null;
|
||||||
|
let modalCallbacks = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a modal dialog
|
||||||
|
* @param {Object} options - Modal configuration
|
||||||
|
* @param {string} options.title - Modal title (supports localization key)
|
||||||
|
* @param {string|HTMLElement} options.content - Modal content (supports localization key)
|
||||||
|
* @param {Array} options.buttons - Array of button configurations
|
||||||
|
* @param {Array} options.inputs - Array of input field configurations
|
||||||
|
* @param {Function} options.onClose - Callback when modal is closed
|
||||||
|
* @param {boolean} options.closable - Whether modal can be closed by clicking overlay or X button
|
||||||
|
* @param {string} options.size - Modal size: 'small', 'medium', 'large'
|
||||||
|
*/
|
||||||
|
function showModal(options = {}) {
|
||||||
|
const {
|
||||||
|
title = 'Modal',
|
||||||
|
content = '',
|
||||||
|
buttons = [{ text: 'modal_close', type: 'secondary', action: 'close' }],
|
||||||
|
inputs = [],
|
||||||
|
onClose = null,
|
||||||
|
closable = true,
|
||||||
|
size = 'medium'
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
const overlay = document.getElementById('modal-overlay');
|
||||||
|
const container = document.getElementById('modal-container');
|
||||||
|
const titleElement = document.getElementById('modal-title');
|
||||||
|
const contentElement = document.getElementById('modal-content');
|
||||||
|
const footerElement = document.getElementById('modal-footer');
|
||||||
|
const closeBtn = document.getElementById('modal-close-btn');
|
||||||
|
|
||||||
|
if (!overlay || !container) {
|
||||||
|
console.error('Modal elements not found in DOM');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set modal size
|
||||||
|
container.className = `modal-container modal-${size}`;
|
||||||
|
|
||||||
|
// Set title (with localization support)
|
||||||
|
titleElement.textContent = getLocalizedText(title);
|
||||||
|
|
||||||
|
// Set content
|
||||||
|
if (typeof content === 'string') {
|
||||||
|
contentElement.innerHTML = `<p>${getLocalizedText(content)}</p>`;
|
||||||
|
} else if (content instanceof HTMLElement) {
|
||||||
|
contentElement.innerHTML = '';
|
||||||
|
contentElement.appendChild(content);
|
||||||
|
} else {
|
||||||
|
contentElement.innerHTML = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add input fields if provided
|
||||||
|
if (inputs && inputs.length > 0) {
|
||||||
|
const formContainer = document.createElement('div');
|
||||||
|
formContainer.className = 'modal-form-container';
|
||||||
|
|
||||||
|
inputs.forEach((input, index) => {
|
||||||
|
const formGroup = createFormGroup(input, index);
|
||||||
|
formContainer.appendChild(formGroup);
|
||||||
|
});
|
||||||
|
|
||||||
|
contentElement.appendChild(formContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create buttons
|
||||||
|
footerElement.innerHTML = '';
|
||||||
|
buttons.forEach((button, index) => {
|
||||||
|
const btn = createModalButton(button, index);
|
||||||
|
footerElement.appendChild(btn);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Configure close button
|
||||||
|
closeBtn.style.display = closable ? 'flex' : 'none';
|
||||||
|
|
||||||
|
// Set up event handlers
|
||||||
|
modalCallbacks.onClose = onClose;
|
||||||
|
|
||||||
|
// Close on overlay click (if closable)
|
||||||
|
if (closable) {
|
||||||
|
overlay.onclick = (e) => {
|
||||||
|
if (e.target === overlay) {
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
overlay.onclick = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close on Escape key (if closable)
|
||||||
|
if (closable) {
|
||||||
|
document.addEventListener('keydown', handleEscapeKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show modal
|
||||||
|
currentModal = options;
|
||||||
|
overlay.classList.add('show');
|
||||||
|
|
||||||
|
// Focus first input if available
|
||||||
|
setTimeout(() => {
|
||||||
|
const firstInput = contentElement.querySelector('.modal-form-input');
|
||||||
|
if (firstInput) {
|
||||||
|
firstInput.focus();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the current modal
|
||||||
|
*/
|
||||||
|
function closeModal() {
|
||||||
|
const overlay = document.getElementById('modal-overlay');
|
||||||
|
if (!overlay) return;
|
||||||
|
|
||||||
|
overlay.classList.remove('show');
|
||||||
|
|
||||||
|
// Clean up event listeners
|
||||||
|
document.removeEventListener('keydown', handleEscapeKey);
|
||||||
|
overlay.onclick = null;
|
||||||
|
|
||||||
|
// Call onClose callback if provided
|
||||||
|
if (modalCallbacks.onClose) {
|
||||||
|
modalCallbacks.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear state
|
||||||
|
currentModal = null;
|
||||||
|
modalCallbacks = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a form group element
|
||||||
|
*/
|
||||||
|
function createFormGroup(input, index) {
|
||||||
|
const {
|
||||||
|
type = 'text',
|
||||||
|
name = `input_${index}`,
|
||||||
|
label = '',
|
||||||
|
placeholder = '',
|
||||||
|
value = '',
|
||||||
|
required = false,
|
||||||
|
help = '',
|
||||||
|
options = [] // for select inputs
|
||||||
|
} = input;
|
||||||
|
|
||||||
|
const formGroup = document.createElement('div');
|
||||||
|
formGroup.className = 'modal-form-group';
|
||||||
|
|
||||||
|
// Create label
|
||||||
|
if (label) {
|
||||||
|
const labelElement = document.createElement('label');
|
||||||
|
labelElement.className = 'modal-form-label';
|
||||||
|
labelElement.textContent = getLocalizedText(label);
|
||||||
|
labelElement.setAttribute('for', name);
|
||||||
|
formGroup.appendChild(labelElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create input element
|
||||||
|
let inputElement;
|
||||||
|
|
||||||
|
if (type === 'textarea') {
|
||||||
|
inputElement = document.createElement('textarea');
|
||||||
|
inputElement.className = 'modal-form-textarea';
|
||||||
|
} else if (type === 'select') {
|
||||||
|
inputElement = document.createElement('select');
|
||||||
|
inputElement.className = 'modal-form-select';
|
||||||
|
|
||||||
|
// Add options
|
||||||
|
options.forEach(option => {
|
||||||
|
const optionElement = document.createElement('option');
|
||||||
|
optionElement.value = option.value || option;
|
||||||
|
optionElement.textContent = getLocalizedText(option.text || option);
|
||||||
|
if (option.selected || option.value === value) {
|
||||||
|
optionElement.selected = true;
|
||||||
|
}
|
||||||
|
inputElement.appendChild(optionElement);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
inputElement = document.createElement('input');
|
||||||
|
inputElement.type = type;
|
||||||
|
inputElement.className = 'modal-form-input';
|
||||||
|
inputElement.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
inputElement.name = name;
|
||||||
|
inputElement.id = name;
|
||||||
|
|
||||||
|
if (placeholder) {
|
||||||
|
inputElement.placeholder = getLocalizedText(placeholder);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (required) {
|
||||||
|
inputElement.required = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
formGroup.appendChild(inputElement);
|
||||||
|
|
||||||
|
// Create help text
|
||||||
|
if (help) {
|
||||||
|
const helpElement = document.createElement('div');
|
||||||
|
helpElement.className = 'modal-form-help';
|
||||||
|
helpElement.textContent = getLocalizedText(help);
|
||||||
|
formGroup.appendChild(helpElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
return formGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a modal button
|
||||||
|
*/
|
||||||
|
function createModalButton(button, index) {
|
||||||
|
const {
|
||||||
|
text = 'Button',
|
||||||
|
type = 'secondary',
|
||||||
|
action = null,
|
||||||
|
callback = null,
|
||||||
|
disabled = false
|
||||||
|
} = button;
|
||||||
|
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.className = `modal-btn modal-btn-${type}`;
|
||||||
|
btn.textContent = getLocalizedText(text);
|
||||||
|
btn.disabled = disabled;
|
||||||
|
|
||||||
|
btn.onclick = () => {
|
||||||
|
if (action === 'close') {
|
||||||
|
closeModal();
|
||||||
|
} else if (callback) {
|
||||||
|
const formData = getModalFormData();
|
||||||
|
const result = callback(formData);
|
||||||
|
|
||||||
|
// If callback returns false, don't close modal
|
||||||
|
if (result !== false) {
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return btn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get form data from modal inputs
|
||||||
|
*/
|
||||||
|
function getModalFormData() {
|
||||||
|
const formData = {};
|
||||||
|
const inputs = document.querySelectorAll('#modal-content .modal-form-input, #modal-content .modal-form-textarea, #modal-content .modal-form-select');
|
||||||
|
|
||||||
|
inputs.forEach(input => {
|
||||||
|
formData[input.name] = input.value;
|
||||||
|
});
|
||||||
|
|
||||||
|
return formData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle Escape key press
|
||||||
|
*/
|
||||||
|
function handleEscapeKey(e) {
|
||||||
|
if (e.key === 'Escape' && currentModal) {
|
||||||
|
closeModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get localized text or return original if not found
|
||||||
|
*/
|
||||||
|
function getLocalizedText(key) {
|
||||||
|
if (typeof key !== 'string') return key;
|
||||||
|
|
||||||
|
const currentLang = window.getCurrentLanguage ? window.getCurrentLanguage() : 'en';
|
||||||
|
|
||||||
|
if (window.translations &&
|
||||||
|
window.translations[currentLang] &&
|
||||||
|
window.translations[currentLang][key]) {
|
||||||
|
return window.translations[currentLang][key];
|
||||||
|
}
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience functions for common modal types
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a confirmation dialog
|
||||||
|
*/
|
||||||
|
function showConfirmModal(options = {}) {
|
||||||
|
const {
|
||||||
|
title = 'modal_confirm',
|
||||||
|
message = 'modal_confirm_message',
|
||||||
|
confirmText = 'modal_confirm_yes',
|
||||||
|
cancelText = 'modal_cancel',
|
||||||
|
onConfirm = null,
|
||||||
|
onCancel = null,
|
||||||
|
type = 'danger' // danger, primary, success
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
showModal({
|
||||||
|
title,
|
||||||
|
content: message,
|
||||||
|
closable: true,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: cancelText,
|
||||||
|
type: 'secondary',
|
||||||
|
action: 'close',
|
||||||
|
callback: onCancel
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: confirmText,
|
||||||
|
type: type,
|
||||||
|
callback: () => {
|
||||||
|
if (onConfirm) onConfirm();
|
||||||
|
return true; // Close modal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show an alert dialog
|
||||||
|
*/
|
||||||
|
function showAlertModal(options = {}) {
|
||||||
|
const {
|
||||||
|
title = 'modal_alert',
|
||||||
|
message = '',
|
||||||
|
buttonText = 'modal_ok',
|
||||||
|
type = 'primary',
|
||||||
|
onClose = null
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
showModal({
|
||||||
|
title,
|
||||||
|
content: message,
|
||||||
|
closable: true,
|
||||||
|
onClose,
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: buttonText,
|
||||||
|
type: type,
|
||||||
|
action: 'close'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a prompt dialog with input
|
||||||
|
*/
|
||||||
|
function showPromptModal(options = {}) {
|
||||||
|
const {
|
||||||
|
title = 'modal_input',
|
||||||
|
message = '',
|
||||||
|
inputLabel = '',
|
||||||
|
inputPlaceholder = '',
|
||||||
|
inputValue = '',
|
||||||
|
inputType = 'text',
|
||||||
|
inputRequired = true,
|
||||||
|
confirmText = 'modal_ok',
|
||||||
|
cancelText = 'modal_cancel',
|
||||||
|
onConfirm = null,
|
||||||
|
onCancel = null
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
showModal({
|
||||||
|
title,
|
||||||
|
content: message,
|
||||||
|
closable: true,
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
type: inputType,
|
||||||
|
name: 'input_value',
|
||||||
|
label: inputLabel,
|
||||||
|
placeholder: inputPlaceholder,
|
||||||
|
value: inputValue,
|
||||||
|
required: inputRequired
|
||||||
|
}
|
||||||
|
],
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
text: cancelText,
|
||||||
|
type: 'secondary',
|
||||||
|
action: 'close',
|
||||||
|
callback: onCancel
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: confirmText,
|
||||||
|
type: 'primary',
|
||||||
|
callback: (formData) => {
|
||||||
|
if (inputRequired && !formData.input_value.trim()) {
|
||||||
|
return false; // Don't close modal
|
||||||
|
}
|
||||||
|
if (onConfirm) onConfirm(formData.input_value);
|
||||||
|
return true; // Close modal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export functions to global scope
|
||||||
|
window.showModal = showModal;
|
||||||
|
window.closeModal = closeModal;
|
||||||
|
window.showConfirmModal = showConfirmModal;
|
||||||
|
window.showAlertModal = showAlertModal;
|
||||||
|
window.showPromptModal = showPromptModal;
|
|
@ -1316,3 +1316,251 @@ button[onclick="copyNodeKey()"]:hover {
|
||||||
/* Responsive design for peer items */
|
/* Responsive design for peer items */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ======================== */
|
||||||
|
/* MODAL SYSTEM */
|
||||||
|
/* ======================== */
|
||||||
|
|
||||||
|
/* Modal overlay background */
|
||||||
|
.modal-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 10000;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-overlay.show {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal container */
|
||||||
|
.modal-container {
|
||||||
|
background: var(--bg-info-card);
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 20px 60px var(--shadow-heavy);
|
||||||
|
max-width: 500px;
|
||||||
|
width: 90%;
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow: hidden;
|
||||||
|
transform: scale(0.9) translateY(-20px);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border: 1px solid var(--border-card);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal sizes */
|
||||||
|
.modal-small {
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-medium {
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-large {
|
||||||
|
max-width: 700px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-overlay.show .modal-container {
|
||||||
|
transform: scale(1) translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal header */
|
||||||
|
.modal-header {
|
||||||
|
padding: 20px 24px 16px;
|
||||||
|
border-bottom: 1px solid var(--border-card);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
background: var(--bg-nav-item);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-title {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text-heading);
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 20px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-close-btn:hover {
|
||||||
|
background: var(--bg-nav-hover);
|
||||||
|
color: var(--text-heading);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal content */
|
||||||
|
.modal-content {
|
||||||
|
padding: 24px;
|
||||||
|
max-height: 60vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
color: var(--text-body);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content p {
|
||||||
|
margin: 0 0 16px 0;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal footer */
|
||||||
|
.modal-footer {
|
||||||
|
padding: 16px 24px 20px;
|
||||||
|
border-top: 1px solid var(--border-card);
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
background: var(--bg-nav-item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal buttons */
|
||||||
|
.modal-btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn-primary {
|
||||||
|
background: var(--bg-nav-active);
|
||||||
|
color: var(--text-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn-primary:hover {
|
||||||
|
background: var(--bg-nav-active-border);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn-secondary {
|
||||||
|
background: var(--bg-nav-item);
|
||||||
|
color: var(--text-nav);
|
||||||
|
border: 1px solid var(--border-nav-item);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn-secondary:hover {
|
||||||
|
background: var(--bg-nav-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn-danger {
|
||||||
|
background: var(--bg-logout);
|
||||||
|
color: var(--text-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn-danger:hover {
|
||||||
|
background: var(--bg-logout-hover);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn-success {
|
||||||
|
background: var(--bg-success-dark);
|
||||||
|
color: var(--text-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn-success:hover {
|
||||||
|
background: #218838;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal form elements */
|
||||||
|
.modal-form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-form-group:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-form-label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
color: var(--text-heading);
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-form-input,
|
||||||
|
.modal-form-textarea,
|
||||||
|
.modal-form-select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border: 1px solid var(--border-nav-item);
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
background: var(--bg-info-card);
|
||||||
|
color: var(--text-body);
|
||||||
|
transition: border-color 0.2s ease;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-form-input:focus,
|
||||||
|
.modal-form-textarea:focus,
|
||||||
|
.modal-form-select:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--bg-nav-active);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-form-textarea {
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-form-help {
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive design */
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.modal-container {
|
||||||
|
width: 95%;
|
||||||
|
margin: 20px;
|
||||||
|
max-height: 90vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header,
|
||||||
|
.modal-content,
|
||||||
|
.modal-footer {
|
||||||
|
padding-left: 16px;
|
||||||
|
padding-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-btn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue