Skip to content

Commit 93e4ed1

Browse files
authored
Add timezone selector for web UI (#212)
## Summary - add timezone selection to web UI with persistence - update time formatting helper - refresh tables when timezone changes - document timezone option ## Testing - `go vet ./...` *(fails: unknown field Command in ComposeJob)* - `go test ./...` *(fails: unknown field Command in ComposeJob)* ------ https://chatgpt.com/codex/tasks/task_b_68696ff6856483339a2ab5340832ebf1
2 parents 01e83e8 + 26afc33 commit 93e4ed1

2 files changed

Lines changed: 31 additions & 4 deletions

File tree

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Label your Docker containers and let this Go-powered daemon handle the schedule.
3636
the binary.
3737
- **Removed job history** keeps deregistered jobs in memory and shows them in the web UI.
3838
- **Enhanced web UI** allows editing and deleting jobs, shows job origin and type, displays each job's configuration and renders the scheduler configuration in a table with empty job sections hidden. Action buttons now use clear icons.
39+
- **Timezone selector** in the web UI lets you view timestamps in local, server or UTC time and remembers your choice.
3940

4041
This fork is based off of [mcuadros/ofelia](https://github.com/mcuadros/ofelia).
4142

@@ -93,7 +94,9 @@ the UI allows starting jobs manually, disabling or enabling them and creating,
9394
updating or deleting jobs of all types. A second table lists jobs removed from
9495
the configuration via `/api/jobs/removed`. The endpoint `/api/jobs/{name}/history`
9596
exposes past runs including stdout, stderr and any error messages while
96-
`/api/config` returns the active configuration as JSON.
97+
`/api/config` returns the active configuration as JSON. The UI includes a
98+
timezone selector so you can view times in your local zone, the server zone or
99+
UTC and have that preference saved locally.
97100
Creating `run` or `exec` jobs requires Ofelia to run with Docker access; the
98101
server rejects such requests if no Docker client is available.
99102

static/ui/index.html

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@
1212
</style>
1313
</head>
1414
<body>
15+
<label>Time zone
16+
<select id="timezoneSelect">
17+
<option value="local">local</option>
18+
<option value="server">server</option>
19+
<option value="utc">utc</option>
20+
</select>
21+
</label>
1522
<h1>Scheduled Jobs</h1>
1623
<table id="jobs">
1724
<thead>
@@ -117,8 +124,25 @@ <h1>Configuration</h1>
117124
return `${ns}ns`;
118125
}
119126

127+
function formatTime(dateStr) {
128+
const pref = localStorage.getItem('timezone') || 'local';
129+
if (dateStr === null || dateStr === undefined) return 'Invalid Date';
130+
const str = String(dateStr);
131+
const dt = new Date(str);
132+
if (isNaN(dt)) return 'Invalid Date';
133+
if (pref === 'utc') return dt.toISOString().replace('T',' ').replace('Z','');
134+
if (pref === 'server') return str.replace('T',' ').replace('Z','');
135+
return dt.toLocaleString();
136+
}
137+
120138
let jobsData = {};
121139
let editing = null;
140+
const tzSelect = document.getElementById('timezoneSelect');
141+
tzSelect.value = localStorage.getItem('timezone') || 'local';
142+
tzSelect.addEventListener('change', () => {
143+
localStorage.setItem('timezone', tzSelect.value);
144+
refresh();
145+
});
122146
async function loadJobs() {
123147
const resp = await fetch('/api/jobs');
124148
const jobs = await resp.json();
@@ -131,7 +155,7 @@ <h1>Configuration</h1>
131155
const statusText = j.last_run ? (j.last_run.failed ? 'Failed' : (j.last_run.skipped ? 'Skipped' : 'Success')) : 'never';
132156
const symbol = statusText === 'Failed' ? '❌' : statusText === 'Skipped' ? '⏭' : statusText === 'Success' ? '✅' : '–';
133157
const status = `${symbol} ${statusText}`;
134-
const time = j.last_run ? new Date(j.last_run.date).toISOString().replace('T',' ').replace('Z','') : '';
158+
const time = j.last_run ? formatTime(j.last_run.date) : '';
135159
const duration = j.last_run ? j.last_run.duration : '';
136160
const cfg = `<details><summary>⚙</summary><pre>${JSON.stringify(j.config, null, 2)}</pre></details>`;
137161
tr.innerHTML = `<td>${j.name}</td><td>${j.type}</td><td class="fixed">${j.schedule}</td><td class="fixed">${j.command}</td>` +
@@ -170,7 +194,7 @@ <h1>Configuration</h1>
170194
const statusText = e.failed ? 'Failed' : (e.skipped ? 'Skipped' : 'Success');
171195
const symbol = statusText === 'Failed' ? '❌' : statusText === 'Skipped' ? '⏭' : '✅';
172196
const status = `${symbol} ${statusText}`;
173-
const time = new Date(e.date).toISOString().replace('T',' ').replace('Z','');
197+
const time = formatTime(e.date);
174198
const err = e.error ? e.error : '';
175199
row.innerHTML = `<td>${time}</td><td>${formatDuration(e.duration)}</td><td>${status}</td>` +
176200
`<td>${err}</td>` +
@@ -189,7 +213,7 @@ <h1>Configuration</h1>
189213
const statusText = j.last_run ? (j.last_run.failed ? 'Failed' : (j.last_run.skipped ? 'Skipped' : 'Success')) : 'never';
190214
const symbol = statusText === 'Failed' ? '❌' : statusText === 'Skipped' ? '⏭' : statusText === 'Success' ? '✅' : '–';
191215
const status = `${symbol} ${statusText}`;
192-
const time = j.last_run ? new Date(j.last_run.date).toISOString().replace('T',' ').replace('Z','') : '';
216+
const time = j.last_run ? formatTime(j.last_run.date) : '';
193217
const duration = j.last_run ? j.last_run.duration : '';
194218
const cfg = `<details><summary>⚙</summary><pre>${JSON.stringify(j.config, null, 2)}</pre></details>`;
195219
tr.innerHTML = `<td>${j.name}</td><td>${j.type}</td><td class="fixed">${j.schedule}</td><td class="fixed">${j.command}</td><td>${j.origin}</td><td>${status}</td><td>${time}</td><td>${formatDuration(duration)}</td><td>${cfg}</td>`;

0 commit comments

Comments
 (0)