11 Commits

Author SHA1 Message Date
fccview
f098ded0c4 Merge pull request #77 from fccview/develop
Lift off
2026-01-01 14:28:29 +00:00
fccview
3ac9a5ca30 finish readme with latest screenshots 2026-01-01 14:22:21 +00:00
fccview
c708c013f3 fix modal on mobile and add proper screenshots 2026-01-01 14:17:11 +00:00
fccview
fb6531d00d fix few small issues, improve logo, add favicon 2026-01-01 13:56:52 +00:00
fccview
46e0838792 make standalone and fix fonts to work well with the new aesthetic 2026-01-01 11:36:50 +00:00
fccview
72f1c0a66d update screenshots 2026-01-01 09:37:34 +00:00
fccview
1adad49020 update minimal 2026-01-01 09:30:47 +00:00
fccview
b2dc0a3cb3 Merge pull request #68 from fccview/develop
Lift off!!
2025-12-14 09:09:41 +00:00
fccview
4beb7053f7 Merge branch 'develop' 2025-11-20 07:26:04 +00:00
fccview
d6b6aff44e Merge pull request #64 from fccview/develop
fix api auth issue
2025-11-20 07:25:41 +00:00
fccview
0ab3358e28 Merge pull request #60 from fccview/develop
Lift Off!
2025-11-19 20:46:15 +00:00
40 changed files with 273 additions and 165 deletions

View File

@@ -1,22 +1,7 @@
FROM node:20-slim AS base
RUN apt-get update && apt-get install -y \
pciutils \
curl \
iputils-ping \
util-linux \
ca-certificates \
gnupg \
lsb-release \
&& rm -rf /var/lib/apt/lists/*
RUN curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null \
&& apt-get update \
&& apt-get install -y docker-ce-cli \
&& rm -rf /var/lib/apt/lists/*
FROM node:20-alpine AS base
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
@@ -42,26 +27,27 @@ WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN groupadd --system --gid 1001 nodejs
RUN useradd --system --uid 1001 nextjs
RUN apk add --no-cache su-exec docker-cli pciutils curl iputils util-linux ca-certificates
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
RUN mkdir -p /app/scripts /app/data /app/snippets && \
chown -R nextjs:nodejs /app/scripts /app/data /app/snippets
RUN mkdir -p /app/.next/cache && \
chown -R nextjs:nodejs /app/.next
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
COPY --from=builder --chown=nextjs:nodejs /app/app ./app
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/yarn.lock ./yarn.lock
COPY --from=deps --chown=nextjs:nodejs /app/node_modules ./node_modules
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["yarn", "start"]
USER nextjs
CMD ["node", "server.js"]

View File

@@ -2,7 +2,7 @@
<img src="public/heading.png" width="400px">
</p>
## Table of Contents
## Quick links
- [Features](#features)
- [Quick Start](#quick-start)
@@ -18,9 +18,16 @@
- [Managing Cron Jobs](#managing-cron-jobs)
- [Job Execution Logging](#job-execution-logging)
- [Managing Scripts](#managing-scripts)
- [Technologies Used](#technologies-used)
---
<div align="center">
| Desktop | Mobile |
|---------|--------|
| ![Dark Mode Desktop](screenshots/home-dark.png) | ![Dark Mode Mobile](screenshots/home-dark-mobile.png) |
| ![Light Mode Desktop](screenshots/home-light.png) | ![Light Mode Mobile](screenshots/home-light-mobile.png) |
</div>
## Features
@@ -49,26 +56,13 @@
<br />
</p>
---
<br />
## Before we start
Hey there! 👋 Just a friendly heads-up: I'm a big believer in open source and love sharing my work with the community. Everything you find in my GitHub repos is and always will be 100% free. If someone tries to sell you a "premium" version of any of my projects while claiming to be me, please know that this is not legitimate. 🚫
If you find my projects helpful and want to fuel my late-night coding sessions with caffeine, I'd be super grateful for any support! ☕
<p align="center">
<a href="https://www.buymeacoffee.com/fccview">
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy me a coffee" width="150">
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy me a coffee" width="120">
</a>
</p>
<div align="center">
<img width="500px" src="screenshots/home.png">
<img width="500px" src="screenshots/live-running.png" />
</div>
---
<a id="quick-start"></a>

View File

@@ -236,7 +236,7 @@ export const CronJobList = ({ cronJobs, scripts }: CronJobListProps) => {
<CardHeader>
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
<div className="flex items-center gap-3">
<div className="p-2 bg-primary/10 rounded-lg">
<div className="p-2 bg-primary/10 ascii-border">
<ClockIcon className="h-5 w-5 text-primary" />
</div>
<div>
@@ -301,7 +301,7 @@ export const CronJobList = ({ cronJobs, scripts }: CronJobListProps) => {
onNewTaskClick={() => setIsNewCronModalOpen(true)}
/>
) : (
<div className="space-y-4 max-h-[55vh] min-h-[55vh] overflow-y-auto tui-scrollbar pr-2">
<div className="space-y-4 max-h-[55vh] min-h-[55vh] overflow-y-auto tui-scrollbar pr-1">
{loadedSettings ? (
filteredJobs.map((job) =>
minimalMode ? (

View File

@@ -148,7 +148,7 @@ export const CronJobItem = ({
return (
<div
key={job.id}
className={`tui-card p-4 terminal-font transition-colors ${isDropdownOpen ? "relative z-10" : ""
className={`border border-border lg:tui-card p-4 terminal-font transition-colors ${isDropdownOpen ? "relative z-10" : ""
}`}
>
<div className="flex flex-col sm:flex-row sm:items-start gap-4">

View File

@@ -139,7 +139,7 @@ export const MinimalCronJobItem = ({
return (
<div
key={job.id}
className={`tui-card p-3 terminal-font transition-colors ${isDropdownOpen ? "relative z-10" : ""
className={`border border-border lg:tui-card p-3 terminal-font transition-colors ${isDropdownOpen ? "relative z-10" : ""
}`}
>
<div className="flex items-center gap-3">
@@ -238,7 +238,7 @@ export const MinimalCronJobItem = ({
size="sm"
onClick={() => onRun(job.id)}
disabled={runningJobId === job.id || job.paused}
className="btn-outline h-8 px-3"
className="btn-outline h-8 px-3 hidden md:flex"
title={t("cronjobs.runCronManually")}
aria-label={t("cronjobs.runCronManually")}
>
@@ -259,7 +259,7 @@ export const MinimalCronJobItem = ({
onPause(job.id);
}
}}
className="btn-outline h-8 px-3"
className="btn-outline h-8 px-3 hidden md:flex"
title={t("cronjobs.pauseCronJob")}
aria-label={t("cronjobs.pauseCronJob")}
>
@@ -280,7 +280,7 @@ export const MinimalCronJobItem = ({
onToggleLogging(job.id);
}
}}
className="btn-outline h-8 px-3"
className="btn-outline h-8 px-3 hidden md:flex"
title={
job.logsEnabled
? t("cronjobs.viewLogs")

View File

@@ -101,29 +101,11 @@ export const Sidebar = forwardRef<HTMLDivElement, SidebarProps>(
)}
</button>
<div className="p-4 ascii-border !border-t-0 border-l-0 !border-r-0 bg-background0">
<div
className={cn(
"flex items-center gap-3",
isCollapsed && "lg:justify-center"
)}
>
<div className="p-2 bg-background0 ascii-border flex-shrink-0">
<HardDrivesIcon className="h-4 w-4" />
</div>
{(!isCollapsed || !isCollapsed) && (
<h2 className="text-sm font-semibold truncate terminal-font">
{t("sidebar.systemOverview")}
</h2>
)}
</div>
</div>
<div
className={cn(
"overflow-y-auto tui-scrollbar",
isCollapsed ? "lg:p-2" : "p-4",
"h-full lg:h-[calc(100vh-88px-80px)]"
"h-full lg:h-[calc(100vh-88px)]"
)}
>
{isCollapsed ? (

View File

@@ -28,7 +28,7 @@ export const TabbedInterface = ({
<div className="flex gap-2">
<button
onClick={() => setActiveTab("cronjobs")}
className={`flex items-center gap-2 px-4 py-2 text-sm font-medium flex-1 justify-center terminal-font ${activeTab === "cronjobs"
className={`flex items-center gap-2 px-4 py-2 border border-transparent text-sm font-medium flex-1 justify-center terminal-font ${activeTab === "cronjobs"
? "bg-background0 ascii-border"
: "hover:ascii-border"
}`}
@@ -41,7 +41,7 @@ export const TabbedInterface = ({
</button>
<button
onClick={() => setActiveTab("scripts")}
className={`flex items-center gap-2 px-4 py-2 text-sm font-medium flex-1 justify-center terminal-font ${activeTab === "scripts"
className={`flex items-center gap-2 px-4 py-2 border border-transparent text-sm font-medium flex-1 justify-center terminal-font ${activeTab === "scripts"
? "bg-background0 ascii-border"
: "hover:ascii-border"
}`}

View File

@@ -94,14 +94,14 @@ export const FiltersModal = ({
</Button>
{isScheduleDropdownOpen && (
<div className="absolute top-full left-0 right-0 mt-1 bg-background0 border border-border rounded-md shadow-lg z-50 min-w-[140px]">
<div className="absolute top-full left-0 p-1 right-0 mt-1 bg-background0 border border-border rounded-md shadow-lg z-50 min-w-[140px]">
<button
onClick={() => {
setLocalScheduleMode("cron");
setIsScheduleDropdownOpen(false);
}}
className={`w-full text-left px-3 py-2 text-sm hover:bg-accent transition-colors flex items-center gap-2 ${localScheduleMode === "cron"
? "bg-accent text-accent-foreground"
className={`w-full text-left px-3 border py-2 text-sm hover:border-border border-transparent transition-colors flex items-center gap-2 ${localScheduleMode === "cron"
? "border-border"
: ""
}`}
>
@@ -113,8 +113,8 @@ export const FiltersModal = ({
setLocalScheduleMode("human");
setIsScheduleDropdownOpen(false);
}}
className={`w-full text-left px-3 py-2 text-sm hover:bg-accent transition-colors flex items-center gap-2 ${localScheduleMode === "human"
? "bg-accent text-accent-foreground"
className={`w-full text-left px-3 py-2 border text-sm hover:border-border border-transparent transition-colors flex items-center gap-2 ${localScheduleMode === "human"
? "border-border"
: ""
}`}
>
@@ -126,8 +126,8 @@ export const FiltersModal = ({
setLocalScheduleMode("both");
setIsScheduleDropdownOpen(false);
}}
className={`w-full text-left px-3 py-2 text-sm hover:bg-accent transition-colors flex items-center gap-2 ${localScheduleMode === "both"
? "bg-accent text-accent-foreground"
className={`w-full text-left px-3 py-2 border text-sm hover:border-border border-transparent transition-colors flex items-center gap-2 ${localScheduleMode === "both"
? "border-border"
: ""
}`}
>

View File

@@ -348,8 +348,8 @@ export const LiveLogModal = ({
</div>
)}
<div className="bg-black/90 dark:bg-black/60 p-4 max-h-[60vh] overflow-auto terminal-font ascii-border">
<pre className="text-xs font-mono text-status-success whitespace-pre-wrap break-words">
<div className="bg-background0 p-4 max-h-[60vh] overflow-auto terminal-font ascii-border">
<pre className="text-xs text-status-success whitespace-pre-wrap break-words">
{logContent || t("cronjobs.waitingForJobToStart")}
<div ref={logEndRef} />
</pre>

View File

@@ -86,14 +86,15 @@ export const RestoreBackupModal = ({
size="xl"
>
<div className="space-y-4">
<div className="flex gap-2">
<div className="flex flex-col sm:flex-row gap-2">
<Button
variant="outline"
onClick={onBackupAll}
className="btn-outline flex-1"
>
<DownloadIcon className="h-4 w-4 mr-2" />
{t("cronjobs.backupAll")}
<DownloadIcon className="h-4 w-4 sm:mr-2" />
<span className="hidden sm:inline">{t("cronjobs.backupAll")}</span>
<span className="sm:hidden">Backup</span>
</Button>
{backups.length > 0 && (
<Button
@@ -101,17 +102,19 @@ export const RestoreBackupModal = ({
onClick={handleRestoreAll}
className="btn-primary flex-1"
>
<UploadIcon className="h-4 w-4 mr-2" />
{t("cronjobs.restoreAll")}
<UploadIcon className="h-4 w-4 sm:mr-2" />
<span className="hidden sm:inline">{t("cronjobs.restoreAll")}</span>
<span className="sm:hidden">Restore</span>
</Button>
)}
<Button
variant="outline"
onClick={onRefresh}
className="btn-outline"
className="btn-outline sm:w-auto"
title={t("common.refresh")}
>
<ArrowsClockwiseIcon className="h-4 w-4" />
<span className="sm:hidden ml-2">Refresh</span>
</Button>
</div>
@@ -126,7 +129,72 @@ export const RestoreBackupModal = ({
key={backup.filename}
className="tui-card p-3 terminal-font"
>
<div className="flex items-center gap-3">
<div className="flex flex-col gap-3 lg:hidden">
<div className="flex items-center justify-between">
<code className="text-xs bg-background0 text-status-warning px-1.5 py-0.5 terminal-font ascii-border">
{backup.job.schedule}
</code>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={() => {
onRestore(backup.filename);
onClose();
}}
className="btn-outline h-8 px-3"
title={t("cronjobs.restoreThisBackup")}
>
<UploadIcon className="h-3 w-3" />
</Button>
<Button
variant="destructive"
size="sm"
onClick={() => handleDelete(backup.filename)}
disabled={deletingFilename === backup.filename}
className="h-8 px-3"
title={t("cronjobs.deleteBackup")}
>
{deletingFilename === backup.filename ? (
<div className="h-3 w-3 animate-spin rounded-full border-2 border-current border-t-transparent" />
) : (
<TrashIcon className="h-3 w-3" />
)}
</Button>
</div>
</div>
<div className="flex items-center gap-2">
{commandCopied === backup.filename && (
<CheckIcon className="h-3 w-3 text-status-success flex-shrink-0" />
)}
<pre
onClick={(e) => {
e.stopPropagation();
copyToClipboard(unwrapCommand(backup.job.command));
setCommandCopied(backup.filename);
setTimeout(() => setCommandCopied(null), 3000);
}}
className="max-w-full overflow-x-auto flex-1 cursor-pointer text-sm font-medium terminal-font bg-background1 px-2 py-1 ascii-border break-all"
title={unwrapCommand(backup.job.command)}
>
{unwrapCommand(backup.job.command)}
</pre>
</div>
<div className="flex flex-wrap items-center gap-3 text-xs text-muted-foreground">
<div className="flex items-center gap-1">
<UserIcon className="h-3 w-3" />
<span>{backup.job.user}</span>
</div>
<div className="flex items-center gap-1">
<CalendarIcon className="h-3 w-3" />
<span>{formatDate(backup.backedUpAt)}</span>
</div>
</div>
</div>
<div className="hidden lg:flex items-center gap-3">
<div className="flex-shrink-0">
<code className="text-xs bg-background0 text-status-warning px-1.5 py-0.5 terminal-font ascii-border">
{backup.job.schedule}
@@ -204,11 +272,11 @@ export const RestoreBackupModal = ({
</div>
)}
<div className="flex justify-between gap-2 pt-4 border-t border-border">
<p className="text-sm text-muted-foreground">
<div className="flex flex-col sm:flex-row sm:justify-between gap-2 pt-4 border-t border-border">
<p className="text-sm text-muted-foreground text-center sm:text-left">
{t("cronjobs.availableBackups")}: {backups.length}
</p>
<Button variant="outline" onClick={onClose} className="btn-outline">
<Button variant="outline" onClick={onClose} className="btn-outline w-full sm:w-auto">
{t("common.close")}
</Button>
</div>

View File

@@ -16,7 +16,6 @@ export const PWAInstallPrompt = (): JSX.Element | null => {
useEffect(() => {
if (typeof window === "undefined") return;
const onBeforeInstallPrompt = (e: Event) => {
e.preventDefault();
setDeferred(e as BeforeInstallPromptEvent);
};
const onAppInstalled = () => {
@@ -49,10 +48,10 @@ export const PWAInstallPrompt = (): JSX.Element | null => {
return (
<button
className="px-3 py-1 rounded-md border border-border bg-background/80 hover:bg-background/60"
className="px-3 py-2 ascii-border bg-background0 hover:bg-background1 transition-colors terminal-font text-sm"
onClick={onInstall}
>
Install App
Install
</button>
);
};

View File

@@ -180,8 +180,8 @@ export const BashSnippetHelper = ({
{snippet.title}
</h4>
{snippet.source === "user" && (
<span className="inline-block px-1.5 py-0.5 text-xs bg-green-100 text-green-700 rounded border border-green-200">
UserIcon
<span className="inline-block px-1.5 py-0.5 text-xs text-status-success border border-border">
User
</span>
)}
</div>

View File

@@ -2,6 +2,7 @@
import { useTheme } from 'next-themes';
import { useEffect, useState } from 'react';
import { SunIcon, MoonIcon } from '@phosphor-icons/react';
export const ThemeToggle = () => {
const [mounted, setMounted] = useState(false);
@@ -18,9 +19,15 @@ export const ThemeToggle = () => {
return (
<button
onClick={() => setTheme(isDark ? 'light' : 'dark')}
className="px-3 py-2 ascii-border terminal-font text-sm bg-background0"
className="p-2 ascii-border bg-background0 hover:bg-background1 transition-colors"
aria-label={isDark ? 'Switch to light mode' : 'Switch to dark mode'}
title={isDark ? 'Switch to light mode' : 'Switch to dark mode'}
>
{isDark ? 'LIGHT' : 'DARK'}
{isDark ? (
<SunIcon size={20} weight="regular" className="text-foreground" />
) : (
<MoonIcon size={20} weight="regular" className="text-foreground" />
)}
</button>
);
};

View File

@@ -79,13 +79,13 @@ export const UserFilter = ({
</div>
{isOpen && (
<div className="absolute top-full left-0 right-0 mt-1 bg-background0 border border-border rounded-md shadow-lg z-50 max-h-48 overflow-y-auto tui-scrollbar">
<div className="absolute top-full left-0 right-0 p-1 mt-1 bg-background0 border border-border rounded-md shadow-lg z-50 max-h-48 overflow-y-auto tui-scrollbar">
<button
onClick={() => {
onUserChange(null);
setIsOpen(false);
}}
className={`w-full text-left px-3 py-2 text-sm hover:bg-accent transition-colors ${!selectedUser ? "bg-accent text-accent-foreground" : ""
className={`w-full text-left px-3 py-2 text-sm hover:border-border transition-colors ${!selectedUser ? "border border-border" : "border border-transparent"
}`}
>
{t("common.allUsers")}
@@ -97,7 +97,7 @@ export const UserFilter = ({
onUserChange(user);
setIsOpen(false);
}}
className={`w-full text-left px-3 py-2 text-sm hover:bg-accent transition-colors ${selectedUser === user ? "bg-accent text-accent-foreground" : ""
className={`w-full text-left px-3 py-2 text-sm border border-transparent hover:border-border transition-colors ${selectedUser === user ? "border border-border" : "border border-transparent"
}`}
>
{user}

View File

@@ -80,7 +80,7 @@ export const UserSwitcher = ({
onUserChange(user);
setIsOpen(false);
}}
className={`w-full text-left px-3 py-2 text-sm hover:bg-accent transition-colors ${selectedUser === user ? "bg-accent text-accent-foreground" : ""
className={`w-full text-left px-3 py-2 text-sm hover:bg-accent transition-colors ${selectedUser === user ? "border border-border" : "border border-transparent"
}`}
>
{user}

View File

@@ -7,10 +7,10 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className = '', variant = 'default', size = 'default', children, ...props }, ref) => {
const baseClasses = 'terminal-font ascii-border px-4 py-2 cursor-pointer inline-flex items-center justify-center disabled:opacity-50 disabled:cursor-not-allowed';
const baseClasses = 'terminal-font border border-border px-4 py-2 cursor-pointer inline-flex items-center justify-center disabled:opacity-50 disabled:cursor-not-allowed';
const variantClasses = {
default: 'bg-background1 hover:bg-background2',
destructive: 'bg-status-error text-white hover:bg-status-error',
destructive: 'text-status-error hover:bg-status-error hover:text-white',
outline: 'bg-background0 hover:bg-background1',
secondary: 'bg-background2 hover:bg-background1',
ghost: 'border-0 bg-transparent hover:bg-background1',

View File

@@ -101,17 +101,17 @@ export const DropdownMenu = ({
className={`absolute right-0 w-56 ascii-border bg-background0 shadow-lg z-[9999] overflow-hidden terminal-font ${positionAbove ? "bottom-full mb-2" : "top-full mt-2"
}`}
>
<div className="py-1">
<div className="p-1">
{items.map((item, index) => (
<button
key={index}
onClick={() => handleItemClick(item)}
disabled={item.disabled}
className={`w-full flex items-center gap-3 px-4 py-2 text-sm transition-colors ${item.disabled
className={`w-full flex items-center border border-transparent gap-3 px-4 py-2 text-sm transition-colors ${item.disabled
? "opacity-50 cursor-not-allowed"
: item.variant === "destructive"
? "text-status-error hover:bg-background1"
: "hover:bg-background1"
? "text-status-error hover:border hover:border-border"
: "hover:border-border"
}`}
>
{item.icon && (

View File

@@ -52,7 +52,7 @@ export const Modal = ({
return (
<dialog
ref={dialogRef}
className={`ascii-border terminal-font bg-background0 ${sizeClasses[size]} max-w-[95vw] ${className}`}
className={`ascii-border terminal-font bg-background0 mobile-modal ${sizeClasses[size]} max-w-[95vw] ${className}`}
onClick={(e) => {
if (e.target === dialogRef.current && !preventCloseOnClickOutside) {
onClose();

View File

@@ -30,6 +30,45 @@
}
}
body {
font-family: 'IBM Plex Mono', monospace !important;
font-weight: 500 !important;
}
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: 'Azeret Mono Variable', monospace !important;
}
p,
span,
a,
label,
input,
textarea,
button,
select,
option {
font-family: 'IBM Plex Mono', monospace !important;
font-weight: 500 !important;
}
pre {
scrollbar-width: none !important;
-ms-overflow-style: none !important;
}
pre::-webkit-scrollbar {
display: none !important;
width: 0 !important;
height: 0 !important;
}
@layer utilities {
.ascii-border {
border: 1px solid var(--box-border-color, var(--foreground2));
@@ -37,12 +76,18 @@
}
.border-border {
border-color: var(--box-border-color, var(--foreground2))!important;
border-color: var(--box-border-color, var(--foreground2)) !important;
}
.tui-scrollbar {
scrollbar-width: auto !important;
padding-right: 15px;
padding-right: 5px;
}
@media (min-width: 992px) {
.tui-scrollbar {
padding-right: 15px;
}
}
.tui-scrollbar::-webkit-scrollbar {
@@ -182,7 +227,6 @@
flex-direction: column;
}
/* Sidebar layout adjustment */
.no-sidebar main {
margin-left: 0 !important;
}
@@ -205,3 +249,15 @@
}
}
}
@media (max-width: 992px) {
.mobile-modal {
position: fixed;
bottom: 0;
top: auto;
width: 100% !important;
max-height: 90vh;
max-width: 100%;
margin: inherit;
}
}

View File

@@ -4,6 +4,10 @@ import "@/app/globals.css";
import { ThemeProvider } from "@/app/_providers/ThemeProvider";
import { ServiceWorkerRegister } from "@/app/_components/FeatureComponents/PWA/ServiceWorkerRegister";
import { loadTranslationMessages } from "@/app/_server/actions/translations";
import '@fontsource/ibm-plex-mono/400.css';
import '@fontsource/ibm-plex-mono/500.css';
import '@fontsource/ibm-plex-mono/600.css';
import '@fontsource-variable/azeret-mono';
import { NextIntlClientProvider } from "next-intl";
@@ -27,7 +31,7 @@ export const metadata: Metadata = {
telephone: false,
},
icons: {
icon: "/logo.png",
icon: "/favicon.png",
shortcut: "/logo.png",
apple: "/logo.png",
},

View File

@@ -78,7 +78,7 @@ export default async function Home() {
<Logo size={48} showGlow={true} />
<div>
<h1 className="text-xl sm:text-2xl lg:text-3xl font-bold terminal-font uppercase">
Cr*nMaster
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)}

View File

@@ -2,7 +2,7 @@
CronMaster supports internationalization (i18n) with both **unofficial custom translations** and **official translations** that can be contributed to the project.
## Table of Contents
## Quick links
- [Custom User Translations (Unofficial)](#custom-user-translations-unofficial)
- [Official Translations via Pull Request](#official-translations-via-pull-request)

View File

@@ -1,49 +1,55 @@
const withNextIntl = require('next-intl/plugin')('./app/i18n.ts');
const withPWA = require('next-pwa')({
dest: 'public',
register: true,
skipWaiting: true,
disable: process.env.NODE_ENV === 'development',
buildExcludes: [/middleware-manifest\.json$/]
dest: 'public',
register: true,
skipWaiting: true,
disable: process.env.NODE_ENV === 'development',
buildExcludes: [/middleware-manifest\.json$/]
})
/** @type {import('next').NextConfig} */
const withNextIntl = require('next-intl/plugin')('./app/i18n.ts')
const nextConfig = {
webpack: (config, { dev, isServer }) => {
config.resolve.alias = {
...config.resolve.alias,
'osx-temperature-sensor': false,
};
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/,
};
}
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' },
],
},
]
},
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 = withNextIntl({
...withPWA(nextConfig)
});
module.exports = withPWA(withNextIntl(nextConfig))

View File

@@ -19,6 +19,8 @@
"@codemirror/state": "^6.5.2",
"@codemirror/theme-one-dark": "^6.1.3",
"@codemirror/view": "^6.38.1",
"@fontsource-variable/azeret-mono": "^5.2.11",
"@fontsource/ibm-plex-mono": "^5.2.7",
"@phosphor-icons/react": "^2.1.10",
"@types/node": "^20",
"@types/react": "^18",
@@ -42,7 +44,6 @@
"proper-lockfile": "^4.1.2",
"react": "^18",
"react-dom": "^18",
"react-icons": "^5.5.0",
"react-syntax-highlighter": "^15.6.1",
"systeminformation": "^5.27.11",
"tailwind-merge": "^2.0.0",

BIN
public/favicon.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

View File

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 305 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
screenshots/home-dark.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
screenshots/home-light.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 355 KiB

BIN
screenshots/logs-view.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 364 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 KiB

View File

@@ -954,6 +954,16 @@
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.1.tgz#de633db3ec2ef6a3c89e2f19038063e8a122e2c2"
integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==
"@fontsource-variable/azeret-mono@^5.2.11":
version "5.2.11"
resolved "https://registry.yarnpkg.com/@fontsource-variable/azeret-mono/-/azeret-mono-5.2.11.tgz#b3292c862522a509f0929ebd22ab709c27ffe931"
integrity sha512-C2lkE7Pg0yg46ze3+AaJkolyldWBxluGiRG4pB5vHSdQRApOdQME6WbdsfYyy6wFDn/zPNx+1mwRJPnMRnovpA==
"@fontsource/ibm-plex-mono@^5.2.7":
version "5.2.7"
resolved "https://registry.yarnpkg.com/@fontsource/ibm-plex-mono/-/ibm-plex-mono-5.2.7.tgz#ef5b6f052115fdf6666208a5f8a0f13fcd7ba1fd"
integrity sha512-MKAb8qV+CaiMQn2B0dIi1OV3565NYzp3WN5b4oT6LTkk+F0jR6j0ZN+5BKJiIhffDC3rtBULsYZE65+0018z9w==
"@formatjs/ecma402-abstract@2.3.6":
version "2.3.6"
resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.6.tgz#d6ca9d3579054fe1e1a0a0b5e872e0d64922e4e1"
@@ -4301,11 +4311,6 @@ react-dom@^18:
loose-envify "^1.1.0"
scheduler "^0.23.2"
react-icons@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.5.0.tgz#8aa25d3543ff84231685d3331164c00299cdfaf2"
integrity sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==
react-is@^16.13.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"