Merge pull request 'feat: run jobs on instance creation when run_on_create is enabled' (#54) from feat/jobs-system into dev
Reviewed-on: #54
This commit was merged in pull request #54.
This commit is contained in:
17
js/ui.js
17
js/ui.js
@@ -463,6 +463,13 @@ async function loadJobDetail(jobId) {
|
|||||||
<label class="form-label" for="job-schedule">Poll interval (minutes)</label>
|
<label class="form-label" for="job-schedule">Poll interval (minutes)</label>
|
||||||
<input class="form-input" id="job-schedule" type="number" min="1" value="${job.schedule}" style="max-width:100px">
|
<input class="form-input" id="job-schedule" type="number" min="1" value="${job.schedule}" style="max-width:100px">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" style="display:flex;align-items:center;gap:8px;cursor:pointer">
|
||||||
|
<input type="checkbox" id="job-run-on-create" ${cfg.run_on_create ? 'checked' : ''}
|
||||||
|
style="accent-color:var(--accent);width:13px;height:13px">
|
||||||
|
Run on instance creation
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
${_renderJobConfigFields(job.key, cfg)}
|
${_renderJobConfigFields(job.key, cfg)}
|
||||||
<div class="job-actions">
|
<div class="job-actions">
|
||||||
<button class="btn btn-secondary" onclick="saveJobDetail(${job.id})">Save</button>
|
<button class="btn btn-secondary" onclick="saveJobDetail(${job.id})">Save</button>
|
||||||
@@ -521,10 +528,12 @@ async function saveJobDetail(jobId) {
|
|||||||
const apiKey = document.getElementById('job-cfg-api-key');
|
const apiKey = document.getElementById('job-cfg-api-key');
|
||||||
const apiUrl = document.getElementById('job-cfg-api-url');
|
const apiUrl = document.getElementById('job-cfg-api-url');
|
||||||
const apiToken = document.getElementById('job-cfg-api-token');
|
const apiToken = document.getElementById('job-cfg-api-token');
|
||||||
if (tailnet) cfg.tailnet = tailnet.value.trim();
|
if (tailnet) cfg.tailnet = tailnet.value.trim();
|
||||||
if (apiKey) cfg.api_key = apiKey.value;
|
if (apiKey) cfg.api_key = apiKey.value;
|
||||||
if (apiUrl) cfg.api_url = apiUrl.value.trim();
|
if (apiUrl) cfg.api_url = apiUrl.value.trim();
|
||||||
if (apiToken) cfg.api_token = apiToken.value;
|
if (apiToken) cfg.api_token = apiToken.value;
|
||||||
|
const runOnCreate = document.getElementById('job-run-on-create');
|
||||||
|
if (runOnCreate) cfg.run_on_create = runOnCreate.checked;
|
||||||
const res = await fetch(`/api/jobs/${jobId}`, {
|
const res = await fetch(`/api/jobs/${jobId}`, {
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
|||||||
@@ -129,6 +129,13 @@ export async function runJob(jobId) {
|
|||||||
|
|
||||||
const _intervals = new Map();
|
const _intervals = new Map();
|
||||||
|
|
||||||
|
export async function runJobsOnCreate() {
|
||||||
|
for (const job of getJobs()) {
|
||||||
|
const cfg = JSON.parse(job.config || '{}');
|
||||||
|
if (cfg.run_on_create) runJob(job.id).catch(e => console.error(`runJobsOnCreate job ${job.id}:`, e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function restartJobs() {
|
export function restartJobs() {
|
||||||
for (const iv of _intervals.values()) clearInterval(iv);
|
for (const iv of _intervals.values()) clearInterval(iv);
|
||||||
_intervals.clear();
|
_intervals.clear();
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
getConfig, setConfig, getJobs, getJob, updateJob, getJobRuns,
|
getConfig, setConfig, getJobs, getJob, updateJob, getJobRuns,
|
||||||
getAllJobs, getAllJobRuns, importJobs,
|
getAllJobs, getAllJobRuns, importJobs,
|
||||||
} from './db.js';
|
} from './db.js';
|
||||||
import { runJob, restartJobs } from './jobs.js';
|
import { runJob, restartJobs, runJobsOnCreate } from './jobs.js';
|
||||||
|
|
||||||
export const router = Router();
|
export const router = Router();
|
||||||
|
|
||||||
@@ -102,6 +102,7 @@ router.post('/instances', (req, res) => {
|
|||||||
createInstance(data);
|
createInstance(data);
|
||||||
const created = getInstance(data.vmid);
|
const created = getInstance(data.vmid);
|
||||||
res.status(201).json(created);
|
res.status(201).json(created);
|
||||||
|
runJobsOnCreate().catch(() => {});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
handleDbError('POST /api/instances', e, res);
|
handleDbError('POST /api/instances', e, res);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -623,6 +623,25 @@ describe('POST /api/jobs/:id/run', () => {
|
|||||||
expect(res.status).toBe(500)
|
expect(res.status).toBe(500)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('run_on_create: triggers matching jobs when an instance is created', async () => {
|
||||||
|
createJob({ ...testJob, config: JSON.stringify({ api_key: 'k', tailnet: 't', run_on_create: true }) })
|
||||||
|
const id = (await request(app).get('/api/jobs')).body[0].id
|
||||||
|
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({ ok: true, json: async () => ({ devices: [] }) }))
|
||||||
|
await request(app).post('/api/instances').send(base)
|
||||||
|
await new Promise(r => setImmediate(r))
|
||||||
|
const detail = await request(app).get(`/api/jobs/${id}`)
|
||||||
|
expect(detail.body.runs).toHaveLength(1)
|
||||||
|
expect(detail.body.runs[0].status).toBe('success')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('run_on_create: does not trigger jobs without the flag', async () => {
|
||||||
|
createJob(testJob)
|
||||||
|
const id = (await request(app).get('/api/jobs')).body[0].id
|
||||||
|
await request(app).post('/api/instances').send(base)
|
||||||
|
await new Promise(r => setImmediate(r))
|
||||||
|
expect((await request(app).get(`/api/jobs/${id}`)).body.runs).toHaveLength(0)
|
||||||
|
})
|
||||||
|
|
||||||
it('semaphore_sync: parses ansible inventory and updates instances', async () => {
|
it('semaphore_sync: parses ansible inventory and updates instances', async () => {
|
||||||
const semaphoreJob = {
|
const semaphoreJob = {
|
||||||
key: 'semaphore_sync', name: 'Semaphore Sync', description: 'test',
|
key: 'semaphore_sync', name: 'Semaphore Sync', description: 'test',
|
||||||
|
|||||||
Reference in New Issue
Block a user