feat(frontend): streamline job creation actions
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { Plus, RotateCcw } from "lucide-react";
|
import { Eye, Plus, RotateCcw } from "lucide-react";
|
||||||
import { FormEvent, useCallback, useEffect, useMemo, useState } from "react";
|
import { FormEvent, useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
@@ -93,6 +93,7 @@ export function JobsPage() {
|
|||||||
max_attempts: nextMaxAttempts
|
max_attempts: nextMaxAttempts
|
||||||
});
|
});
|
||||||
await refresh();
|
await refresh();
|
||||||
|
setCreateOpen(false);
|
||||||
toast.success("Demo job created.");
|
toast.success("Demo job created.");
|
||||||
} catch (caught) {
|
} catch (caught) {
|
||||||
toast.error(caught instanceof Error ? caught.message : String(caught));
|
toast.error(caught instanceof Error ? caught.message : String(caught));
|
||||||
@@ -115,6 +116,7 @@ export function JobsPage() {
|
|||||||
try {
|
try {
|
||||||
await Promise.all(batch.map(([type, nextPriority]) => api.createJob({ type, payload: demoPayload(type), priority: nextPriority, max_attempts: 3 })));
|
await Promise.all(batch.map(([type, nextPriority]) => api.createJob({ type, payload: demoPayload(type), priority: nextPriority, max_attempts: 3 })));
|
||||||
await refresh();
|
await refresh();
|
||||||
|
setCreateOpen(false);
|
||||||
toast.success("Batch created.");
|
toast.success("Batch created.");
|
||||||
} catch (caught) {
|
} catch (caught) {
|
||||||
toast.error(caught instanceof Error ? caught.message : String(caught));
|
toast.error(caught instanceof Error ? caught.message : String(caught));
|
||||||
@@ -135,29 +137,6 @@ export function JobsPage() {
|
|||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section className="panel">
|
|
||||||
<div className="row-actions">
|
|
||||||
<button type="button" onClick={() => void createDemo("demo.success", 60)}>
|
|
||||||
Success
|
|
||||||
</button>
|
|
||||||
<button type="button" onClick={() => void createDemo("demo.fail", 50, 2)}>
|
|
||||||
Failing
|
|
||||||
</button>
|
|
||||||
<button type="button" onClick={() => void createDemo("demo.slow", 80)}>
|
|
||||||
Slow
|
|
||||||
</button>
|
|
||||||
<button type="button" onClick={() => void createDemo("demo.timeout", 70, 2)}>
|
|
||||||
Timeout
|
|
||||||
</button>
|
|
||||||
<button type="button" onClick={() => void createDemo("demo.flaky", 75, 3)}>
|
|
||||||
Flaky Retry
|
|
||||||
</button>
|
|
||||||
<button type="button" onClick={() => void createBatch()}>
|
|
||||||
<RotateCcw size={16} /> 10 Mixed
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="panel">
|
<section className="panel">
|
||||||
<div className="toolbar">
|
<div className="toolbar">
|
||||||
<SelectField value={statusFilter} options={statusOptions} onChange={(value) => setStatusFilter(value as JobStatus | "all")} />
|
<SelectField value={statusFilter} options={statusOptions} onChange={(value) => setStatusFilter(value as JobStatus | "all")} />
|
||||||
@@ -182,7 +161,9 @@ export function JobsPage() {
|
|||||||
<td>
|
<td>
|
||||||
<StatusBadge status={job.status} />
|
<StatusBadge status={job.status} />
|
||||||
</td>
|
</td>
|
||||||
<td>{job.type}</td>
|
<td>
|
||||||
|
<code className="inline-code">{job.type}</code>
|
||||||
|
</td>
|
||||||
<td>{job.priority}</td>
|
<td>{job.priority}</td>
|
||||||
<td>
|
<td>
|
||||||
{job.attempts}/{job.max_attempts}
|
{job.attempts}/{job.max_attempts}
|
||||||
@@ -192,8 +173,8 @@ export function JobsPage() {
|
|||||||
</td>
|
</td>
|
||||||
<td>{job.locked_by ?? "-"}</td>
|
<td>{job.locked_by ?? "-"}</td>
|
||||||
<td>
|
<td>
|
||||||
<Link className="secondary-button" to={`/jobs/${job.id}`}>
|
<Link className="icon-button" aria-label={`view ${job.type}`} title="View job" to={`/jobs/${job.id}`}>
|
||||||
Detail
|
<Eye size={18} />
|
||||||
</Link>
|
</Link>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -207,6 +188,26 @@ export function JobsPage() {
|
|||||||
{createOpen && (
|
{createOpen && (
|
||||||
<Modal title="Create Job" onClose={() => setCreateOpen(false)}>
|
<Modal title="Create Job" onClose={() => setCreateOpen(false)}>
|
||||||
<form className="modal-form" onSubmit={createJob}>
|
<form className="modal-form" onSubmit={createJob}>
|
||||||
|
<div className="preset-grid">
|
||||||
|
<button className="secondary-button" type="button" onClick={() => setType("demo.success")}>
|
||||||
|
Success
|
||||||
|
</button>
|
||||||
|
<button className="secondary-button" type="button" onClick={() => setType("demo.fail")}>
|
||||||
|
Failing
|
||||||
|
</button>
|
||||||
|
<button className="secondary-button" type="button" onClick={() => setType("demo.slow")}>
|
||||||
|
Slow
|
||||||
|
</button>
|
||||||
|
<button className="secondary-button" type="button" onClick={() => setType("demo.timeout")}>
|
||||||
|
Timeout
|
||||||
|
</button>
|
||||||
|
<button className="secondary-button" type="button" onClick={() => setType("demo.flaky")}>
|
||||||
|
Flaky Retry
|
||||||
|
</button>
|
||||||
|
<button className="secondary-button" type="button" onClick={() => void createBatch()}>
|
||||||
|
<RotateCcw size={16} /> 10 Mixed
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<SelectField label="Job type" value={jobType} options={jobTypeOptions} onChange={setType} />
|
<SelectField label="Job type" value={jobType} options={jobTypeOptions} onChange={setType} />
|
||||||
<JsonField label="Payload JSON" value={payload} onChange={setPayload} />
|
<JsonField label="Payload JSON" value={payload} onChange={setPayload} />
|
||||||
<label>
|
<label>
|
||||||
@@ -226,6 +227,9 @@ export function JobsPage() {
|
|||||||
<input value={idempotencyKey} onChange={(event) => setIdempotencyKey(event.target.value)} />
|
<input value={idempotencyKey} onChange={(event) => setIdempotencyKey(event.target.value)} />
|
||||||
</label>
|
</label>
|
||||||
<div className="modal-actions">
|
<div className="modal-actions">
|
||||||
|
<button className="secondary-button" type="button" onClick={() => void createDemo(jobType, priority, maxAttempts)}>
|
||||||
|
Quick Create
|
||||||
|
</button>
|
||||||
<button className="secondary-button" type="button" onClick={() => setCreateOpen(false)}>
|
<button className="secondary-button" type="button" onClick={() => setCreateOpen(false)}>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -992,6 +992,12 @@ code {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inline-code {
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 4px 7px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.action-grid {
|
.action-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
@@ -1004,6 +1010,12 @@ code {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.preset-grid {
|
||||||
|
display: grid;
|
||||||
|
gap: 8px;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
.checkbox-row {
|
.checkbox-row {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -1164,6 +1176,7 @@ code {
|
|||||||
|
|
||||||
.modal-backdrop {
|
.modal-backdrop {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
background: rgb(0 0 0 / 48%);
|
background: rgb(0 0 0 / 48%);
|
||||||
display: flex;
|
display: flex;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user