mirror of
https://github.com/fccview/cronmaster.git
synced 2026-02-18 23:07:11 -05:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64a75d265f | ||
|
|
f2fb381964 | ||
|
|
0ed4942a30 | ||
|
|
aeab383116 |
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
github: fccview
|
||||
2
.github/workflows/docker-build.yml
vendored
2
.github/workflows/docker-build.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Reusable Docker Build Logic
|
||||
name: Builder
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
2
.github/workflows/docker-publish.yml
vendored
2
.github/workflows/docker-publish.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Build and Publish Multi-Platform Docker Image
|
||||
name: Build and Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
48
.github/workflows/pr-checks.yml
vendored
Normal file
48
.github/workflows/pr-checks.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
name: PR Checks
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: ["**"]
|
||||
|
||||
jobs:
|
||||
validate-branch:
|
||||
name: Validate Target Branch
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: YOU SHALL NOT PASS
|
||||
if: github.base_ref == 'main'
|
||||
run: |
|
||||
if [ "${{ github.head_ref }}" != "develop" ]; then
|
||||
echo "ERROR: Pull requests to 'main' are not allowed."
|
||||
echo "Current source branch: ${{ github.head_ref }}"
|
||||
echo ""
|
||||
echo "Please create a PR to 'develop' first, this will become a release candidate when merged into 'main' by a maintainer"
|
||||
exit 1
|
||||
fi
|
||||
echo "Valid PR: develop → main"
|
||||
|
||||
- name: PR info
|
||||
run: |
|
||||
echo "PR validation passed"
|
||||
echo "Source: ${{ github.head_ref }}"
|
||||
echo "Target: ${{ github.base_ref }}"
|
||||
|
||||
typing:
|
||||
name: Type and install checks
|
||||
runs-on: ubuntu-latest
|
||||
needs: validate-branch
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup the best engine ever
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
cache: "yarn"
|
||||
|
||||
- name: Install all dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: This will totally fail if you only use AI
|
||||
run: yarn tsc --noEmit
|
||||
60
.github/workflows/prebuild-release.yml
vendored
Normal file
60
.github/workflows/prebuild-release.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
name: Build and Release Prebuilt Tarball
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ["*"]
|
||||
|
||||
jobs:
|
||||
build-prebuild:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup the best engine ever
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Bake cronmaster
|
||||
env:
|
||||
NODE_ENV: production
|
||||
NEXT_TELEMETRY_DISABLED: 1
|
||||
run: yarn build
|
||||
|
||||
- name: Get version from tag
|
||||
id: version
|
||||
run: |
|
||||
VERSION="${GITHUB_REF#refs/tags/}"
|
||||
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Structure the prebuild stuff
|
||||
run: |
|
||||
mkdir -p prebuild-release/cronmaster/.next
|
||||
cp -r .next/standalone/. prebuild-release/cronmaster/
|
||||
cp -r .next/static prebuild-release/cronmaster/.next/static
|
||||
if [ -f .next/BUILD_ID ]; then
|
||||
cp .next/BUILD_ID prebuild-release/cronmaster/.next/BUILD_ID
|
||||
fi
|
||||
cp -r public prebuild-release/cronmaster/public
|
||||
cp -r howto prebuild-release/cronmaster/howto
|
||||
|
||||
- name: Create tarball - tarball is a funny name
|
||||
run: |
|
||||
cd prebuild-release
|
||||
tar -czf cronmaster_${{ steps.version.outputs.version }}_prebuild.tar.gz cronmaster
|
||||
sha256sum cronmaster_${{ steps.version.outputs.version }}_prebuild.tar.gz > cronmaster_${{ steps.version.outputs.version }}_prebuild.tar.gz.sha256
|
||||
|
||||
- name: Attach to Release - pray it works
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: |
|
||||
prebuild-release/cronmaster_*_prebuild.tar.gz
|
||||
prebuild-release/cronmaster_*_prebuild.tar.gz.sha256
|
||||
tag_name: ${{ steps.version.outputs.version }}
|
||||
@@ -1,5 +1,5 @@
|
||||
<p align="center">
|
||||
<img src="public/heading.png" width="400px">
|
||||
<img src="public/heading.png">
|
||||
</p>
|
||||
|
||||
## Quick links
|
||||
|
||||
@@ -92,7 +92,7 @@ export const Sidebar = forwardRef<HTMLDivElement, SidebarProps>(
|
||||
>
|
||||
<button
|
||||
onClick={() => setIsCollapsed(!isCollapsed)}
|
||||
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"
|
||||
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"
|
||||
>
|
||||
{isCollapsed ? (
|
||||
<CaretRightIcon className="h-3 w-3" />
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { Modal } from "@/app/_components/GlobalComponents/UIElements/Modal";
|
||||
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
|
||||
import { FileTextIcon, TrashIcon, EyeIcon, XIcon, ArrowsClockwiseIcon, WarningCircleIcon, CheckCircleIcon } from "@phosphor-icons/react";
|
||||
import { FileTextIcon, TrashIcon, EyeIcon, XIcon, ArrowsClockwiseIcon, WarningCircleIcon, CheckCircleIcon, DownloadIcon } from "@phosphor-icons/react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { zipSync, strToU8 } from "fflate";
|
||||
import {
|
||||
getJobLogs,
|
||||
getLogContent,
|
||||
@@ -44,6 +45,7 @@ export const LogsModal = ({
|
||||
const [logContent, setLogContent] = useState<string>("");
|
||||
const [isLoadingLogs, setIsLoadingLogs] = useState(false);
|
||||
const [isLoadingContent, setIsLoadingContent] = useState(false);
|
||||
const [isDownloading, setIsDownloading] = useState(false);
|
||||
const [stats, setStats] = useState<{
|
||||
count: number;
|
||||
totalSize: number;
|
||||
@@ -133,6 +135,28 @@ export const LogsModal = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleDownloadLogs = async () => {
|
||||
if (logs.length === 0) return;
|
||||
setIsDownloading(true);
|
||||
try {
|
||||
const files: Record<string, Uint8Array> = {};
|
||||
for (const log of logs) {
|
||||
const content = await getLogContent(jobId, log.filename);
|
||||
files[log.filename] = strToU8(content);
|
||||
}
|
||||
const zipped = zipSync(files);
|
||||
const blob = new Blob([zipped as unknown as ArrayBuffer], { type: "application/zip" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `${jobComment || jobId}_logs.zip`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
} finally {
|
||||
setIsDownloading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const formatFileSize = (bytes: number): string => {
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`;
|
||||
@@ -157,16 +181,29 @@ export const LogsModal = ({
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} title={t("cronjobs.viewLogs")} size="xl">
|
||||
<div className="flex flex-col h-[600px]">
|
||||
<div className="flex items-center justify-between mb-4 pb-4 border-b border-border">
|
||||
<div>
|
||||
<h3 className="font-semibold text-lg">{jobComment || jobId}</h3>
|
||||
<div className="block sm:flex items-center justify-between mb-4 pb-4 border-b border-border">
|
||||
<div className="min-w-0 mb-4 sm:mb-0">
|
||||
<h3 className="font-semibold text-lg truncate">{jobComment || jobId}</h3>
|
||||
{stats && (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{stats.count} {t("cronjobs.logs")} • {stats.totalSizeMB} MB
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex gap-2 flex-shrink-0">
|
||||
<Button
|
||||
onClick={handleDownloadLogs}
|
||||
disabled={logs.length === 0 || isDownloading}
|
||||
className="btn-primary glow-primary"
|
||||
size="sm"
|
||||
>
|
||||
{isDownloading ? (
|
||||
<ArrowsClockwiseIcon className="w-4 h-4 sm:mr-2 animate-spin" />
|
||||
) : (
|
||||
<DownloadIcon className="w-4 h-4 sm:mr-2" />
|
||||
)}
|
||||
<span className="hidden sm:inline">{t("cronjobs.downloadLog")}</span>
|
||||
</Button>
|
||||
<Button
|
||||
onClick={loadLogs}
|
||||
disabled={isLoadingLogs}
|
||||
@@ -174,10 +211,10 @@ export const LogsModal = ({
|
||||
size="sm"
|
||||
>
|
||||
<ArrowsClockwiseIcon
|
||||
className={`w-4 h-4 mr-2 ${isLoadingLogs ? "animate-spin" : ""
|
||||
className={`w-4 h-4 sm:mr-2 ${isLoadingLogs ? "animate-spin" : ""
|
||||
}`}
|
||||
/>
|
||||
{t("common.refresh")}
|
||||
<span className="hidden sm:inline">{t("common.refresh")}</span>
|
||||
</Button>
|
||||
{logs.length > 0 && (
|
||||
<Button
|
||||
@@ -185,15 +222,15 @@ export const LogsModal = ({
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
>
|
||||
<TrashIcon className="w-4 h-4 mr-2" />
|
||||
{t("cronjobs.deleteAll")}
|
||||
<TrashIcon className="w-4 h-4 sm:mr-2" />
|
||||
<span className="hidden sm:inline">{t("cronjobs.deleteAll")}</span>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 flex gap-4 overflow-hidden">
|
||||
<div className="w-1/3 flex flex-col border-r border-border pr-4 overflow-hidden">
|
||||
<div className="flex-1 flex flex-col sm:flex-row gap-4 overflow-hidden">
|
||||
<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">
|
||||
<h4 className="font-semibold mb-2">{t("cronjobs.logFiles")}</h4>
|
||||
<div className="flex-1 overflow-y-auto space-y-2">
|
||||
{isLoadingLogs ? (
|
||||
@@ -288,8 +325,8 @@ export const LogsModal = ({
|
||||
|
||||
<div className="mt-4 pt-4 border-t border-border flex justify-end">
|
||||
<Button onClick={onClose} className="btn-primary glow-primary">
|
||||
<XIcon className="w-4 h-4 mr-2" />
|
||||
{t("common.close")}
|
||||
<XIcon className="w-4 h-4 sm:mr-2" />
|
||||
<span className="hidden sm:inline">{t("common.close")}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState, type JSX } from "react";
|
||||
|
||||
type BeforeInstallPromptEvent = Event & {
|
||||
prompt: () => Promise<void>;
|
||||
|
||||
@@ -13,7 +13,10 @@ export const ServiceWorkerRegister = (): null => {
|
||||
r.scope.endsWith("/")
|
||||
);
|
||||
if (alreadyRegistered) return;
|
||||
await navigator.serviceWorker.register("/sw.js", { scope: "/" });
|
||||
await navigator.serviceWorker.register("/serwist/sw.js", {
|
||||
scope: "/",
|
||||
updateViaCache: "none",
|
||||
});
|
||||
} catch (_err) {}
|
||||
};
|
||||
register();
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"logs": "logs",
|
||||
"logFiles": "Log Files",
|
||||
"logContent": "Log Content",
|
||||
"downloadLog": "Download",
|
||||
"selectLogToView": "Select a log file to view its content",
|
||||
"noLogsFound": "No logs found for this job",
|
||||
"confirmDeleteLog": "Are you sure you want to delete this log file?",
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
"logs": "log",
|
||||
"logFiles": "File",
|
||||
"logContent": "Contenuto Log",
|
||||
"downloadLog": "Scarica",
|
||||
"selectLogToView": "Seleziona un file per visualizzarne il contenuto",
|
||||
"noLogsFound": "Nessun log trovato per questa operazione",
|
||||
"confirmDeleteLog": "Sei sicuro di voler eliminare questo file?",
|
||||
|
||||
@@ -4,10 +4,8 @@ import { executeJob } from "@/app/_server/actions/cronjobs";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
export async function GET(request: NextRequest, props: { params: Promise<{ id: string }> }) {
|
||||
const params = await props.params;
|
||||
const authError = await requireAuth(request);
|
||||
if (authError) return authError;
|
||||
|
||||
|
||||
@@ -8,10 +8,8 @@ import {
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
export async function GET(request: NextRequest, props: { params: Promise<{ id: string }> }) {
|
||||
const params = await props.params;
|
||||
const authError = await requireAuth(request);
|
||||
if (authError) return authError;
|
||||
|
||||
@@ -40,10 +38,8 @@ export async function GET(
|
||||
}
|
||||
}
|
||||
|
||||
export async function PATCH(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
export async function PATCH(request: NextRequest, props: { params: Promise<{ id: string }> }) {
|
||||
const params = await props.params;
|
||||
const authError = await requireAuth(request);
|
||||
if (authError) return authError;
|
||||
|
||||
@@ -79,10 +75,8 @@ export async function PATCH(
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
) {
|
||||
export async function DELETE(request: NextRequest, props: { params: Promise<{ id: string }> }) {
|
||||
const params = await props.params;
|
||||
const authError = await requireAuth(request);
|
||||
if (authError) return authError;
|
||||
|
||||
|
||||
@@ -260,4 +260,20 @@ pre::-webkit-scrollbar {
|
||||
max-width: 100%;
|
||||
margin: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-shrinker {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.sidebar-shrinker:before {
|
||||
content: '';
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-top: 6px solid var(--box-border-color, var(--foreground2));
|
||||
border-right: 12px solid transparent;
|
||||
position: absolute;
|
||||
right: -1px;
|
||||
bottom: -6px;
|
||||
z-index: -1;
|
||||
}
|
||||
@@ -33,7 +33,7 @@ export const metadata: Metadata = {
|
||||
icons: {
|
||||
icon: "/favicon.png",
|
||||
shortcut: "/logo.png",
|
||||
apple: "/logo.png",
|
||||
apple: "/logo-pwa.png",
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -81,7 +81,9 @@ export default async function Home() {
|
||||
Cr<span className="text-status-error">*</span>nMaster
|
||||
</h1>
|
||||
<p className="text-xs terminal-font flex items-center gap-2">
|
||||
{t("common.version").replace("{version}", version)}
|
||||
<a href={`https://github.com/fccview/cronmaster/releases/tag/${version}`} target="_blank" rel="noopener noreferrer">
|
||||
{t("common.version").replace("{version}", version)}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
7
app/serwist/[path]/route.ts
Normal file
7
app/serwist/[path]/route.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { createSerwistRoute } from "@serwist/turbopack";
|
||||
|
||||
const { GET } = createSerwistRoute({
|
||||
swSrc: "app/sw.ts",
|
||||
});
|
||||
|
||||
export { GET };
|
||||
89
app/sw.ts
Normal file
89
app/sw.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import type { PrecacheEntry, SerwistGlobalConfig } from "serwist";
|
||||
import {
|
||||
Serwist,
|
||||
NetworkFirst,
|
||||
CacheableResponsePlugin,
|
||||
ExpirationPlugin,
|
||||
StaleWhileRevalidate,
|
||||
CacheFirst,
|
||||
} from "serwist";
|
||||
import { cleanupOutdatedCaches } from "serwist/internal";
|
||||
|
||||
declare global {
|
||||
interface WorkerGlobalScope extends SerwistGlobalConfig {
|
||||
__SW_MANIFEST: (PrecacheEntry | string)[] | undefined;
|
||||
}
|
||||
}
|
||||
|
||||
declare const self: WorkerGlobalScope;
|
||||
|
||||
const pageStrategy = new NetworkFirst({
|
||||
cacheName: "pages",
|
||||
matchOptions: { ignoreVary: true },
|
||||
networkTimeoutSeconds: 5,
|
||||
plugins: [
|
||||
new CacheableResponsePlugin({ statuses: [200] }),
|
||||
new ExpirationPlugin({
|
||||
maxEntries: 128,
|
||||
maxAgeSeconds: 30 * 24 * 60 * 60,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
cleanupOutdatedCaches();
|
||||
|
||||
const serwist = new Serwist({
|
||||
precacheEntries: self.__SW_MANIFEST,
|
||||
skipWaiting: true,
|
||||
clientsClaim: true,
|
||||
navigationPreload: false,
|
||||
runtimeCaching: [
|
||||
{
|
||||
matcher: ({ request }) => request.mode === "navigate",
|
||||
handler: pageStrategy,
|
||||
},
|
||||
{
|
||||
matcher: ({ request, sameOrigin }) =>
|
||||
request.headers.get("RSC") === "1" && sameOrigin,
|
||||
handler: pageStrategy,
|
||||
},
|
||||
{
|
||||
matcher: /\/_next\/static.+\.js$/i,
|
||||
handler: new CacheFirst({
|
||||
cacheName: "static-js",
|
||||
plugins: [
|
||||
new ExpirationPlugin({
|
||||
maxEntries: 64,
|
||||
maxAgeSeconds: 30 * 24 * 60 * 60,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
matcher: /\.(?:css|woff|woff2|ttf|eot|otf)$/i,
|
||||
handler: new StaleWhileRevalidate({
|
||||
cacheName: "static-assets",
|
||||
plugins: [
|
||||
new ExpirationPlugin({
|
||||
maxEntries: 64,
|
||||
maxAgeSeconds: 30 * 24 * 60 * 60,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
{
|
||||
matcher: /\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,
|
||||
handler: new StaleWhileRevalidate({
|
||||
cacheName: "images",
|
||||
plugins: [
|
||||
new ExpirationPlugin({
|
||||
maxEntries: 128,
|
||||
maxAgeSeconds: 30 * 24 * 60 * 60,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
serwist.addEventListeners();
|
||||
18
eslint.config.mjs
Normal file
18
eslint.config.mjs
Normal file
@@ -0,0 +1,18 @@
|
||||
import nextCoreWebVitals from "eslint-config-next/core-web-vitals";
|
||||
import nextTypescript from "eslint-config-next/typescript";
|
||||
|
||||
const eslintConfig = [
|
||||
...nextCoreWebVitals,
|
||||
...nextTypescript,
|
||||
{
|
||||
ignores: [
|
||||
"node_modules/**",
|
||||
".next/**",
|
||||
"out/**",
|
||||
"build/**",
|
||||
"next-env.d.ts",
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export default eslintConfig;
|
||||
3
next-env.d.ts
vendored
3
next-env.d.ts
vendored
@@ -1,5 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
const withPWA = require('next-pwa')({
|
||||
dest: 'public',
|
||||
register: true,
|
||||
skipWaiting: true,
|
||||
disable: process.env.NODE_ENV === 'development',
|
||||
buildExcludes: [/middleware-manifest\.json$/]
|
||||
})
|
||||
|
||||
const withNextIntl = require('next-intl/plugin')('./app/i18n.ts')
|
||||
|
||||
const nextConfig = {
|
||||
output: 'standalone',
|
||||
experimental: {
|
||||
serverComponentsExternalPackages: [],
|
||||
webpackBuildWorker: true
|
||||
},
|
||||
swcMinify: true,
|
||||
images: {
|
||||
unoptimized: true
|
||||
},
|
||||
webpack: (config, { dev, isServer }) => {
|
||||
config.resolve.alias = {
|
||||
...config.resolve.alias,
|
||||
'osx-temperature-sensor': false,
|
||||
};
|
||||
|
||||
if (dev && !isServer) {
|
||||
config.watchOptions = {
|
||||
...config.watchOptions,
|
||||
ignored: /node_modules/,
|
||||
};
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
source: '/manifest.json',
|
||||
headers: [
|
||||
{ key: 'Content-Type', value: 'application/manifest+json' },
|
||||
],
|
||||
},
|
||||
{
|
||||
source: '/sw.js',
|
||||
headers: [
|
||||
{ key: 'Service-Worker-Allowed', value: '/' },
|
||||
{ key: 'Cache-Control', value: 'no-cache' },
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = withPWA(withNextIntl(nextConfig))
|
||||
39
next.config.mjs
Normal file
39
next.config.mjs
Normal file
@@ -0,0 +1,39 @@
|
||||
import { withSerwist } from "@serwist/turbopack";
|
||||
import createNextIntlPlugin from "next-intl/plugin";
|
||||
|
||||
const withNextIntl = createNextIntlPlugin("./app/i18n.ts");
|
||||
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
output: "standalone",
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
webpack: (config, { dev, isServer }) => {
|
||||
config.resolve.alias = {
|
||||
...config.resolve.alias,
|
||||
"osx-temperature-sensor": false,
|
||||
};
|
||||
|
||||
if (dev && !isServer) {
|
||||
config.watchOptions = {
|
||||
...config.watchOptions,
|
||||
ignored: /node_modules/,
|
||||
};
|
||||
}
|
||||
|
||||
return config;
|
||||
},
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
source: "/manifest.json",
|
||||
headers: [
|
||||
{ key: "Content-Type", value: "application/manifest+json" },
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
export default withSerwist(withNextIntl(nextConfig));
|
||||
32
package.json
32
package.json
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "cronjob-manager",
|
||||
"version": "2.0.0",
|
||||
"version": "2.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.18.6",
|
||||
@@ -23,8 +23,8 @@
|
||||
"@fontsource/ibm-plex-mono": "^5.2.7",
|
||||
"@phosphor-icons/react": "^2.1.10",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"@types/react": "19.2.14",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"@webtui/css": "^0.1.5",
|
||||
"@webtui/theme-catppuccin": "^0.0.3",
|
||||
@@ -34,27 +34,37 @@
|
||||
"codemirror": "^6.0.2",
|
||||
"cron-parser": "^5.3.0",
|
||||
"cronstrue": "^3.2.0",
|
||||
"fflate": "^0.8.2",
|
||||
"jose": "^6.1.1",
|
||||
"minimatch": "^10.0.3",
|
||||
"next": "14.2.35",
|
||||
"next": "16.1.6",
|
||||
"next-intl": "^4.4.0",
|
||||
"next-pwa": "^5.6.0",
|
||||
"next-themes": "^0.2.1",
|
||||
"postcss": "^8",
|
||||
"proper-lockfile": "^4.1.2",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4",
|
||||
"react-syntax-highlighter": "^15.6.1",
|
||||
"systeminformation": "^5.27.11",
|
||||
"serwist": "^9.5.5",
|
||||
"systeminformation": "^5.27.14",
|
||||
"tailwind-merge": "^2.0.0",
|
||||
"tailwindcss": "^3.3.0",
|
||||
"typescript": "^5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@serwist/turbopack": "^9.5.5",
|
||||
"@types/bcryptjs": "^2.4.6",
|
||||
"@types/minimatch": "^6.0.0",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.0.4",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.1.6",
|
||||
"postcss-import": "^16.1.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"@isaacs/brace-expansion": "^5.0.1",
|
||||
"@types/react": "19.2.14",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"lodash": "^4.17.23",
|
||||
"prismjs": "^1.30.0",
|
||||
"systeminformation": "^5.27.14"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import type { NextRequest } from "next/server";
|
||||
|
||||
export const middleware = async (request: NextRequest) => {
|
||||
export const proxy = async (request: NextRequest) => {
|
||||
const { pathname } = request.nextUrl;
|
||||
|
||||
if (
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
BIN
public/logo-pwa.png
Normal file
BIN
public/logo-pwa.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
@@ -9,13 +9,13 @@
|
||||
"orientation": "portrait-primary",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/logo.png",
|
||||
"src": "/logo-pwa.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "/logo.png",
|
||||
"src": "/logo-pwa.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -3,7 +3,8 @@
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"es6"
|
||||
"es6",
|
||||
"webworker"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
@@ -14,7 +15,7 @@
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
@@ -26,13 +27,15 @@
|
||||
"@/*": [
|
||||
"./*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"target": "ES2017"
|
||||
},
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts"
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
|
||||
Reference in New Issue
Block a user