plum-e2e 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.prettierrc +15 -15
- package/README.md +5 -5
- package/backend/_scaffold/pages/LoginPage.js +22 -22
- package/backend/_scaffold/step_definitions/LoginSteps.js +9 -9
- package/backend/_scaffold/utils/constants.js +3 -3
- package/backend/_scaffold/utils/hooks.js +65 -65
- package/backend/_scaffold/utils/utils.js +10 -10
- package/backend/app.js +37 -37
- package/backend/config/scripts/create-settings.js +60 -60
- package/backend/config/scripts/generate-report.js +135 -135
- package/backend/config/scripts/run-tests.js +37 -37
- package/backend/cucumber.json +6 -6
- package/backend/package.json +29 -29
- package/backend/playwright.config.js +97 -97
- package/backend/routes/cron.routes.js +127 -127
- package/backend/routes/reports.routes.js +42 -42
- package/backend/routes/schedules.routes.js +32 -32
- package/backend/routes/tests.routes.js +33 -33
- package/backend/server.js +39 -39
- package/backend/services/cronService.js +127 -127
- package/backend/services/envService.js +43 -43
- package/backend/services/reportService.js +50 -50
- package/backend/services/scheduleService.js +34 -34
- package/backend/services/testService.js +70 -70
- package/backend/websockets/socketHandler.js +46 -46
- package/bin/plum.js +198 -198
- package/docker-compose.yml +41 -41
- package/frontend/jsconfig.json +13 -13
- package/frontend/package.json +26 -26
- package/frontend/postcss.config.js +23 -23
- package/frontend/src/app.css +35 -35
- package/frontend/src/app.html +28 -28
- package/frontend/src/lib/index.js +18 -18
- package/frontend/src/routes/+layout.svelte +34 -34
- package/frontend/src/routes/+page.svelte +188 -188
- package/frontend/src/routes/components/Navigation.svelte +53 -53
- package/frontend/src/routes/reports/+page.svelte +160 -160
- package/frontend/src/routes/scheduled-tests/+page.svelte +363 -363
- package/frontend/svelte.config.js +30 -30
- package/frontend/tailwind.config.js +44 -44
- package/frontend/vite.config.js +23 -23
- package/license-config.json +37 -37
- package/package.json +32 -28
|
@@ -1,363 +1,363 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
* This file is part of Plum.
|
|
3
|
-
*
|
|
4
|
-
* Plum is free software: you can redistribute it and/or modify
|
|
5
|
-
* it under the terms of the GNU General Public License as published by
|
|
6
|
-
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
-
* (at your option) any later version.
|
|
8
|
-
*
|
|
9
|
-
* Plum is distributed in the hope that it will be useful,
|
|
10
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
-
* GNU General Public License for more details.
|
|
13
|
-
*
|
|
14
|
-
* You should have received a copy of the GNU General Public License
|
|
15
|
-
* along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
|
-
-->
|
|
17
|
-
|
|
18
|
-
<script>
|
|
19
|
-
import { onMount } from 'svelte';
|
|
20
|
-
|
|
21
|
-
let scheduledTests = [];
|
|
22
|
-
let schedules = [];
|
|
23
|
-
let cronExpression = '';
|
|
24
|
-
let taskName = '';
|
|
25
|
-
let tags = '';
|
|
26
|
-
let successMessage = '';
|
|
27
|
-
let errorMessage = '';
|
|
28
|
-
let isModalOpen = false;
|
|
29
|
-
let isEditing = false; // To track if we are editing a cron job
|
|
30
|
-
let editTaskName = ''; // To store the task name of the cron job being edited
|
|
31
|
-
let isDeleting = false; // To track if the user is confirming deletion
|
|
32
|
-
let taskToDelete = ''; // The task name of the cron job being deleted
|
|
33
|
-
|
|
34
|
-
const cronRegex =
|
|
35
|
-
/^(\*|([0-5]?[0-9])) (\*|([01]?[0-9]|2[0-3])) (\*|([01]?[0-9]|3[01])) (\*|([1-9]|1[0-2])) (\*|[0-6])$/;
|
|
36
|
-
|
|
37
|
-
// Fetch schedules from the settings.json through backend API
|
|
38
|
-
async function fetchSchedules() {
|
|
39
|
-
try {
|
|
40
|
-
const res = await fetch('http://localhost:3001/schedules');
|
|
41
|
-
const data = await res.json();
|
|
42
|
-
|
|
43
|
-
if (data.schedules) {
|
|
44
|
-
schedules = data.schedules;
|
|
45
|
-
}
|
|
46
|
-
} catch (error) {
|
|
47
|
-
errorMessage = 'Error fetching schedules.';
|
|
48
|
-
console.error('Error fetching schedules', error);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Fetch scheduled tests (cron jobs) from the backend API
|
|
53
|
-
async function fetchScheduledTests() {
|
|
54
|
-
try {
|
|
55
|
-
const res = await fetch('http://localhost:3001/cron-jobs');
|
|
56
|
-
const data = await res.json();
|
|
57
|
-
if (data.cronJobs) {
|
|
58
|
-
scheduledTests = data.cronJobs;
|
|
59
|
-
}
|
|
60
|
-
} catch (error) {
|
|
61
|
-
errorMessage = 'Error fetching scheduled tests.';
|
|
62
|
-
console.error('Error fetching scheduled tests:', error);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Add or edit a cron job via the API
|
|
67
|
-
async function saveCronJob() {
|
|
68
|
-
if (!cronExpression || !taskName || !tags) {
|
|
69
|
-
errorMessage = 'Please fill in all fields';
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Validate cron expression
|
|
74
|
-
if (!cronRegex.test(cronExpression)) {
|
|
75
|
-
errorMessage = 'Invalid cron expression. Please use a valid cron format.';
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Lowercase " OR " while keeping other tags intact
|
|
80
|
-
const formattedTags = tags.replace(/\sOR\s/gi, (match) => match.toLowerCase());
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
let res;
|
|
84
|
-
if (isEditing) {
|
|
85
|
-
// Edit cron job
|
|
86
|
-
res = await fetch(`http://localhost:3001/cron-jobs/${editTaskName}`, {
|
|
87
|
-
method: 'PUT',
|
|
88
|
-
headers: {
|
|
89
|
-
'Content-Type': 'application/json'
|
|
90
|
-
},
|
|
91
|
-
body: JSON.stringify({ cronExpression, taskName, tags: formattedTags })
|
|
92
|
-
});
|
|
93
|
-
} else {
|
|
94
|
-
// Add new cron job
|
|
95
|
-
res = await fetch('http://localhost:3001/cron-jobs', {
|
|
96
|
-
method: 'POST',
|
|
97
|
-
headers: {
|
|
98
|
-
'Content-Type': 'application/json'
|
|
99
|
-
},
|
|
100
|
-
body: JSON.stringify({ cronExpression, taskName, tags: formattedTags })
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const data = await res.json();
|
|
105
|
-
if (data.message) {
|
|
106
|
-
successMessage = `Cron job ${isEditing ? 'updated' : 'added'}: ${data.message}`;
|
|
107
|
-
fetchScheduledTests(); // Refresh the list
|
|
108
|
-
setTimeout(() => {
|
|
109
|
-
successMessage = ''; // Clear success message after 5 seconds
|
|
110
|
-
}, 5000);
|
|
111
|
-
clearModal();
|
|
112
|
-
} else {
|
|
113
|
-
errorMessage = 'Failed to save cron job';
|
|
114
|
-
}
|
|
115
|
-
} catch (error) {
|
|
116
|
-
errorMessage = 'Error saving cron job.';
|
|
117
|
-
console.error('Error saving cron job:', error);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Delete a cron job
|
|
122
|
-
async function deleteCronJob(taskName) {
|
|
123
|
-
try {
|
|
124
|
-
const res = await fetch(`http://localhost:3001/cron-jobs/${taskName}`, {
|
|
125
|
-
method: 'DELETE'
|
|
126
|
-
});
|
|
127
|
-
const data = await res.json();
|
|
128
|
-
if (data.message) {
|
|
129
|
-
successMessage = `Cron job deleted: ${data.message}`;
|
|
130
|
-
// Refresh the list
|
|
131
|
-
fetchScheduledTests();
|
|
132
|
-
setTimeout(() => {
|
|
133
|
-
successMessage = '';
|
|
134
|
-
}, 5000);
|
|
135
|
-
} else {
|
|
136
|
-
errorMessage = 'Failed to delete cron job';
|
|
137
|
-
setTimeout(() => {
|
|
138
|
-
errorMessage = '';
|
|
139
|
-
}, 5000);
|
|
140
|
-
}
|
|
141
|
-
} catch (error) {
|
|
142
|
-
errorMessage = 'Error deleting cron job.';
|
|
143
|
-
console.error('Error deleting cron job:', error);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Open modal to edit cron job
|
|
148
|
-
function openEditModal(cronJob) {
|
|
149
|
-
isEditing = true;
|
|
150
|
-
editTaskName = cronJob.taskName;
|
|
151
|
-
taskName = cronJob.taskName;
|
|
152
|
-
cronExpression = cronJob.cronExpression;
|
|
153
|
-
tags = cronJob.tags;
|
|
154
|
-
isModalOpen = true;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Reset modal values
|
|
158
|
-
function clearModal() {
|
|
159
|
-
isEditing = false;
|
|
160
|
-
editTaskName = '';
|
|
161
|
-
taskName = '';
|
|
162
|
-
cronExpression = '';
|
|
163
|
-
tags = '';
|
|
164
|
-
isModalOpen = false;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Open confirmation modal for deleting cron job
|
|
168
|
-
function openDeleteConfirmation(taskName) {
|
|
169
|
-
isDeleting = true;
|
|
170
|
-
taskToDelete = taskName;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Confirm deletion
|
|
174
|
-
function confirmDelete() {
|
|
175
|
-
deleteCronJob(taskToDelete);
|
|
176
|
-
isDeleting = false;
|
|
177
|
-
taskToDelete = '';
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Cancel deletion
|
|
181
|
-
function cancelDelete() {
|
|
182
|
-
isDeleting = false;
|
|
183
|
-
taskToDelete = '';
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Get human-readable label for cron expression
|
|
187
|
-
function getCronLabel(cronExpression) {
|
|
188
|
-
const match = schedules.find((option) => option.value === cronExpression);
|
|
189
|
-
return match ? match.label : cronExpression;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
onMount(() => {
|
|
193
|
-
fetchScheduledTests();
|
|
194
|
-
fetchSchedules(); // <-- also call this here
|
|
195
|
-
});
|
|
196
|
-
</script>
|
|
197
|
-
|
|
198
|
-
<!-- Add/Modify modal -->
|
|
199
|
-
<dialog id="my_modal_1" class="modal" class:modal-open={isModalOpen}>
|
|
200
|
-
<div class="modal-box">
|
|
201
|
-
<div class="modal-header">
|
|
202
|
-
<button class="btn btn-sm btn-circle z-10 absolute right-2 top-2" on:click={clearModal}>
|
|
203
|
-
✖
|
|
204
|
-
</button>
|
|
205
|
-
</div>
|
|
206
|
-
|
|
207
|
-
<h2 class="card-title mb-4">{isEditing ? 'Edit' : 'Add New'} Cron Job</h2>
|
|
208
|
-
|
|
209
|
-
<form on:submit|preventDefault={saveCronJob} class="space-y-4">
|
|
210
|
-
<div>
|
|
211
|
-
<div for="taskName" class="label">
|
|
212
|
-
<span class="label-text">Task Name</span>
|
|
213
|
-
<span class="label-text-alt"
|
|
214
|
-
>{isEditing
|
|
215
|
-
? 'Name is used as ID. This cannot be changed.'
|
|
216
|
-
: 'Use a meaningful name'}</span
|
|
217
|
-
>
|
|
218
|
-
</div>
|
|
219
|
-
<input
|
|
220
|
-
type="text"
|
|
221
|
-
id="taskName"
|
|
222
|
-
bind:value={taskName}
|
|
223
|
-
class="input input-bordered w-full"
|
|
224
|
-
placeholder="Task Name"
|
|
225
|
-
required
|
|
226
|
-
disabled={isEditing}
|
|
227
|
-
/>
|
|
228
|
-
</div>
|
|
229
|
-
<div>
|
|
230
|
-
<div for="cronExpression" class="label">
|
|
231
|
-
<span class="label-text">Cron Expression</span>
|
|
232
|
-
<span class="label-text-alt">You can use AI to construct your expression.</span>
|
|
233
|
-
</div>
|
|
234
|
-
<select
|
|
235
|
-
id="cronExpression"
|
|
236
|
-
bind:value={cronExpression}
|
|
237
|
-
class="input input-bordered w-full"
|
|
238
|
-
required
|
|
239
|
-
>
|
|
240
|
-
<option value="" disabled selected>Select Cron Expression</option>
|
|
241
|
-
{#each schedules as schedule}
|
|
242
|
-
<option value={schedule.value}>{schedule.label}</option>
|
|
243
|
-
{/each}
|
|
244
|
-
</select>
|
|
245
|
-
</div>
|
|
246
|
-
<div>
|
|
247
|
-
<div for="tags" class="label">
|
|
248
|
-
<span class="label-text">Tags</span>
|
|
249
|
-
<span class="label-text-alt">If using multiple tags, "@test-1 or @test-2".</span>
|
|
250
|
-
</div>
|
|
251
|
-
<input
|
|
252
|
-
type="text"
|
|
253
|
-
id="tags"
|
|
254
|
-
bind:value={tags}
|
|
255
|
-
class="input input-bordered w-full"
|
|
256
|
-
placeholder="Tags"
|
|
257
|
-
required
|
|
258
|
-
/>
|
|
259
|
-
</div>
|
|
260
|
-
<button type="submit" class="btn btn-success w-full">
|
|
261
|
-
{isEditing ? 'Edit' : 'Add'} Cron Job
|
|
262
|
-
</button>
|
|
263
|
-
</form>
|
|
264
|
-
</div>
|
|
265
|
-
</dialog>
|
|
266
|
-
|
|
267
|
-
<!-- Delete Confirmation Modal -->
|
|
268
|
-
<dialog id="delete_confirmation_modal" class="modal" class:modal-open={isDeleting}>
|
|
269
|
-
<div class="modal-box">
|
|
270
|
-
<h2 class="card-title mb-4">Are you sure you want to delete this cron job?</h2>
|
|
271
|
-
<div class="flex justify-center gap-4">
|
|
272
|
-
<button class="btn btn-error" on:click={confirmDelete}>Yes</button>
|
|
273
|
-
<button class="btn" on:click={cancelDelete}>No</button>
|
|
274
|
-
</div>
|
|
275
|
-
</div>
|
|
276
|
-
</dialog>
|
|
277
|
-
|
|
278
|
-
<div class="flex justify-center items-center w-full my-4">
|
|
279
|
-
<div class="card bg-base-300 rounded-box p-4">
|
|
280
|
-
<div class="card-body text-left">
|
|
281
|
-
<h2 class="card-title sticky top-0 bg-base-300 z-10">Scheduled Tests</h2>
|
|
282
|
-
{#if successMessage}
|
|
283
|
-
<div role="alert" class="alert alert-success z-0">
|
|
284
|
-
<svg
|
|
285
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
286
|
-
class="h-6 w-6 shrink-0 stroke-current"
|
|
287
|
-
fill="none"
|
|
288
|
-
viewBox="0 0 24 24"
|
|
289
|
-
>
|
|
290
|
-
<path
|
|
291
|
-
stroke-linecap="round"
|
|
292
|
-
stroke-linejoin="round"
|
|
293
|
-
stroke-width="2"
|
|
294
|
-
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
295
|
-
/>
|
|
296
|
-
</svg>
|
|
297
|
-
<span>{successMessage}</span>
|
|
298
|
-
</div>
|
|
299
|
-
{/if}
|
|
300
|
-
|
|
301
|
-
{#if errorMessage}
|
|
302
|
-
<div role="alert" class="alert alert-error">
|
|
303
|
-
<svg
|
|
304
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
305
|
-
class="h-6 w-6 shrink-0 stroke-current"
|
|
306
|
-
fill="none"
|
|
307
|
-
viewBox="0 0 24 24"
|
|
308
|
-
>
|
|
309
|
-
<path
|
|
310
|
-
stroke-linecap="round"
|
|
311
|
-
stroke-linejoin="round"
|
|
312
|
-
stroke-width="2"
|
|
313
|
-
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
314
|
-
/>
|
|
315
|
-
</svg>
|
|
316
|
-
<span>{errorMessage}</span>
|
|
317
|
-
</div>
|
|
318
|
-
{/if}
|
|
319
|
-
<div class="mt-4">
|
|
320
|
-
{#if scheduledTests.length > 0}
|
|
321
|
-
<div class="overflow-x-auto">
|
|
322
|
-
<table class="table table-zebra">
|
|
323
|
-
<thead>
|
|
324
|
-
<tr>
|
|
325
|
-
<th>Name</th>
|
|
326
|
-
<th>Frequency</th>
|
|
327
|
-
<th>Tags</th>
|
|
328
|
-
<th>Action</th>
|
|
329
|
-
</tr>
|
|
330
|
-
</thead>
|
|
331
|
-
<tbody>
|
|
332
|
-
{#each scheduledTests as scheduledTest}
|
|
333
|
-
<tr>
|
|
334
|
-
<td>{scheduledTest.taskName}</td>
|
|
335
|
-
<td>{getCronLabel(scheduledTest.cronExpression)}</td>
|
|
336
|
-
<td>{scheduledTest.tags}</td>
|
|
337
|
-
<td>
|
|
338
|
-
<button
|
|
339
|
-
class="btn btn-warning btn-sm"
|
|
340
|
-
on:click={() => openEditModal(scheduledTest)}
|
|
341
|
-
>
|
|
342
|
-
Edit
|
|
343
|
-
</button>
|
|
344
|
-
<button
|
|
345
|
-
class="btn btn-error btn-sm"
|
|
346
|
-
on:click={() => openDeleteConfirmation(scheduledTest.taskName)}
|
|
347
|
-
>
|
|
348
|
-
Delete
|
|
349
|
-
</button>
|
|
350
|
-
</td>
|
|
351
|
-
</tr>
|
|
352
|
-
{/each}
|
|
353
|
-
</tbody>
|
|
354
|
-
</table>
|
|
355
|
-
</div>
|
|
356
|
-
{:else}
|
|
357
|
-
<p>No Scheduled Tests Available.</p>
|
|
358
|
-
{/if}
|
|
359
|
-
</div>
|
|
360
|
-
<button class="btn mt-2" on:click={() => (isModalOpen = true)}>Add New Cron Job</button>
|
|
361
|
-
</div>
|
|
362
|
-
</div>
|
|
363
|
-
</div>
|
|
1
|
+
<!--
|
|
2
|
+
* This file is part of Plum.
|
|
3
|
+
*
|
|
4
|
+
* Plum is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU General Public License as published by
|
|
6
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* Plum is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU General Public License
|
|
15
|
+
* along with Plum. If not, see https://www.gnu.org/licenses/.
|
|
16
|
+
-->
|
|
17
|
+
|
|
18
|
+
<script>
|
|
19
|
+
import { onMount } from 'svelte';
|
|
20
|
+
|
|
21
|
+
let scheduledTests = [];
|
|
22
|
+
let schedules = [];
|
|
23
|
+
let cronExpression = '';
|
|
24
|
+
let taskName = '';
|
|
25
|
+
let tags = '';
|
|
26
|
+
let successMessage = '';
|
|
27
|
+
let errorMessage = '';
|
|
28
|
+
let isModalOpen = false;
|
|
29
|
+
let isEditing = false; // To track if we are editing a cron job
|
|
30
|
+
let editTaskName = ''; // To store the task name of the cron job being edited
|
|
31
|
+
let isDeleting = false; // To track if the user is confirming deletion
|
|
32
|
+
let taskToDelete = ''; // The task name of the cron job being deleted
|
|
33
|
+
|
|
34
|
+
const cronRegex =
|
|
35
|
+
/^(\*|([0-5]?[0-9])) (\*|([01]?[0-9]|2[0-3])) (\*|([01]?[0-9]|3[01])) (\*|([1-9]|1[0-2])) (\*|[0-6])$/;
|
|
36
|
+
|
|
37
|
+
// Fetch schedules from the settings.json through backend API
|
|
38
|
+
async function fetchSchedules() {
|
|
39
|
+
try {
|
|
40
|
+
const res = await fetch('http://localhost:3001/schedules');
|
|
41
|
+
const data = await res.json();
|
|
42
|
+
|
|
43
|
+
if (data.schedules) {
|
|
44
|
+
schedules = data.schedules;
|
|
45
|
+
}
|
|
46
|
+
} catch (error) {
|
|
47
|
+
errorMessage = 'Error fetching schedules.';
|
|
48
|
+
console.error('Error fetching schedules', error);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Fetch scheduled tests (cron jobs) from the backend API
|
|
53
|
+
async function fetchScheduledTests() {
|
|
54
|
+
try {
|
|
55
|
+
const res = await fetch('http://localhost:3001/cron-jobs');
|
|
56
|
+
const data = await res.json();
|
|
57
|
+
if (data.cronJobs) {
|
|
58
|
+
scheduledTests = data.cronJobs;
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
errorMessage = 'Error fetching scheduled tests.';
|
|
62
|
+
console.error('Error fetching scheduled tests:', error);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Add or edit a cron job via the API
|
|
67
|
+
async function saveCronJob() {
|
|
68
|
+
if (!cronExpression || !taskName || !tags) {
|
|
69
|
+
errorMessage = 'Please fill in all fields';
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Validate cron expression
|
|
74
|
+
if (!cronRegex.test(cronExpression)) {
|
|
75
|
+
errorMessage = 'Invalid cron expression. Please use a valid cron format.';
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Lowercase " OR " while keeping other tags intact
|
|
80
|
+
const formattedTags = tags.replace(/\sOR\s/gi, (match) => match.toLowerCase());
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
let res;
|
|
84
|
+
if (isEditing) {
|
|
85
|
+
// Edit cron job
|
|
86
|
+
res = await fetch(`http://localhost:3001/cron-jobs/${editTaskName}`, {
|
|
87
|
+
method: 'PUT',
|
|
88
|
+
headers: {
|
|
89
|
+
'Content-Type': 'application/json'
|
|
90
|
+
},
|
|
91
|
+
body: JSON.stringify({ cronExpression, taskName, tags: formattedTags })
|
|
92
|
+
});
|
|
93
|
+
} else {
|
|
94
|
+
// Add new cron job
|
|
95
|
+
res = await fetch('http://localhost:3001/cron-jobs', {
|
|
96
|
+
method: 'POST',
|
|
97
|
+
headers: {
|
|
98
|
+
'Content-Type': 'application/json'
|
|
99
|
+
},
|
|
100
|
+
body: JSON.stringify({ cronExpression, taskName, tags: formattedTags })
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const data = await res.json();
|
|
105
|
+
if (data.message) {
|
|
106
|
+
successMessage = `Cron job ${isEditing ? 'updated' : 'added'}: ${data.message}`;
|
|
107
|
+
fetchScheduledTests(); // Refresh the list
|
|
108
|
+
setTimeout(() => {
|
|
109
|
+
successMessage = ''; // Clear success message after 5 seconds
|
|
110
|
+
}, 5000);
|
|
111
|
+
clearModal();
|
|
112
|
+
} else {
|
|
113
|
+
errorMessage = 'Failed to save cron job';
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
errorMessage = 'Error saving cron job.';
|
|
117
|
+
console.error('Error saving cron job:', error);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Delete a cron job
|
|
122
|
+
async function deleteCronJob(taskName) {
|
|
123
|
+
try {
|
|
124
|
+
const res = await fetch(`http://localhost:3001/cron-jobs/${taskName}`, {
|
|
125
|
+
method: 'DELETE'
|
|
126
|
+
});
|
|
127
|
+
const data = await res.json();
|
|
128
|
+
if (data.message) {
|
|
129
|
+
successMessage = `Cron job deleted: ${data.message}`;
|
|
130
|
+
// Refresh the list
|
|
131
|
+
fetchScheduledTests();
|
|
132
|
+
setTimeout(() => {
|
|
133
|
+
successMessage = '';
|
|
134
|
+
}, 5000);
|
|
135
|
+
} else {
|
|
136
|
+
errorMessage = 'Failed to delete cron job';
|
|
137
|
+
setTimeout(() => {
|
|
138
|
+
errorMessage = '';
|
|
139
|
+
}, 5000);
|
|
140
|
+
}
|
|
141
|
+
} catch (error) {
|
|
142
|
+
errorMessage = 'Error deleting cron job.';
|
|
143
|
+
console.error('Error deleting cron job:', error);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Open modal to edit cron job
|
|
148
|
+
function openEditModal(cronJob) {
|
|
149
|
+
isEditing = true;
|
|
150
|
+
editTaskName = cronJob.taskName;
|
|
151
|
+
taskName = cronJob.taskName;
|
|
152
|
+
cronExpression = cronJob.cronExpression;
|
|
153
|
+
tags = cronJob.tags;
|
|
154
|
+
isModalOpen = true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Reset modal values
|
|
158
|
+
function clearModal() {
|
|
159
|
+
isEditing = false;
|
|
160
|
+
editTaskName = '';
|
|
161
|
+
taskName = '';
|
|
162
|
+
cronExpression = '';
|
|
163
|
+
tags = '';
|
|
164
|
+
isModalOpen = false;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Open confirmation modal for deleting cron job
|
|
168
|
+
function openDeleteConfirmation(taskName) {
|
|
169
|
+
isDeleting = true;
|
|
170
|
+
taskToDelete = taskName;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Confirm deletion
|
|
174
|
+
function confirmDelete() {
|
|
175
|
+
deleteCronJob(taskToDelete);
|
|
176
|
+
isDeleting = false;
|
|
177
|
+
taskToDelete = '';
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Cancel deletion
|
|
181
|
+
function cancelDelete() {
|
|
182
|
+
isDeleting = false;
|
|
183
|
+
taskToDelete = '';
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Get human-readable label for cron expression
|
|
187
|
+
function getCronLabel(cronExpression) {
|
|
188
|
+
const match = schedules.find((option) => option.value === cronExpression);
|
|
189
|
+
return match ? match.label : cronExpression;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
onMount(() => {
|
|
193
|
+
fetchScheduledTests();
|
|
194
|
+
fetchSchedules(); // <-- also call this here
|
|
195
|
+
});
|
|
196
|
+
</script>
|
|
197
|
+
|
|
198
|
+
<!-- Add/Modify modal -->
|
|
199
|
+
<dialog id="my_modal_1" class="modal" class:modal-open={isModalOpen}>
|
|
200
|
+
<div class="modal-box">
|
|
201
|
+
<div class="modal-header">
|
|
202
|
+
<button class="btn btn-sm btn-circle z-10 absolute right-2 top-2" on:click={clearModal}>
|
|
203
|
+
✖
|
|
204
|
+
</button>
|
|
205
|
+
</div>
|
|
206
|
+
|
|
207
|
+
<h2 class="card-title mb-4">{isEditing ? 'Edit' : 'Add New'} Cron Job</h2>
|
|
208
|
+
|
|
209
|
+
<form on:submit|preventDefault={saveCronJob} class="space-y-4">
|
|
210
|
+
<div>
|
|
211
|
+
<div for="taskName" class="label">
|
|
212
|
+
<span class="label-text">Task Name</span>
|
|
213
|
+
<span class="label-text-alt"
|
|
214
|
+
>{isEditing
|
|
215
|
+
? 'Name is used as ID. This cannot be changed.'
|
|
216
|
+
: 'Use a meaningful name'}</span
|
|
217
|
+
>
|
|
218
|
+
</div>
|
|
219
|
+
<input
|
|
220
|
+
type="text"
|
|
221
|
+
id="taskName"
|
|
222
|
+
bind:value={taskName}
|
|
223
|
+
class="input input-bordered w-full"
|
|
224
|
+
placeholder="Task Name"
|
|
225
|
+
required
|
|
226
|
+
disabled={isEditing}
|
|
227
|
+
/>
|
|
228
|
+
</div>
|
|
229
|
+
<div>
|
|
230
|
+
<div for="cronExpression" class="label">
|
|
231
|
+
<span class="label-text">Cron Expression</span>
|
|
232
|
+
<span class="label-text-alt">You can use AI to construct your expression.</span>
|
|
233
|
+
</div>
|
|
234
|
+
<select
|
|
235
|
+
id="cronExpression"
|
|
236
|
+
bind:value={cronExpression}
|
|
237
|
+
class="input input-bordered w-full"
|
|
238
|
+
required
|
|
239
|
+
>
|
|
240
|
+
<option value="" disabled selected>Select Cron Expression</option>
|
|
241
|
+
{#each schedules as schedule}
|
|
242
|
+
<option value={schedule.value}>{schedule.label}</option>
|
|
243
|
+
{/each}
|
|
244
|
+
</select>
|
|
245
|
+
</div>
|
|
246
|
+
<div>
|
|
247
|
+
<div for="tags" class="label">
|
|
248
|
+
<span class="label-text">Tags</span>
|
|
249
|
+
<span class="label-text-alt">If using multiple tags, "@test-1 or @test-2".</span>
|
|
250
|
+
</div>
|
|
251
|
+
<input
|
|
252
|
+
type="text"
|
|
253
|
+
id="tags"
|
|
254
|
+
bind:value={tags}
|
|
255
|
+
class="input input-bordered w-full"
|
|
256
|
+
placeholder="Tags"
|
|
257
|
+
required
|
|
258
|
+
/>
|
|
259
|
+
</div>
|
|
260
|
+
<button type="submit" class="btn btn-success w-full">
|
|
261
|
+
{isEditing ? 'Edit' : 'Add'} Cron Job
|
|
262
|
+
</button>
|
|
263
|
+
</form>
|
|
264
|
+
</div>
|
|
265
|
+
</dialog>
|
|
266
|
+
|
|
267
|
+
<!-- Delete Confirmation Modal -->
|
|
268
|
+
<dialog id="delete_confirmation_modal" class="modal" class:modal-open={isDeleting}>
|
|
269
|
+
<div class="modal-box">
|
|
270
|
+
<h2 class="card-title mb-4">Are you sure you want to delete this cron job?</h2>
|
|
271
|
+
<div class="flex justify-center gap-4">
|
|
272
|
+
<button class="btn btn-error" on:click={confirmDelete}>Yes</button>
|
|
273
|
+
<button class="btn" on:click={cancelDelete}>No</button>
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
</dialog>
|
|
277
|
+
|
|
278
|
+
<div class="flex justify-center items-center w-full my-4">
|
|
279
|
+
<div class="card bg-base-300 rounded-box p-4">
|
|
280
|
+
<div class="card-body text-left">
|
|
281
|
+
<h2 class="card-title sticky top-0 bg-base-300 z-10">Scheduled Tests</h2>
|
|
282
|
+
{#if successMessage}
|
|
283
|
+
<div role="alert" class="alert alert-success z-0">
|
|
284
|
+
<svg
|
|
285
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
286
|
+
class="h-6 w-6 shrink-0 stroke-current"
|
|
287
|
+
fill="none"
|
|
288
|
+
viewBox="0 0 24 24"
|
|
289
|
+
>
|
|
290
|
+
<path
|
|
291
|
+
stroke-linecap="round"
|
|
292
|
+
stroke-linejoin="round"
|
|
293
|
+
stroke-width="2"
|
|
294
|
+
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
295
|
+
/>
|
|
296
|
+
</svg>
|
|
297
|
+
<span>{successMessage}</span>
|
|
298
|
+
</div>
|
|
299
|
+
{/if}
|
|
300
|
+
|
|
301
|
+
{#if errorMessage}
|
|
302
|
+
<div role="alert" class="alert alert-error">
|
|
303
|
+
<svg
|
|
304
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
305
|
+
class="h-6 w-6 shrink-0 stroke-current"
|
|
306
|
+
fill="none"
|
|
307
|
+
viewBox="0 0 24 24"
|
|
308
|
+
>
|
|
309
|
+
<path
|
|
310
|
+
stroke-linecap="round"
|
|
311
|
+
stroke-linejoin="round"
|
|
312
|
+
stroke-width="2"
|
|
313
|
+
d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
314
|
+
/>
|
|
315
|
+
</svg>
|
|
316
|
+
<span>{errorMessage}</span>
|
|
317
|
+
</div>
|
|
318
|
+
{/if}
|
|
319
|
+
<div class="mt-4">
|
|
320
|
+
{#if scheduledTests.length > 0}
|
|
321
|
+
<div class="overflow-x-auto">
|
|
322
|
+
<table class="table table-zebra">
|
|
323
|
+
<thead>
|
|
324
|
+
<tr>
|
|
325
|
+
<th>Name</th>
|
|
326
|
+
<th>Frequency</th>
|
|
327
|
+
<th>Tags</th>
|
|
328
|
+
<th>Action</th>
|
|
329
|
+
</tr>
|
|
330
|
+
</thead>
|
|
331
|
+
<tbody>
|
|
332
|
+
{#each scheduledTests as scheduledTest}
|
|
333
|
+
<tr>
|
|
334
|
+
<td>{scheduledTest.taskName}</td>
|
|
335
|
+
<td>{getCronLabel(scheduledTest.cronExpression)}</td>
|
|
336
|
+
<td>{scheduledTest.tags}</td>
|
|
337
|
+
<td>
|
|
338
|
+
<button
|
|
339
|
+
class="btn btn-warning btn-sm"
|
|
340
|
+
on:click={() => openEditModal(scheduledTest)}
|
|
341
|
+
>
|
|
342
|
+
Edit
|
|
343
|
+
</button>
|
|
344
|
+
<button
|
|
345
|
+
class="btn btn-error btn-sm"
|
|
346
|
+
on:click={() => openDeleteConfirmation(scheduledTest.taskName)}
|
|
347
|
+
>
|
|
348
|
+
Delete
|
|
349
|
+
</button>
|
|
350
|
+
</td>
|
|
351
|
+
</tr>
|
|
352
|
+
{/each}
|
|
353
|
+
</tbody>
|
|
354
|
+
</table>
|
|
355
|
+
</div>
|
|
356
|
+
{:else}
|
|
357
|
+
<p>No Scheduled Tests Available.</p>
|
|
358
|
+
{/if}
|
|
359
|
+
</div>
|
|
360
|
+
<button class="btn mt-2" on:click={() => (isModalOpen = true)}>Add New Cron Job</button>
|
|
361
|
+
</div>
|
|
362
|
+
</div>
|
|
363
|
+
</div>
|