Add script execution to Web UI
- Add "Quick Actions" section to display configured scripts - Load scripts from /api/scripts/list on connection - Display scripts in responsive grid layout - Execute scripts with single click via /api/scripts/execute - Show toast notifications for execution feedback - Visual feedback during script execution - Auto-hide section if no scripts configured Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -263,6 +263,108 @@
|
|||||||
border-top: 1px solid var(--border);
|
border-top: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Scripts Section */
|
||||||
|
.scripts-container {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 2rem;
|
||||||
|
margin-top: 2rem;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.scripts-container h2 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.scripts-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.script-btn {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
min-height: 80px;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
color: var(--text-primary);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.script-btn:hover:not(:disabled) {
|
||||||
|
background: var(--accent);
|
||||||
|
border-color: var(--accent);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.script-btn:disabled {
|
||||||
|
opacity: 0.3;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.script-btn .script-label {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.script-btn .script-description {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.script-btn.executing {
|
||||||
|
opacity: 0.6;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scripts-empty {
|
||||||
|
text-align: center;
|
||||||
|
color: var(--text-muted);
|
||||||
|
padding: 2rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 2rem;
|
||||||
|
right: 2rem;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
transition: all 0.3s;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.show {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.success {
|
||||||
|
border-color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast.error {
|
||||||
|
border-color: var(--error);
|
||||||
|
}
|
||||||
|
|
||||||
/* Auth Modal */
|
/* Auth Modal */
|
||||||
#auth-overlay {
|
#auth-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -489,8 +591,19 @@
|
|||||||
Source: <span id="source">Unknown</span>
|
Source: <span id="source">Unknown</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Scripts Section -->
|
||||||
|
<div class="scripts-container" id="scripts-container" style="display: none;">
|
||||||
|
<h2>Quick Actions</h2>
|
||||||
|
<div class="scripts-grid" id="scripts-grid">
|
||||||
|
<div class="scripts-empty">No scripts configured</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Toast Notification -->
|
||||||
|
<div class="toast" id="toast"></div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
let ws = null;
|
let ws = null;
|
||||||
let reconnectTimeout = null;
|
let reconnectTimeout = null;
|
||||||
@@ -498,12 +611,14 @@
|
|||||||
let currentDuration = 0;
|
let currentDuration = 0;
|
||||||
let currentPosition = 0;
|
let currentPosition = 0;
|
||||||
let isUserAdjustingVolume = false;
|
let isUserAdjustingVolume = false;
|
||||||
|
let scripts = [];
|
||||||
|
|
||||||
// Initialize on page load
|
// Initialize on page load
|
||||||
window.addEventListener('DOMContentLoaded', () => {
|
window.addEventListener('DOMContentLoaded', () => {
|
||||||
const token = localStorage.getItem('media_server_token');
|
const token = localStorage.getItem('media_server_token');
|
||||||
if (token) {
|
if (token) {
|
||||||
connectWebSocket(token);
|
connectWebSocket(token);
|
||||||
|
loadScripts();
|
||||||
} else {
|
} else {
|
||||||
showAuthForm();
|
showAuthForm();
|
||||||
}
|
}
|
||||||
@@ -588,6 +703,7 @@
|
|||||||
console.log('WebSocket connected');
|
console.log('WebSocket connected');
|
||||||
updateConnectionStatus(true);
|
updateConnectionStatus(true);
|
||||||
hideAuthForm();
|
hideAuthForm();
|
||||||
|
loadScripts();
|
||||||
};
|
};
|
||||||
|
|
||||||
ws.onmessage = (event) => {
|
ws.onmessage = (event) => {
|
||||||
@@ -796,6 +912,102 @@
|
|||||||
function seek(position) {
|
function seek(position) {
|
||||||
sendCommand('seek', { position: position });
|
sendCommand('seek', { position: position });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Scripts functionality
|
||||||
|
async function loadScripts() {
|
||||||
|
const token = localStorage.getItem('media_server_token');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/scripts/list', {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
scripts = await response.json();
|
||||||
|
displayScripts();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading scripts:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayScripts() {
|
||||||
|
const container = document.getElementById('scripts-container');
|
||||||
|
const grid = document.getElementById('scripts-grid');
|
||||||
|
|
||||||
|
if (scripts.length === 0) {
|
||||||
|
container.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.style.display = 'block';
|
||||||
|
grid.innerHTML = '';
|
||||||
|
|
||||||
|
scripts.forEach(script => {
|
||||||
|
const button = document.createElement('button');
|
||||||
|
button.className = 'script-btn';
|
||||||
|
button.onclick = () => executeScript(script.name, button);
|
||||||
|
|
||||||
|
const label = document.createElement('div');
|
||||||
|
label.className = 'script-label';
|
||||||
|
label.textContent = script.label || script.name;
|
||||||
|
|
||||||
|
button.appendChild(label);
|
||||||
|
|
||||||
|
if (script.description) {
|
||||||
|
const description = document.createElement('div');
|
||||||
|
description.className = 'script-description';
|
||||||
|
description.textContent = script.description;
|
||||||
|
button.appendChild(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
grid.appendChild(button);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executeScript(scriptName, buttonElement) {
|
||||||
|
const token = localStorage.getItem('media_server_token');
|
||||||
|
|
||||||
|
// Add executing state
|
||||||
|
buttonElement.classList.add('executing');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/scripts/execute/${scriptName}`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ args: [] })
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (response.ok && result.success) {
|
||||||
|
showToast(`${scriptName} executed successfully`, 'success');
|
||||||
|
} else {
|
||||||
|
showToast(`Failed to execute ${scriptName}`, 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error executing script ${scriptName}:`, error);
|
||||||
|
showToast(`Error executing ${scriptName}`, 'error');
|
||||||
|
} finally {
|
||||||
|
// Remove executing state
|
||||||
|
buttonElement.classList.remove('executing');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showToast(message, type = 'success') {
|
||||||
|
const toast = document.getElementById('toast');
|
||||||
|
toast.textContent = message;
|
||||||
|
toast.className = `toast ${type} show`;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
toast.classList.remove('show');
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user