Add frontend support for script execution

This commit is contained in:
Sami Abuzakuk
2025-10-11 12:38:19 +02:00
parent 78b19a03a8
commit 6afc50eb81
3 changed files with 197 additions and 38 deletions

View File

@@ -20,6 +20,8 @@ export interface Log {
id: number; id: number;
script_id: number; script_id: number;
message: string; message: string;
error_message: string;
error_code: number;
created_at?: string; created_at?: string;
} }
export interface Script { export interface Script {
@@ -27,6 +29,7 @@ export interface Script {
name: string; name: string;
script_content?: string; script_content?: string;
created_at?: string; created_at?: string;
enabled: boolean;
} }
// Fetch all scripts // Fetch all scripts
@@ -39,7 +42,9 @@ export async function fetchScripts(): Promise<Script[]> {
} }
// Add a new script // Add a new script
export async function addScript(script: Omit<Script, 'id' | 'created_at'>): Promise<Script> { export async function addScript(
script: Omit<Script, 'id' | 'created_at' | 'enabled'>
): Promise<Script> {
const response = await fetch(`${API_URL}/script`, { const response = await fetch(`${API_URL}/script`, {
method: 'POST', method: 'POST',
headers: { headers: {
@@ -87,13 +92,13 @@ export async function fetchLogs(scriptId: number): Promise<Log[]> {
} }
// Add a new log to a specific script // Add a new log to a specific script
export async function addLog(scriptId: number, message: string): Promise<Log> { export async function addLog(scriptId: number, log: Log): Promise<Log> {
const response = await fetch(`${API_URL}/script/${scriptId}/log`, { const response = await fetch(`${API_URL}/script/${scriptId}/log`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ message }) body: JSON.stringify(log)
}); });
if (!response.ok) { if (!response.ok) {
throw new Error('Failed to add log'); throw new Error('Failed to add log');
@@ -101,6 +106,17 @@ export async function addLog(scriptId: number, message: string): Promise<Log> {
return response.json(); return response.json();
} }
// Execute a script by ID
export async function executeScript(scriptId: number): Promise<{ message: string }> {
const response = await fetch(`${API_URL}/script/${scriptId}/execute`, {
method: 'POST'
});
if (!response.ok) {
throw new Error('Failed to execute script');
}
return response.json();
}
// Delete a log from a specific script // Delete a log from a specific script
export async function deleteLog(scriptId: number, logId: number): Promise<void> { export async function deleteLog(scriptId: number, logId: number): Promise<void> {
const response = await fetch(`${API_URL}/script/${scriptId}/log/${logId}`, { const response = await fetch(`${API_URL}/script/${scriptId}/log/${logId}`, {

View File

@@ -27,7 +27,9 @@
{#each scripts as script (script.id)} {#each scripts as script (script.id)}
<a <a
href={`/scripts/${script.id}`} href={`/scripts/${script.id}`}
class="block p-4 border rounded shadow bg-white hover:bg-gray-100" class="block p-4 border rounded bg-white hover:bg-gray-100 {script.enabled
? 'shadow-lg shadow-green-500/50'
: 'shadow-lg shadow-red-500/50'}"
> >
<h2 class="text-lg font-semibold text-gray-800">{script.name}</h2> <h2 class="text-lg font-semibold text-gray-800">{script.name}</h2>
</a> </a>

View File

@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { updateScript, deleteScript, addLog, deleteLog } from '$lib/api'; import { updateScript, deleteScript, addLog, deleteLog, executeScript } from '$lib/api';
import type { Script, Log } from '$lib/api'; import type { Script, Log } from '$lib/api';
import CodeMirror from 'svelte-codemirror-editor'; import CodeMirror from 'svelte-codemirror-editor';
import { python } from '@codemirror/lang-python'; import { python } from '@codemirror/lang-python';
@@ -9,17 +9,44 @@
let logs: Log[] = data.logs; let logs: Log[] = data.logs;
let updatedTitle: string = script.name || ''; let updatedTitle: string = script.name || '';
let updatedContent: string = script.script_content || ''; let updatedContent: string = script.script_content || '';
let updatedEnabled: boolean = script.enabled || false;
let isEditMode: boolean = false; let isEditMode: boolean = false;
let newLogMessage: string = ''; let newLog: Omit<Log, 'id' | 'script_id'> = {
message: '',
error_code: 0,
error_message: ''
};
let selectedLog: Log | null = null;
function openLogPopup(log: Log) {
selectedLog = log;
}
function closeLogPopup() {
selectedLog = null;
}
// Notifications are now handled globally via the layout // Notifications are now handled globally via the layout
async function handleExecuteScript() {
try {
await executeScript(script.id);
window.showNotification('success', 'Script executed successfully!');
} catch (err) {
window.showNotification('error', 'Failed to execute script. ' + err);
}
}
async function handleUpdateScript() { async function handleUpdateScript() {
if (script) { if (script) {
try { try {
const updatedScript = await updateScript(script.id, { const updatedScript = await updateScript(script.id, {
name: updatedTitle, name: updatedTitle,
script_content: updatedContent script_content: updatedContent,
enabled: updatedEnabled
}); });
script = updatedScript; script = updatedScript;
window.showNotification('success', 'Script updated successfully!'); window.showNotification('success', 'Script updated successfully!');
@@ -31,11 +58,15 @@
} }
async function handleAddLog() { async function handleAddLog() {
if (newLogMessage.trim()) { if (newLog.message.trim()) {
try { try {
const newLog = await addLog(script.id, newLogMessage); const addedLog = await addLog(script.id, newLog);
logs = [newLog, ...logs]; logs = [addedLog, ...logs];
newLogMessage = ''; newLog = {
message: '',
error_code: 0,
error_message: ''
};
window.showNotification('success', 'Log added successfully!'); window.showNotification('success', 'Log added successfully!');
} catch (err) { } catch (err) {
window.showNotification('error', 'Failed to add log. ' + err); window.showNotification('error', 'Failed to add log. ' + err);
@@ -77,7 +108,19 @@
required required
class="text-2xl font-bold mb-4 w-full p-2 border rounded" class="text-2xl font-bold mb-4 w-full p-2 border rounded"
/> />
<CodeMirror bind:value={updatedContent} lang={python()} /> <CodeMirror bind:value={updatedContent} lang={python()} />
<div class="mt-4 flex items-center space-x-2">
<input
id="enabled"
type="checkbox"
bind:checked={updatedEnabled}
class="h-5 w-5 text-blue-500 border-gray-300 rounded focus:ring focus:ring-blue-200"
/>
<label for="enabled" class="text-sm font-medium text-gray-700">Enabled</label>
</div>
<button <button
class="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600" class="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
on:click={handleUpdateScript} on:click={handleUpdateScript}
@@ -98,51 +141,149 @@
<h1 class="text-2xl font-bold mb-4">{script.name}</h1> <h1 class="text-2xl font-bold mb-4">{script.name}</h1>
<pre <pre
class="w-full p-2 border rounded font-mono text-sm bg-gray-100">{script.script_content}</pre> class="w-full p-2 border rounded font-mono text-sm bg-gray-100">{script.script_content}</pre>
<button <div class="mt-4 flex items-center space-x-2">
class="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600" <span class="text-sm font-medium text-gray-700">Enabled:</span>
on:click={() => (isEditMode = true)} <span
> class="px-2 py-1 text-xs font-semibold rounded"
Edit Script class:bg-green-100={script.enabled}
</button> class:bg-red-100={!script.enabled}
<button class:text-green-700={script.enabled}
class="mt-4 px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600" class:text-red-700={!script.enabled}
on:click={handleDeleteScript} >
> {script.enabled ? 'Yes' : 'No'}
Delete Script </span>
</button> </div>
<div class="mt-4 flex justify-between">
<div class="flex space-x-2">
<button
class="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600"
on:click={handleExecuteScript}
aria-label="Execute the script"
>
Execute Script
</button>
<button
class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
on:click={() => (isEditMode = true)}
>
Edit Script
</button>
</div>
<button
class="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
on:click={handleDeleteScript}
>
Delete Script
</button>
</div>
{/if} {/if}
{/if} {/if}
<section class="mt-8"> <section class="mt-8">
<h2 class="text-xl font-bold mb-4">Logs</h2> <h2 class="text-xl font-bold mb-4">Logs</h2>
<form on:submit|preventDefault={handleAddLog} class="mb-4"> <!--- --
<input <form on:submit|preventDefault={handleAddLog} class="mb-4 space-y-4">
type="text" <div>
bind:value={newLogMessage} <label for="logMessage" class="block text-sm font-medium">Log Message</label>
placeholder="Enter new log message" <input
class="w-full p-2 border rounded mb-2" id="logMessage"
required type="text"
/> bind:value={newLog.message}
placeholder="Enter new log message"
class="w-full p-2 border rounded"
required
/>
</div>
<div>
<label for="errorCode" class="block text-sm font-medium">Error Code</label>
<input
id="errorCode"
type="number"
bind:value={newLog.error_code}
placeholder="Enter error code (0 for no error)"
class="w-full p-2 border rounded"
required
/>
</div>
<div>
<label for="errorMessage" class="block text-sm font-medium">Error Message</label>
<textarea
id="errorMessage"
type="text"
bind:value={newLog.error_message}
placeholder="Enter error message (optional)"
class="w-full p-2 border rounded"
>
</textarea>
</div>
<button type="submit" class="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600"> <button type="submit" class="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600">
Add Log Add Log
</button> </button>
</form> </form>
-->
<ul class="space-y-4"> <ul class="space-y-4">
{#each logs as log (log.id)} {#each logs as log (log.id)}
<li class="p-4 border rounded bg-gray-50 flex justify-between items-center"> <li
<div> class="p-2 rounded flex justify-between items-center border-s-slate-400 border-1"
<p class="text-sm text-gray-700">{log.message}</p> class:bg-red-100={log.error_code !== 0}
<p class="text-xs text-gray-500">{log.created_at}</p> class:bg-gray-50={log.error_code === 0}
</div> >
<button class="p-2 w-full" on:click={() => openLogPopup(log)}>
<div class="z">
<p class="text-sm text-left">
{log.error_code !== 0
? log.error_message.split('\n')[0]
: log.message.split('\n')[0]}
</p>
<p class="text-xs text-gray-500 text-left">{log.created_at}</p>
</div>
</button>
<button <button
class="px-2 py-1 bg-red-500 text-white rounded hover:bg-red-600" class="px-2 py-1 bg-red-500 text-white rounded hover:bg-red-600 block"
on:click={() => handleDeleteLog(log.id)} on:click|stopPropagation={() => handleDeleteLog(log.id)}
> >
Delete Delete
</button> </button>
</li> </li>
{/each} {/each}
</ul> </ul>
{#if selectedLog}
<div
class="fixed inset-0 bg-opacity-30 backdrop-blur-sm flex items-center justify-center z-50"
>
<div class="bg-white p-6 rounded shadow-lg w-3/4 max-w-2xl">
<h3 class="text-lg font-bold mb-4">Log Details</h3>
<div class="mb-4">
<p class="font-semibold">Message:</p>
<pre
class="whitespace-pre-wrap bg-gray-100 p-2 rounded border">{selectedLog.message}</pre>
</div>
<div class="mb-4">
<p class="font-semibold">Error Code:</p>
<pre
class="whitespace-pre-wrap bg-gray-100 p-2 rounded border">{selectedLog.error_code}</pre>
</div>
<div class="mb-4">
<p class="font-semibold">Error Message:</p>
<pre
class="whitespace-pre-wrap bg-gray-100 p-2 rounded border">{selectedLog.error_message ||
'N/A'}</pre>
</div>
<div class="mb-4">
<p class="font-semibold">Created At:</p>
<pre
class="whitespace-pre-wrap bg-gray-100 p-2 rounded border">{selectedLog.created_at}</pre>
</div>
<button
class="mt-4 px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
on:click={closeLogPopup}
>
Close
</button>
</div>
</div>
{/if}
</section> </section>
</main> </main>