Compare commits

..

10 Commits

Author SHA1 Message Date
Daniel Volz e55e415a50 chore: release v1.8.6 (#129) 2026-02-08 14:06:03 +01:00
Daniel Volz 5253d14af7 fix: make frontend image self-contained for read-only filesystems (#128)
Revert Dockerfile to use /tmp redirect for envsubst output, so the image
works regardless of docker-compose.yml tmpfs configuration. Removes the
uid=101,gid=101 requirement from compose that was a breaking change.
2026-02-08 14:03:53 +01:00
github-actions[bot] 4f75d78a2b chore: update test count badges [skip ci] 2026-02-08 12:54:19 +00:00
Daniel Volz 8f9b65147b fix: use PAT for badge workflow to bypass branch protection (#127) 2026-02-08 13:53:19 +01:00
Daniel Volz 571ab00918 chore: release v1.8.5 (#126) 2026-02-08 13:35:52 +01:00
Daniel Volz 27f5478dad fix: clean up nginx read-only filesystem approach (#125)
Remove Dockerfile /tmp workaround hacks (NGINX_ENVSUBST_OUTPUT_DIR and sed).
Use tmpfs with uid=101,gid=101 in docker-compose.yml instead, so the
nginx user can write to /etc/nginx/conf.d directly under read_only: true.
2026-02-08 13:33:40 +01:00
Daniel Volz 5cd519be50 chore: release v1.8.4 (#124) 2026-02-08 13:12:58 +01:00
Daniel Volz e0c5eb4bf3 feat: simplify About modal with single version link to GitHub release (#123)
- Replace separate Frontend/Backend versions with single app version
- Version is now a clickable link to the GitHub release page
- Replace stopwatch SVG with actual app logo (favicon.svg)
- Fix update check UX: previous result stays visible during re-check
- Add 1s minimum delay for update check spinner visibility
- Reserve space for update result to prevent modal jumping
- Remove unused i18n keys (frontend/backend)
- Update release-manager docs with version link info
2026-02-08 13:09:33 +01:00
Daniel Volz aa92bcd96d fix: nginx read_only filesystem compatibility for envsubst (#122)
Redirect NGINX_ENVSUBST_OUTPUT_DIR to /tmp and update nginx.conf include
path so envsubst works with read_only: true in docker-compose.
Add tmpfs mount for /etc/nginx/conf.d for additional write layer.
2026-02-08 13:07:21 +01:00
Daniel Volz 1798a608bc fix: badge workflow commits directly instead of creating PRs (#121)
* fix: badge workflow commits directly instead of creating PRs

Replace peter-evans/create-pull-request with direct git push.
Removes need for pull-requests:write permission and the repo setting
'Allow GitHub Actions to create pull requests'.

Uses [skip ci] in commit message to avoid triggering itself.

* chore: trigger CI
2026-02-08 12:25:33 +01:00
14 changed files with 76 additions and 81 deletions
+5 -3
View File
@@ -139,12 +139,14 @@ This script handles: branch creation → version bump → PR → CI wait → mer
### Version Files (MANDATORY)
The version number is displayed in the **About modal** (Settings → About) showing both Frontend and Backend versions. These are read from:
The version number is displayed in the **About modal** (Settings → About) as a single unified app version. This version is a **clickable link** pointing to the corresponding GitHub release (`https://github.com/DanielVolz/medassist-ng/releases/tag/vX.Y.Z`). The version is read from:
- **`backend/package.json`** → Backend version, returned by `/health` endpoint
- **`frontend/package.json`** → Frontend version, injected at build time via Vite's `__APP_VERSION__` define
- **`frontend/package.json`** → Frontend version, injected at build time via Vite's `__APP_VERSION__` define and used to construct the release link
**Both files MUST be updated to the new version before tagging a release.** If forgotten, the About modal will show the old version even after the update.
**Both files MUST be updated to the new version before tagging a release.** If forgotten:
- The About modal will show the old version
- The version link will point to a non-existent GitHub release page
### Manual Release (if script is not available)
+12 -16
View File
@@ -12,7 +12,6 @@ on:
permissions:
contents: write
pull-requests: write
jobs:
update-badges:
@@ -23,7 +22,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
token: ${{ secrets.BADGE_TOKEN || secrets.GITHUB_TOKEN }}
- name: Setup Node.js
uses: actions/setup-node@v4
@@ -92,17 +91,14 @@ jobs:
exit 0
fi
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'chore: update test count badges'
title: 'chore: update test count badges'
body: |
Automated update of test count badges in README.md.
- Backend tests: ${{ steps.backend-tests.outputs.count }}
- Frontend tests: ${{ steps.frontend-tests.outputs.count }}
branch: chore/update-test-badges
delete-branch: true
labels: automated
- name: Commit and push badge updates
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add README.md
if git diff --cached --quiet; then
echo "No badge changes to commit"
else
git commit -m "chore: update test count badges [skip ci]"
git push
fi
+2 -2
View File
@@ -18,8 +18,8 @@
</p>
<p align="center">
<img src="https://img.shields.io/badge/Backend_Tests-454%2F454-brightgreen?logo=vitest" alt="Backend Tests 454/454" />
<img src="https://img.shields.io/badge/Frontend_Tests-611%2F611-brightgreen?logo=vitest" alt="Frontend Tests 611/611" />
<img src="https://img.shields.io/badge/Backend_Tests-494%2F494-brightgreen?logo=vitest" alt="Backend Tests 454/454" />
<img src="https://img.shields.io/badge/Frontend_Tests-639%2F639-brightgreen?logo=vitest" alt="Frontend Tests 611/611" />
</p>
### 🤖 AI-Generated Code
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "medassist-ng-backend",
"version": "1.7.1",
"version": "1.8.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "medassist-ng-backend",
"version": "1.7.1",
"version": "1.8.3",
"dependencies": {
"@fastify/cookie": "^10.0.1",
"@fastify/cors": "^10.0.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "medassist-ng-backend",
"version": "1.8.3",
"version": "1.8.6",
"private": true,
"type": "module",
"scripts": {
+1
View File
@@ -52,6 +52,7 @@ services:
- /tmp:noexec,nosuid,size=64m
- /var/cache/nginx:noexec,nosuid,size=64m
- /var/run:noexec,nosuid,size=64m
- /etc/nginx/conf.d:noexec,nosuid,size=1m
cap_drop:
- ALL
+5
View File
@@ -32,6 +32,11 @@ RUN npm run build
# -----------------------------------------------------------------------------
FROM nginxinc/nginx-unprivileged:1.27-alpine AS runner
# Redirect envsubst output to /tmp (writable under read_only: true)
# and update nginx main config to include from there instead of /etc/nginx/conf.d/
ENV NGINX_ENVSUBST_OUTPUT_DIR=/tmp
RUN sed -i 's|include /etc/nginx/conf.d/\*.conf;|include /tmp/default.conf;|' /etc/nginx/nginx.conf
# Copy custom nginx config as template for envsubst processing
# nginx-unprivileged automatically substitutes env vars in .template files
COPY nginx.conf /etc/nginx/templates/default.conf.template
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "medassist-ng-frontend",
"version": "1.7.1",
"version": "1.8.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "medassist-ng-frontend",
"version": "1.7.1",
"version": "1.8.3",
"dependencies": {
"i18next": "^24.2.2",
"i18next-browser-languagedetector": "^8.0.4",
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "medassist-ng-frontend",
"private": true,
"version": "1.8.3",
"version": "1.8.6",
"type": "module",
"scripts": {
"dev": "vite",
+24 -29
View File
@@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next";
import { FRONTEND_VERSION, GITHUB_URL } from "../App";
interface UpdateCheckResult {
status: "checking" | "up-to-date" | "update-available" | "error";
status: "up-to-date" | "update-available" | "error";
latestVersion?: string;
lastChecked?: string;
}
@@ -15,19 +15,13 @@ interface AboutModalProps {
export default function AboutModal({ isOpen, onClose }: AboutModalProps) {
const { t } = useTranslation();
const [backendVersion, setBackendVersion] = useState<string | null>(null);
const [isChecking, setIsChecking] = useState(false);
const [updateCheckResult, setUpdateCheckResult] = useState<UpdateCheckResult | null>(null);
// Fetch backend version and cached update result on mount
// Load cached update check result on mount
useEffect(() => {
if (!isOpen) return;
// Fetch backend version
fetch("/api/health")
.then((res) => res.json())
.then((data) => setBackendVersion(data.version || "unknown"))
.catch(() => setBackendVersion("unknown"));
// Load cached update check result
const cached = sessionStorage.getItem("updateCheckResult");
if (cached) {
@@ -43,9 +37,13 @@ export default function AboutModal({ isOpen, onClose }: AboutModalProps) {
}, [isOpen]);
async function checkForUpdates() {
setUpdateCheckResult({ status: "checking" });
setIsChecking(true);
const minDelay = new Promise((resolve) => setTimeout(resolve, 1000));
try {
const res = await fetch(`https://api.github.com/repos/DanielVolz/medassist-ng/releases/latest`);
const [res] = await Promise.all([
fetch(`https://api.github.com/repos/DanielVolz/medassist-ng/releases/latest`),
minDelay,
]);
if (!res.ok) throw new Error("Failed to fetch");
const data = await res.json();
const latestVersion = (data.tag_name || "").replace(/^v/, "");
@@ -61,6 +59,8 @@ export default function AboutModal({ isOpen, onClose }: AboutModalProps) {
sessionStorage.setItem("updateCheckResult", JSON.stringify(result));
} catch {
setUpdateCheckResult({ status: "error" });
} finally {
setIsChecking(false);
}
}
@@ -74,32 +74,27 @@ export default function AboutModal({ isOpen, onClose }: AboutModalProps) {
</button>
<div className="about-header">
<div className="about-logo">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
<path d="M19.5 12c0 4.14-3.36 7.5-7.5 7.5S4.5 16.14 4.5 12 7.86 4.5 12 4.5s7.5 3.36 7.5 7.5z" />
<path d="M12 8v4l2.5 2.5" />
<path d="M9 2h6M12 2v2" />
</svg>
<img src="/favicon.svg" alt="MedAssist-ng" />
</div>
<h2>{t("about.appName", "MedAssist-ng")}</h2>
<p className="about-tagline">{t("about.description", "Personal medication tracking and reminder app")}</p>
</div>
<div className="about-versions">
<div className="about-version-row">
<span className="about-version-label">{t("about.frontendVersion", "Frontend")}</span>
<span className="about-version-value">{FRONTEND_VERSION}</span>
</div>
<div className="about-version-row">
<span className="about-version-label">{t("about.backendVersion", "Backend")}</span>
<span className="about-version-value">{backendVersion || "..."}</span>
<span className="about-version-label">{t("about.version", "Version")}</span>
<a
href={`${GITHUB_URL}/releases/tag/v${FRONTEND_VERSION}`}
target="_blank"
rel="noopener noreferrer"
className="about-version-value about-version-link"
>
{FRONTEND_VERSION}
</a>
</div>
</div>
<div className="about-update-section">
<button
className="about-update-btn"
onClick={checkForUpdates}
disabled={updateCheckResult?.status === "checking"}
>
{updateCheckResult?.status === "checking" ? (
<button className="about-update-btn" onClick={checkForUpdates} disabled={isChecking}>
{isChecking ? (
<>
<span className="spinner-small"></span>
{t("about.checking", "Checking...")}
@@ -116,7 +111,7 @@ export default function AboutModal({ isOpen, onClose }: AboutModalProps) {
</>
)}
</button>
{updateCheckResult && updateCheckResult.status !== "checking" && (
{updateCheckResult && (
<div className={`about-update-result ${updateCheckResult.status}`}>
{updateCheckResult.status === "up-to-date" && (
<span className="update-status-text"> {t("about.upToDate", "You are up to date!")}</span>
-2
View File
@@ -474,8 +474,6 @@
"appName": "MedAssist-ng",
"description": "Open-Source Medikamentenverwaltung und Planungsanwendung für selbst gehostete Umgebungen.",
"version": "Version",
"frontend": "Frontend",
"backend": "Backend",
"checkForUpdates": "Nach Updates suchen",
"checking": "Prüfe...",
"upToDate": "Du bist auf dem neuesten Stand!",
-2
View File
@@ -474,8 +474,6 @@
"appName": "MedAssist-ng",
"description": "Open-source medication tracking and planning application for self-hosted environments.",
"version": "Version",
"frontend": "Frontend",
"backend": "Backend",
"checkForUpdates": "Check for Updates",
"checking": "Checking...",
"upToDate": "You're up to date!",
+17 -15
View File
@@ -4551,18 +4551,12 @@ h3 .reminder-icon.info-tooltip {
width: 64px;
height: 64px;
margin: 0 auto 1rem;
background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%);
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 4px 12px rgba(var(--accent-rgb, 59, 130, 246), 0.25);
}
.about-logo svg {
width: 36px;
height: 36px;
stroke: white;
.about-logo img {
width: 64px;
height: 64px;
border-radius: 16px;
}
.about-header h2 {
@@ -4586,15 +4580,12 @@ h3 .reminder-icon.info-tooltip {
.about-version-row {
display: flex;
justify-content: space-between;
justify-content: center;
align-items: center;
gap: 0.75rem;
padding: 0.5rem 0;
}
.about-version-row:not(:last-child) {
border-bottom: 1px dashed var(--border-secondary);
}
.about-version-label {
font-size: 0.875rem;
color: var(--text-secondary);
@@ -4610,9 +4601,20 @@ h3 .reminder-icon.info-tooltip {
border-radius: 4px;
}
a.about-version-link {
color: var(--accent-primary);
text-decoration: none;
transition: text-decoration 0.15s ease;
}
a.about-version-link:hover {
text-decoration: underline;
}
.about-update-section {
padding: 1.25rem 1.5rem;
border-bottom: 1px solid var(--border-primary);
min-height: 148px;
}
.about-update-btn {
@@ -16,10 +16,6 @@ describe("AboutModal", () => {
beforeEach(() => {
vi.clearAllMocks();
(global.fetch as ReturnType<typeof vi.fn>).mockResolvedValue({
ok: true,
json: () => Promise.resolve({ version: "1.0.0" }),
});
});
it("returns null when not open", () => {
@@ -65,8 +61,10 @@ describe("AboutModal", () => {
expect(links.length).toBeGreaterThan(0);
});
it("fetches backend version on open", async () => {
it("renders version as link to GitHub release", () => {
render(<AboutModal {...defaultProps} />);
expect(fetch).toHaveBeenCalledWith("/api/health");
const versionLink = screen.getByText("1.0.0").closest("a");
expect(versionLink).toHaveAttribute("href", "https://github.com/test/repo/releases/tag/v1.0.0");
expect(versionLink).toHaveAttribute("target", "_blank");
});
});