Skip to content

Commit 6a127fc

Browse files
authored
Merge pull request #81 from fccview/develop
2.1.0
2 parents 6b89a9e + 64a75d2 commit 6a127fc

30 files changed

Lines changed: 1397 additions & 2242 deletions

.github/FUNDING.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
github: fccview

.github/workflows/docker-build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Reusable Docker Build Logic
1+
name: Builder
22

33
on:
44
workflow_call:

.github/workflows/docker-publish.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Build and Publish Multi-Platform Docker Image
1+
name: Build and Publish
22

33
on:
44
push:

.github/workflows/pr-checks.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: PR Checks
2+
3+
on:
4+
pull_request:
5+
branches: ["**"]
6+
7+
jobs:
8+
validate-branch:
9+
name: Validate Target Branch
10+
runs-on: ubuntu-latest
11+
steps:
12+
- name: YOU SHALL NOT PASS
13+
if: github.base_ref == 'main'
14+
run: |
15+
if [ "${{ github.head_ref }}" != "develop" ]; then
16+
echo "ERROR: Pull requests to 'main' are not allowed."
17+
echo "Current source branch: ${{ github.head_ref }}"
18+
echo ""
19+
echo "Please create a PR to 'develop' first, this will become a release candidate when merged into 'main' by a maintainer"
20+
exit 1
21+
fi
22+
echo "Valid PR: develop → main"
23+
24+
- name: PR info
25+
run: |
26+
echo "PR validation passed"
27+
echo "Source: ${{ github.head_ref }}"
28+
echo "Target: ${{ github.base_ref }}"
29+
30+
typing:
31+
name: Type and install checks
32+
runs-on: ubuntu-latest
33+
needs: validate-branch
34+
steps:
35+
- name: Checkout repository
36+
uses: actions/checkout@v4
37+
38+
- name: Setup the best engine ever
39+
uses: actions/setup-node@v4
40+
with:
41+
node-version: "20"
42+
cache: "yarn"
43+
44+
- name: Install all dependencies
45+
run: yarn install --frozen-lockfile
46+
47+
- name: This will totally fail if you only use AI
48+
run: yarn tsc --noEmit
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: Build and Release Prebuilt Tarball
2+
3+
on:
4+
push:
5+
tags: ["*"]
6+
7+
jobs:
8+
build-prebuild:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
contents: write
12+
steps:
13+
- name: Checkout repository
14+
uses: actions/checkout@v4
15+
16+
- name: Setup the best engine ever
17+
uses: actions/setup-node@v4
18+
with:
19+
node-version: '20'
20+
cache: 'yarn'
21+
22+
- name: Install dependencies
23+
run: yarn install --frozen-lockfile
24+
25+
- name: Bake cronmaster
26+
env:
27+
NODE_ENV: production
28+
NEXT_TELEMETRY_DISABLED: 1
29+
run: yarn build
30+
31+
- name: Get version from tag
32+
id: version
33+
run: |
34+
VERSION="${GITHUB_REF#refs/tags/}"
35+
echo "version=${VERSION}" >> $GITHUB_OUTPUT
36+
37+
- name: Structure the prebuild stuff
38+
run: |
39+
mkdir -p prebuild-release/cronmaster/.next
40+
cp -r .next/standalone/. prebuild-release/cronmaster/
41+
cp -r .next/static prebuild-release/cronmaster/.next/static
42+
if [ -f .next/BUILD_ID ]; then
43+
cp .next/BUILD_ID prebuild-release/cronmaster/.next/BUILD_ID
44+
fi
45+
cp -r public prebuild-release/cronmaster/public
46+
cp -r howto prebuild-release/cronmaster/howto
47+
48+
- name: Create tarball - tarball is a funny name
49+
run: |
50+
cd prebuild-release
51+
tar -czf cronmaster_${{ steps.version.outputs.version }}_prebuild.tar.gz cronmaster
52+
sha256sum cronmaster_${{ steps.version.outputs.version }}_prebuild.tar.gz > cronmaster_${{ steps.version.outputs.version }}_prebuild.tar.gz.sha256
53+
54+
- name: Attach to Release - pray it works
55+
uses: softprops/action-gh-release@v1
56+
with:
57+
files: |
58+
prebuild-release/cronmaster_*_prebuild.tar.gz
59+
prebuild-release/cronmaster_*_prebuild.tar.gz.sha256
60+
tag_name: ${{ steps.version.outputs.version }}

app/_components/FeatureComponents/Layout/Sidebar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export const Sidebar = forwardRef<HTMLDivElement, SidebarProps>(
9292
>
9393
<button
9494
onClick={() => setIsCollapsed(!isCollapsed)}
95-
className="absolute -right-3 top-[21.5vh] w-6 h-6 bg-background0 ascii-border items-center justify-center transition-colors z-40 hidden lg:flex"
95+
className="sidebar-shrinker absolute -right-3 top-[21.5vh] w-6 h-6 bg-background0 ascii-border items-center justify-center transition-colors z-40 hidden lg:flex"
9696
>
9797
{isCollapsed ? (
9898
<CaretRightIcon className="h-3 w-3" />

app/_components/FeatureComponents/Modals/LogsModal.tsx

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
import { useState, useEffect } from "react";
44
import { Modal } from "@/app/_components/GlobalComponents/UIElements/Modal";
55
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
6-
import { FileTextIcon, TrashIcon, EyeIcon, XIcon, ArrowsClockwiseIcon, WarningCircleIcon, CheckCircleIcon } from "@phosphor-icons/react";
6+
import { FileTextIcon, TrashIcon, EyeIcon, XIcon, ArrowsClockwiseIcon, WarningCircleIcon, CheckCircleIcon, DownloadIcon } from "@phosphor-icons/react";
77
import { useTranslations } from "next-intl";
8+
import { zipSync, strToU8 } from "fflate";
89
import {
910
getJobLogs,
1011
getLogContent,
@@ -44,6 +45,7 @@ export const LogsModal = ({
4445
const [logContent, setLogContent] = useState<string>("");
4546
const [isLoadingLogs, setIsLoadingLogs] = useState(false);
4647
const [isLoadingContent, setIsLoadingContent] = useState(false);
48+
const [isDownloading, setIsDownloading] = useState(false);
4749
const [stats, setStats] = useState<{
4850
count: number;
4951
totalSize: number;
@@ -133,6 +135,28 @@ export const LogsModal = ({
133135
}
134136
};
135137

138+
const handleDownloadLogs = async () => {
139+
if (logs.length === 0) return;
140+
setIsDownloading(true);
141+
try {
142+
const files: Record<string, Uint8Array> = {};
143+
for (const log of logs) {
144+
const content = await getLogContent(jobId, log.filename);
145+
files[log.filename] = strToU8(content);
146+
}
147+
const zipped = zipSync(files);
148+
const blob = new Blob([zipped as unknown as ArrayBuffer], { type: "application/zip" });
149+
const url = URL.createObjectURL(blob);
150+
const a = document.createElement("a");
151+
a.href = url;
152+
a.download = `${jobComment || jobId}_logs.zip`;
153+
a.click();
154+
URL.revokeObjectURL(url);
155+
} finally {
156+
setIsDownloading(false);
157+
}
158+
};
159+
136160
const formatFileSize = (bytes: number): string => {
137161
if (bytes < 1024) return `${bytes} B`;
138162
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`;
@@ -157,43 +181,56 @@ export const LogsModal = ({
157181
return (
158182
<Modal isOpen={isOpen} onClose={onClose} title={t("cronjobs.viewLogs")} size="xl">
159183
<div className="flex flex-col h-[600px]">
160-
<div className="flex items-center justify-between mb-4 pb-4 border-b border-border">
161-
<div>
162-
<h3 className="font-semibold text-lg">{jobComment || jobId}</h3>
184+
<div className="block sm:flex items-center justify-between mb-4 pb-4 border-b border-border">
185+
<div className="min-w-0 mb-4 sm:mb-0">
186+
<h3 className="font-semibold text-lg truncate">{jobComment || jobId}</h3>
163187
{stats && (
164188
<p className="text-sm text-muted-foreground">
165189
{stats.count} {t("cronjobs.logs")}{stats.totalSizeMB} MB
166190
</p>
167191
)}
168192
</div>
169-
<div className="flex gap-2">
193+
<div className="flex gap-2 flex-shrink-0">
194+
<Button
195+
onClick={handleDownloadLogs}
196+
disabled={logs.length === 0 || isDownloading}
197+
className="btn-primary glow-primary"
198+
size="sm"
199+
>
200+
{isDownloading ? (
201+
<ArrowsClockwiseIcon className="w-4 h-4 sm:mr-2 animate-spin" />
202+
) : (
203+
<DownloadIcon className="w-4 h-4 sm:mr-2" />
204+
)}
205+
<span className="hidden sm:inline">{t("cronjobs.downloadLog")}</span>
206+
</Button>
170207
<Button
171208
onClick={loadLogs}
172209
disabled={isLoadingLogs}
173210
className="btn-primary glow-primary"
174211
size="sm"
175212
>
176213
<ArrowsClockwiseIcon
177-
className={`w-4 h-4 mr-2 ${isLoadingLogs ? "animate-spin" : ""
214+
className={`w-4 h-4 sm:mr-2 ${isLoadingLogs ? "animate-spin" : ""
178215
}`}
179216
/>
180-
{t("common.refresh")}
217+
<span className="hidden sm:inline">{t("common.refresh")}</span>
181218
</Button>
182219
{logs.length > 0 && (
183220
<Button
184221
onClick={handleDeleteAllLogs}
185222
variant="destructive"
186223
size="sm"
187224
>
188-
<TrashIcon className="w-4 h-4 mr-2" />
189-
{t("cronjobs.deleteAll")}
225+
<TrashIcon className="w-4 h-4 sm:mr-2" />
226+
<span className="hidden sm:inline">{t("cronjobs.deleteAll")}</span>
190227
</Button>
191228
)}
192229
</div>
193230
</div>
194231

195-
<div className="flex-1 flex gap-4 overflow-hidden">
196-
<div className="w-1/3 flex flex-col border-r border-border pr-4 overflow-hidden">
232+
<div className="flex-1 flex flex-col sm:flex-row gap-4 overflow-hidden">
233+
<div className="sm:w-1/3 flex flex-col sm:border-r border-b sm:border-b-0 border-border sm:pr-4 pb-4 sm:pb-0 overflow-hidden max-h-[40%] sm:max-h-none">
197234
<h4 className="font-semibold mb-2">{t("cronjobs.logFiles")}</h4>
198235
<div className="flex-1 overflow-y-auto space-y-2">
199236
{isLoadingLogs ? (
@@ -288,8 +325,8 @@ export const LogsModal = ({
288325

289326
<div className="mt-4 pt-4 border-t border-border flex justify-end">
290327
<Button onClick={onClose} className="btn-primary glow-primary">
291-
<XIcon className="w-4 h-4 mr-2" />
292-
{t("common.close")}
328+
<XIcon className="w-4 h-4 sm:mr-2" />
329+
<span className="hidden sm:inline">{t("common.close")}</span>
293330
</Button>
294331
</div>
295332
</div>

app/_components/FeatureComponents/PWA/PWAInstallPrompt.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { useCallback, useEffect, useState } from "react";
3+
import { useCallback, useEffect, useState, type JSX } from "react";
44

55
type BeforeInstallPromptEvent = Event & {
66
prompt: () => Promise<void>;

app/_components/FeatureComponents/PWA/ServiceWorkerRegister.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ export const ServiceWorkerRegister = (): null => {
1313
r.scope.endsWith("/")
1414
);
1515
if (alreadyRegistered) return;
16-
await navigator.serviceWorker.register("/sw.js", { scope: "/" });
16+
await navigator.serviceWorker.register("/serwist/sw.js", {
17+
scope: "/",
18+
updateViaCache: "none",
19+
});
1720
} catch (_err) {}
1821
};
1922
register();

app/_translations/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"logs": "logs",
4848
"logFiles": "Log Files",
4949
"logContent": "Log Content",
50+
"downloadLog": "Download",
5051
"selectLogToView": "Select a log file to view its content",
5152
"noLogsFound": "No logs found for this job",
5253
"confirmDeleteLog": "Are you sure you want to delete this log file?",

0 commit comments

Comments
 (0)