From 9580ceb24c052dfde624b2b7a2c615990d5ced5b Mon Sep 17 00:00:00 2001
From: SeSo
Date: Fri, 9 Oct 2026 16:15:19 -0700
Subject: [PATCH 1/2] feat: add timezone-aware datetime formatting using
`local_time` filter
---
pyproject.toml | 1 +
src/makeitso/__init__.py | 2 ++
src/makeitso/local_time.py | 11 +++++++++++
src/makeitso/monitor/monitor.py | 3 ++-
src/makeitso/templates/deploys/detail.html | 2 +-
src/makeitso/templates/deploys/list.html | 2 +-
src/makeitso/templates/main/index.html | 2 +-
src/makeitso/templates/stacks/_commit_row.html | 2 +-
src/makeitso/templates/stacks/_commits.html | 4 ++--
uv.lock | 2 ++
10 files changed, 24 insertions(+), 7 deletions(-)
create mode 100644 src/makeitso/local_time.py
diff --git a/pyproject.toml b/pyproject.toml
index a58551e..7bfa78e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -26,6 +26,7 @@ dependencies = [
"requests>=2.34.2",
"rq>=2.12.0",
"psycopg[binary]>=3.3.6",
+ "tzdata>=2026.4",
]
[build-system]
diff --git a/src/makeitso/__init__.py b/src/makeitso/__init__.py
index 7d80f40..d22f2ff 100644
--- a/src/makeitso/__init__.py
+++ b/src/makeitso/__init__.py
@@ -4,6 +4,7 @@
from makeitso.config import configs
from makeitso.extensions import bootstrap, csrf, db, job_queue, migrate, oauth
+from makeitso.local_time import local_time
def create_app(config_name: str | None = None) -> Flask:
@@ -23,6 +24,7 @@ def create_app(config_name: str | None = None) -> Flask:
migrate.init_app(app, db)
job_queue.init_app(app)
oauth.init_app(app)
+ app.add_template_filter(local_time)
from makeitso import models # noqa: F401 (registers the models with SQLAlchemy)
from makeitso.auth import bp as auth_bp
diff --git a/src/makeitso/local_time.py b/src/makeitso/local_time.py
new file mode 100644
index 0000000..2b7e822
--- /dev/null
+++ b/src/makeitso/local_time.py
@@ -0,0 +1,11 @@
+from datetime import UTC, datetime
+from zoneinfo import ZoneInfo
+
+LOCAL_TZ = ZoneInfo("America/Vancouver")
+
+
+def local_time(dt: datetime, fmt: str = "%b %d, %Y %H:%M Vancouver time") -> str:
+ # Naive datetimes are UTC
+ if dt.tzinfo is None:
+ dt = dt.replace(tzinfo=UTC)
+ return dt.astimezone(LOCAL_TZ).strftime(fmt)
diff --git a/src/makeitso/monitor/monitor.py b/src/makeitso/monitor/monitor.py
index 4d321bc..5533bc8 100644
--- a/src/makeitso/monitor/monitor.py
+++ b/src/makeitso/monitor/monitor.py
@@ -13,6 +13,7 @@
)
from makeitso.extensions import job_queue
+from makeitso.local_time import local_time
# Most jobs to show per queue and per registry, so old failed jobs don't pile up on the page
MAX_JOBS = 50
@@ -73,7 +74,7 @@ def get_queue_data(queue: Queue):
JobData(
id=job.id,
status=job.get_status().value,
- started_at=(job.started_at.strftime("%b %d, %Y %H:%M") if job.started_at else None),
+ started_at=(local_time(job.started_at) if job.started_at else None),
execution_time=_execution_time(job),
)
for job in jobs
diff --git a/src/makeitso/templates/deploys/detail.html b/src/makeitso/templates/deploys/detail.html
index b6a931d..42b5a26 100644
--- a/src/makeitso/templates/deploys/detail.html
+++ b/src/makeitso/templates/deploys/detail.html
@@ -23,7 +23,7 @@
{{ deploy.commit.commit_message }}
{% set details = [
("person", "Author", deploy.commit.author_name),
- ("clock", "Started", deploy.started_at.strftime("%b %d, %Y %H:%M")),
+ ("clock", "Started", deploy.started_at|local_time),
("person-check", "Deployed by", deployed_by(deploy)),
] %}
diff --git a/src/makeitso/templates/deploys/list.html b/src/makeitso/templates/deploys/list.html
index 396020e..fafc723 100644
--- a/src/makeitso/templates/deploys/list.html
+++ b/src/makeitso/templates/deploys/list.html
@@ -27,7 +27,7 @@
{{ bypass_badge(deploy) }}
| {{ deployed_by(deploy) }} |
- {{ deploy.started_at.strftime("%b %d, %Y %H:%M") }} |
+ {{ deploy.started_at|local_time }} |
{{ deploy.elapsed }} |
{{ render_icon("terminal") }} View log |
diff --git a/src/makeitso/templates/main/index.html b/src/makeitso/templates/main/index.html
index fa5d3f3..57df4a2 100644
--- a/src/makeitso/templates/main/index.html
+++ b/src/makeitso/templates/main/index.html
@@ -56,7 +56,7 @@ Stacks
{% elif state.latest %}
{{ status_badge(state.latest.status) }}
- {{ state.latest.started_at.strftime("%b %d, %H:%M") }} by {{ deployed_by(state.latest) }}
+ {{ state.latest.started_at|local_time("%b %d, %H:%M Vancouver time") }} by {{ deployed_by(state.latest) }}
{% else %}
no deploys yet
diff --git a/src/makeitso/templates/stacks/_commit_row.html b/src/makeitso/templates/stacks/_commit_row.html
index fc38641..78df453 100644
--- a/src/makeitso/templates/stacks/_commit_row.html
+++ b/src/makeitso/templates/stacks/_commit_row.html
@@ -27,7 +27,7 @@
target="_blank" rel="noopener" class="text-muted">#{{ commit.pull_request_number }}
{% endif %}
- {{ commit.short_sha }} · {{ commit.committed_at.strftime("%b %d, %Y %H:%M") }}
+ {{ commit.short_sha }} · {{ commit.committed_at|local_time }}
{% if show_deploy %}
diff --git a/src/makeitso/templates/stacks/_commits.html b/src/makeitso/templates/stacks/_commits.html
index 6a80c5f..8847407 100644
--- a/src/makeitso/templates/stacks/_commits.html
+++ b/src/makeitso/templates/stacks/_commits.html
@@ -12,7 +12,7 @@
Syncing with GitHub…
{% else %}
- {% if stack.synced_at %}Last synced {{ stack.synced_at.strftime("%b %d, %Y %H:%M") }}{% else %}Never synced{% endif %}
+ {% if stack.synced_at %}Last synced {{ stack.synced_at|local_time }}{% else %}Never synced{% endif %}
{% endif %}
@@ -85,7 +85,7 @@ Previous deploys
{{ bypass_badge(deploy) }}
{{ render_icon("person-check") }} {{ deployed_by(deploy) }}
- {{ render_icon("clock") }} {{ deploy.started_at.strftime("%b %d, %Y %H:%M") }}
+ {{ render_icon("clock") }} {{ deploy.started_at|local_time }}
{{ render_icon("stopwatch") }} {{ deploy.elapsed }}
{{ render_icon("terminal") }} View log
diff --git a/uv.lock b/uv.lock
index 2fd3b58..e074c68 100644
--- a/uv.lock
+++ b/uv.lock
@@ -547,6 +547,7 @@ dependencies = [
{ name = "redis" },
{ name = "requests" },
{ name = "rq" },
+ { name = "tzdata" },
]
[package.dev-dependencies]
@@ -576,6 +577,7 @@ requires-dist = [
{ name = "redis", specifier = ">=8.1.0" },
{ name = "requests", specifier = ">=2.34.2" },
{ name = "rq", specifier = ">=2.12.0" },
+ { name = "tzdata", specifier = ">=2026.4" },
]
[package.metadata.requires-dev]
From 1c8fbae212eb21c71160c18d2019458788ca0df8 Mon Sep 17 00:00:00 2001
From: SeSo
Date: Fri, 9 Oct 2026 16:15:42 -0700
Subject: [PATCH 2/2] feat: update stack locking UI with separate lock/unlock
actions and icons
---
src/makeitso/stacks/forms.py | 7 ++++---
src/makeitso/stacks/views/settings.py | 2 +-
src/makeitso/templates/main/index.html | 2 +-
src/makeitso/templates/stacks/settings.html | 10 +++++++---
4 files changed, 13 insertions(+), 8 deletions(-)
diff --git a/src/makeitso/stacks/forms.py b/src/makeitso/stacks/forms.py
index d21da6a..6839413 100644
--- a/src/makeitso/stacks/forms.py
+++ b/src/makeitso/stacks/forms.py
@@ -1,6 +1,6 @@
from flask_wtf import FlaskForm
from github import GithubException
-from wtforms import BooleanField, Field, StringField, TextAreaField
+from wtforms import BooleanField, Field, StringField, SubmitField, TextAreaField
from wtforms.validators import DataRequired, Regexp, ValidationError
from makeitso.extensions import db
@@ -81,13 +81,14 @@ def _strip(value: str | None) -> str | None:
class LockForm(FlaskForm):
- locked = BooleanField("Lock this stack")
+ lock = SubmitField("Lock")
+ unlock = SubmitField("Unlock")
lock_reason = TextAreaField(
"Reason", description="Shown on the stack page. Needed when locking.", filters=[_strip]
)
def validate_lock_reason(self, field: Field) -> None:
- if self.locked.data and not field.data:
+ if self.lock.data and not field.data:
raise ValidationError("Give a reason for locking the stack")
diff --git a/src/makeitso/stacks/views/settings.py b/src/makeitso/stacks/views/settings.py
index dcb7e64..df2ff19 100644
--- a/src/makeitso/stacks/views/settings.py
+++ b/src/makeitso/stacks/views/settings.py
@@ -47,7 +47,7 @@ def post(self, stack_id: int):
form = _lock_form(stack)
if not form.validate_on_submit():
return _render(stack, form, _env_var_form())
- stack.locked = form.locked.data
+ stack.locked = form.lock.data
stack.lock_reason = form.lock_reason.data if stack.locked else None
db.session.commit()
flash("Stack locked" if stack.locked else "Stack unlocked", "success")
diff --git a/src/makeitso/templates/main/index.html b/src/makeitso/templates/main/index.html
index 57df4a2..022ea88 100644
--- a/src/makeitso/templates/main/index.html
+++ b/src/makeitso/templates/main/index.html
@@ -35,7 +35,7 @@ Stacks
{{ stack.organization }}/{{ stack.repository }}
{{ stack.environment }}
{% if stack.locked %}
- Locked
+ {{ render_icon("lock-fill", color="secondary") }}
{% endif %}
{{ stack.branch }} |
diff --git a/src/makeitso/templates/stacks/settings.html b/src/makeitso/templates/stacks/settings.html
index 83f39e4..374529e 100644
--- a/src/makeitso/templates/stacks/settings.html
+++ b/src/makeitso/templates/stacks/settings.html
@@ -28,9 +28,13 @@ Lock deploys
Environment variables