start replacing all UI with terminal UI

This commit is contained in:
fccview
2025-12-31 20:09:11 +00:00
parent e40b0c0f63
commit ebce8f698b
47 changed files with 687 additions and 824 deletions

View File

@@ -108,12 +108,12 @@ export const CronJobItem = ({
},
...(job.logsEnabled
? [
{
label: t("cronjobs.viewLogs"),
icon: <FileText className="h-3 w-3" />,
onClick: () => onViewLogs(job),
},
]
{
label: t("cronjobs.viewLogs"),
icon: <FileText className="h-3 w-3" />,
onClick: () => onViewLogs(job),
},
]
: []),
{
label: job.paused
@@ -148,22 +148,21 @@ export const CronJobItem = ({
return (
<div
key={job.id}
className={`glass-card p-4 border border-border/50 rounded-lg hover:bg-accent/30 transition-colors ${
isDropdownOpen ? "relative z-10" : ""
}`}
className={`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">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-3 mb-2">
{(scheduleDisplayMode === "cron" ||
scheduleDisplayMode === "both") && (
<code className="text-sm bg-purple-500/10 text-purple-600 dark:text-purple-400 px-2 py-1 rounded font-mono border border-purple-500/20">
{job.schedule}
</code>
)}
<code className="text-sm bg-background0 text-status-warning px-2 py-1 terminal-font ascii-border">
{job.schedule}
</code>
)}
{scheduleDisplayMode === "human" && cronExplanation?.isValid && (
<div className="flex items-start gap-1.5 border-b border-primary/30 bg-primary/10 rounded text-primary px-2 py-0.5">
<Info className="h-3 w-3 text-primary mt-0.5 flex-shrink-0" />
<div className="flex items-start gap-1.5 ascii-border bg-background2 px-2 py-0.5">
<Info className="h-3 w-3 mt-0.5 flex-shrink-0" />
<p className="text-sm italic">
{cronExplanation.humanReadable}
</p>
@@ -172,7 +171,7 @@ export const CronJobItem = ({
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 min-w-0 w-full">
{commandCopied === job.id && (
<Check className="h-3 w-3 text-green-600" />
<Check className="h-3 w-3 text-status-success" />
)}
<pre
onClick={(e) => {
@@ -181,7 +180,7 @@ export const CronJobItem = ({
setCommandCopied(job.id);
setTimeout(() => setCommandCopied(null), 3000);
}}
className="w-full cursor-pointer overflow-x-auto text-sm font-medium text-foreground bg-muted/30 px-2 py-1 rounded border border-border/30 hide-scrollbar"
className="w-full cursor-pointer overflow-x-auto text-sm font-medium terminal-font bg-background1 px-2 py-1 ascii-border hide-scrollbar"
>
{unwrapCommand(displayCommand)}
</pre>
@@ -191,8 +190,8 @@ export const CronJobItem = ({
<div className="flex items-center gap-2 pb-2 pt-4">
{scheduleDisplayMode === "both" && cronExplanation?.isValid && (
<div className="flex items-start gap-1.5 border-b border-primary/30 bg-primary/10 rounded text-primary px-2 py-0.5">
<Info className="h-3 w-3 text-primary mt-0.5 flex-shrink-0" />
<div className="flex items-start gap-1.5 ascii-border bg-background2 px-2 py-0.5">
<Info className="h-3 w-3 mt-0.5 flex-shrink-0" />
<p className="text-xs italic">
{cronExplanation.humanReadable}
</p>
@@ -201,7 +200,7 @@ export const CronJobItem = ({
{job.comment && (
<p
className="text-xs text-muted-foreground italic truncate"
className="text-xs italic truncate"
title={job.comment}
>
{job.comment}
@@ -210,13 +209,13 @@ export const CronJobItem = ({
</div>
<div className="flex flex-wrap items-center gap-2 py-3">
<div className="flex items-center gap-1 text-xs bg-muted/50 text-muted-foreground px-2 py-0.5 rounded border border-border/30 cursor-pointer hover:bg-muted/70 transition-colors relative">
<div className="flex items-center gap-1 text-xs bg-background0 px-2 py-0.5 ascii-border cursor-pointer hover:bg-background2 transition-colors relative terminal-font">
<User className="h-3 w-3" />
<span>{job.user}</span>
</div>
<div
className="flex items-center gap-1 text-xs bg-muted/50 text-muted-foreground px-2 py-0.5 rounded border border-border/30 cursor-pointer hover:bg-muted/70 transition-colors relative"
className="flex items-center gap-1 text-xs bg-background0 px-2 py-0.5 ascii-border cursor-pointer hover:bg-background2 transition-colors relative terminal-font"
title="Click to copy Job UUID"
onClick={async () => {
const success = await copyToClipboard(job.id);
@@ -227,7 +226,7 @@ export const CronJobItem = ({
}}
>
{showCopyConfirmation ? (
<Check className="h-3 w-3 text-green-600" />
<Check className="h-3 w-3 text-status-success" />
) : (
<Hash className="h-3 w-3" />
)}
@@ -235,14 +234,14 @@ export const CronJobItem = ({
</div>
{job.paused && (
<span className="text-xs bg-yellow-500/10 text-yellow-600 dark:text-yellow-400 px-2 py-0.5 rounded border border-yellow-500/20">
{t("cronjobs.paused")}
<span className="text-xs bg-background2 px-2 py-0.5 ascii-border terminal-font">
<span className="text-status-warning">{t("cronjobs.paused")}</span>
</span>
)}
{job.logsEnabled && (
<span className="text-xs bg-blue-500/10 text-blue-600 dark:text-blue-400 px-2 py-0.5 rounded border border-blue-500/20">
{t("cronjobs.logged")}
<span className="text-xs bg-background0 px-2 py-0.5 ascii-border terminal-font">
<span className="text-status-info">{t("cronjobs.logged")}</span>
</span>
)}
@@ -252,11 +251,11 @@ export const CronJobItem = ({
e.stopPropagation();
onViewLogs(job);
}}
className="flex items-center gap-1 text-xs bg-red-500/10 text-red-600 dark:text-red-400 px-2 py-0.5 rounded border border-red-500/30 hover:bg-red-500/20 transition-colors cursor-pointer"
className="flex items-center gap-1 text-xs bg-background0 px-2 py-0.5 ascii-border hover:bg-background1 transition-colors cursor-pointer terminal-font"
title="Latest execution failed - Click to view error log"
>
<AlertCircle className="h-3 w-3" />
<span>
<AlertCircle className="h-3 w-3 text-status-error" />
<span className="text-status-error">
{t("cronjobs.failed", {
exitCode: job.logError?.exitCode?.toString() ?? "",
})}
@@ -272,12 +271,12 @@ export const CronJobItem = ({
e.stopPropagation();
onViewLogs(job);
}}
className="flex items-center gap-1 text-xs bg-yellow-500/10 text-yellow-600 dark:text-yellow-400 px-2 py-0.5 rounded border border-yellow-500/30 hover:bg-yellow-500/20 transition-colors cursor-pointer"
className="flex items-center gap-1 text-xs bg-background0 px-2 py-0.5 ascii-border hover:bg-background1 transition-colors cursor-pointer terminal-font"
title="Latest execution succeeded, but has historical failures - Click to view logs"
>
<CheckCircle className="h-3 w-3" />
<span>{t("cronjobs.healthy")}</span>
<AlertTriangle className="h-3 w-3" />
<CheckCircle className="h-3 w-3 text-status-success" />
<span className="text-status-warning">{t("cronjobs.healthy")}</span>
<AlertTriangle className="h-3 w-3 text-status-warning" />
</button>
)}
@@ -285,9 +284,9 @@ export const CronJobItem = ({
!job.logError?.hasError &&
!job.logError?.hasHistoricalFailures &&
job.logError?.latestExitCode === 0 && (
<div className="flex items-center gap-1 text-xs bg-green-500/10 text-green-600 dark:text-green-400 px-2 py-0.5 rounded border border-green-500/30">
<CheckCircle className="h-3 w-3" />
<span>{t("cronjobs.healthy")}</span>
<div className="flex items-center gap-1 text-xs bg-background0 px-2 py-0.5 ascii-border terminal-font">
<CheckCircle className="h-3 w-3 text-status-success" />
<span className="text-status-success">{t("cronjobs.healthy")}</span>
</div>
)}

View File

@@ -139,19 +139,19 @@ export const MinimalCronJobItem = ({
return (
<div
key={job.id}
className={`glass-card p-3 border border-border/50 rounded-lg hover:bg-accent/30 transition-colors ${isDropdownOpen ? "relative z-10" : ""
className={`tui-card p-3 terminal-font transition-colors ${isDropdownOpen ? "relative z-10" : ""
}`}
>
<div className="flex items-center gap-3">
<div className="flex items-center gap-1 flex-shrink-0">
{scheduleDisplayMode === "cron" && (
<code className="text-xs bg-purple-500/10 text-purple-600 dark:text-purple-400 px-1.5 py-0.5 rounded font-mono border border-purple-500/20">
<code className="text-xs bg-background2 px-1.5 py-0.5 terminal-font ascii-border">
{job.schedule}
</code>
)}
{scheduleDisplayMode === "human" && cronExplanation?.isValid && (
<div className="flex items-center gap-1 border-b border-primary/30 bg-primary/10 rounded text-primary px-1.5 py-0.5">
<Info className="h-3 w-3 text-primary flex-shrink-0" />
<div className="flex items-center gap-1 ascii-border bg-background2 px-1.5 py-0.5">
<Info className="h-3 w-3 flex-shrink-0" />
<span className="text-xs italic truncate max-w-32">
{cronExplanation.humanReadable}
</span>
@@ -159,15 +159,15 @@ export const MinimalCronJobItem = ({
)}
{scheduleDisplayMode === "both" && (
<div className="flex items-center gap-1">
<code className="text-xs bg-purple-500/10 text-purple-600 dark:text-purple-400 px-1 py-0.5 rounded font-mono border border-purple-500/20">
<code className="text-xs bg-background0 text-status-warning px-1 py-0.5 terminal-font ascii-border">
{job.schedule}
</code>
{cronExplanation?.isValid && (
<div
className="flex items-center gap-1 border-b border-primary/30 bg-primary/10 rounded text-primary px-1 py-0.5 cursor-help"
className="flex items-center gap-1 ascii-border bg-background0 px-1 py-0.5 cursor-help"
title={cronExplanation.humanReadable}
>
<Info className="h-2.5 w-2.5 text-primary flex-shrink-0" />
<Info className="h-2.5 w-2.5 flex-shrink-0" />
</div>
)}
</div>
@@ -177,7 +177,7 @@ export const MinimalCronJobItem = ({
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
{commandCopied === job.id && (
<Check className="h-3 w-3 text-green-600 flex-shrink-0" />
<Check className="h-3 w-3 text-status-success flex-shrink-0" />
)}
<pre
onClick={(e) => {
@@ -186,7 +186,7 @@ export const MinimalCronJobItem = ({
setCommandCopied(job.id);
setTimeout(() => setCommandCopied(null), 3000);
}}
className="flex-1 cursor-pointer overflow-hidden text-sm font-medium text-foreground bg-muted/30 px-2 py-1 rounded border border-border/30 truncate"
className="flex-1 cursor-pointer overflow-hidden text-sm font-medium terminal-font bg-background1 px-2 py-1 ascii-border truncate"
title={unwrapCommand(job.command)}
>
{unwrapCommand(displayCommand)}
@@ -197,25 +197,25 @@ export const MinimalCronJobItem = ({
<div className="flex items-center gap-1 flex-shrink-0">
{job.logsEnabled && (
<div
className="w-2 h-2 bg-blue-500 rounded-full"
className="w-2 h-2 bg-status-info ascii-border"
title={t("cronjobs.logged")}
/>
)}
{job.paused && (
<div
className="w-2 h-2 bg-yellow-500 rounded-full"
className="w-2 h-2 bg-status-warning ascii-border"
title={t("cronjobs.paused")}
/>
)}
{!job.logError?.hasError && job.logsEnabled && (
<div
className="w-2 h-2 bg-green-500 rounded-full"
className="w-2 h-2 bg-status-success ascii-border"
title={t("cronjobs.healthy")}
/>
)}
{job.logsEnabled && job.logError?.hasError && (
<div
className="w-2 h-2 bg-red-500 rounded-full cursor-pointer"
className="w-2 h-2 bg-status-error ascii-border cursor-pointer"
title="Latest execution failed - Click to view error log"
onClick={(e) => {
e.stopPropagation();
@@ -225,21 +225,22 @@ export const MinimalCronJobItem = ({
)}
{!job.logsEnabled && errors.length > 0 && (
<div
className="w-2 h-2 bg-orange-500 rounded-full cursor-pointer"
className="w-2 h-2 bg-status-warning ascii-border cursor-pointer"
title={`${errors.length} error(s)`}
onClick={(e) => onErrorClick(errors[0])}
/>
)}
</div>
<div className="flex items-center gap-1 flex-shrink-0">
<div className="flex items-center gap-2 flex-shrink-0">
<Button
variant="ghost"
variant="outline"
size="sm"
onClick={() => onRun(job.id)}
disabled={runningJobId === job.id || job.paused}
className="h-6 w-6 p-0"
className="btn-outline h-8 px-3"
title={t("cronjobs.runCronManually")}
aria-label={t("cronjobs.runCronManually")}
>
{runningJobId === job.id ? (
<div className="h-3 w-3 animate-spin rounded-full border-2 border-current border-t-transparent" />
@@ -249,7 +250,7 @@ export const MinimalCronJobItem = ({
</Button>
<Button
variant="ghost"
variant="outline"
size="sm"
onClick={() => {
if (job.paused) {
@@ -258,8 +259,9 @@ export const MinimalCronJobItem = ({
onPause(job.id);
}
}}
className="h-6 w-6 p-0"
className="btn-outline h-8 px-3"
title={t("cronjobs.pauseCronJob")}
aria-label={t("cronjobs.pauseCronJob")}
>
{job.paused ? (
<Play className="h-3 w-3" />
@@ -269,7 +271,7 @@ export const MinimalCronJobItem = ({
</Button>
<Button
variant="ghost"
variant="outline"
size="sm"
onClick={() => {
if (job.logsEnabled) {
@@ -278,12 +280,17 @@ export const MinimalCronJobItem = ({
onToggleLogging(job.id);
}
}}
className="h-6 w-6 p-0"
className="btn-outline h-8 px-3"
title={
job.logsEnabled
? t("cronjobs.viewLogs")
: t("cronjobs.enableLogging")
}
aria-label={
job.logsEnabled
? t("cronjobs.viewLogs")
: t("cronjobs.enableLogging")
}
>
{job.logsEnabled ? (
<FileText className="h-3 w-3" />

View File

@@ -54,7 +54,7 @@ export const Sidebar = forwardRef<HTMLDivElement, SidebarProps>(
<>
<button
onClick={() => setIsMobileOpen(!isMobileOpen)}
className="fixed bottom-4 right-4 z-50 lg:hidden p-2 bg-background/80 backdrop-blur-md border border-border/50 rounded-lg hover:bg-accent transition-colors"
className="fixed bottom-4 right-4 z-50 lg:hidden p-2 bg-background0 ascii-border transition-colors terminal-font"
>
{isMobileOpen ? (
<X className="h-5 w-5" />
@@ -65,7 +65,7 @@ export const Sidebar = forwardRef<HTMLDivElement, SidebarProps>(
<div
className={cn(
"fixed inset-0 bg-black/50 z-20 lg:hidden transition-opacity duration-300",
"fixed inset-0 bg-background0 z-20 lg:hidden transition-opacity duration-300",
isMobileOpen ? "opacity-100" : "opacity-0 pointer-events-none"
)}
onClick={() => setIsMobileOpen(false)}
@@ -74,7 +74,7 @@ export const Sidebar = forwardRef<HTMLDivElement, SidebarProps>(
<div
ref={ref}
className={cn(
"bg-background/95 backdrop-blur-md border-r border-border/50 transition-all duration-300 ease-in-out glass-card",
"bg-background0 ascii-border transition-all duration-300 ease-in-out terminal-font",
isMobileOpen
? "fixed left-0 top-0 h-full w-80 z-30 translate-x-0"
: "fixed left-0 top-0 h-full w-80 z-30 -translate-x-full lg:translate-x-0",
@@ -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-background border border-border rounded-full items-center justify-center hover:bg-accent transition-colors z-40 hidden lg:flex"
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"
>
{isCollapsed ? (
<ChevronRight className="h-3 w-3" />
@@ -101,18 +101,18 @@ export const Sidebar = forwardRef<HTMLDivElement, SidebarProps>(
)}
</button>
<div className="p-4 border-b border-border/50 bg-background/95 backdrop-blur-md">
<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-gradient-to-br from-cyan-500/20 to-blue-500/20 rounded-lg flex-shrink-0">
<Server className="h-4 w-4 text-cyan-500" />
<div className="p-2 bg-background0 ascii-border flex-shrink-0">
<Server className="h-4 w-4" />
</div>
{(!isCollapsed || !isCollapsed) && (
<h2 className="text-sm font-semibold text-foreground truncate">
<h2 className="text-sm font-semibold truncate terminal-font">
{t("sidebar.systemOverview")}
</h2>
)}
@@ -131,22 +131,22 @@ export const Sidebar = forwardRef<HTMLDivElement, SidebarProps>(
<div className="flex flex-col items-center space-y-4">
{quickStats ? (
<>
<div className="w-12 h-12 bg-card/50 border border-border/30 rounded-lg flex flex-col items-center justify-center p-1">
<Cpu className="h-3 w-3 text-pink-500 mb-1" />
<div className="w-12 h-12 bg-background0 ascii-border flex flex-col items-center justify-center p-1">
<Cpu className="h-3 w-3 mb-1" />
<span className="text-xs font-bold text-foreground">
{quickStats.cpu}%
</span>
</div>
<div className="w-12 h-12 bg-card/50 border border-border/30 rounded-lg flex flex-col items-center justify-center p-1">
<HardDrive className="h-3 w-3 text-cyan-500 mb-1" />
<div className="w-12 h-12 bg-background0 ascii-border flex flex-col items-center justify-center p-1">
<HardDrive className="h-3 w-3 mb-1" />
<span className="text-xs font-bold text-foreground">
{quickStats.memory}%
</span>
</div>
<div className="w-12 h-12 bg-card/50 border border-border/30 rounded-lg flex flex-col items-center justify-center p-1">
<Wifi className="h-3 w-3 text-teal-500 mb-1" />
<div className="w-12 h-12 bg-background0 ascii-border flex flex-col items-center justify-center p-1">
<Wifi className="h-3 w-3 mb-1" />
<div className="flex flex-col items-center">
<span className="text-xs font-bold text-foreground leading-none">
{quickStats.network}
@@ -163,7 +163,7 @@ export const Sidebar = forwardRef<HTMLDivElement, SidebarProps>(
return (
<div
key={index}
className="w-8 h-8 bg-card/50 border border-border/30 rounded-lg flex items-center justify-center"
className="w-8 h-8 bg-background2 ascii-border flex items-center justify-center"
>
<Server className="h-4 w-4 text-muted-foreground" />
</div>

View File

@@ -24,33 +24,31 @@ export const TabbedInterface = ({
return (
<div className="space-y-6">
<div className="bg-background/80 backdrop-blur-md border border-border/50 rounded-lg p-1 glass-card">
<div className="flex">
<div className="tui-card p-1 terminal-font">
<div className="flex gap-2">
<button
onClick={() => setActiveTab("cronjobs")}
className={`flex items-center gap-2 px-4 py-2 text-sm font-medium transition-all duration-200 rounded-md flex-1 justify-center ${
activeTab === "cronjobs"
? "bg-primary/10 text-primary shadow-sm border border-primary/20"
: "text-muted-foreground hover:text-foreground hover:bg-muted/50"
}`}
className={`flex items-center gap-2 px-4 py-2 text-sm font-medium transition-all duration-200 flex-1 justify-center terminal-font ${activeTab === "cronjobs"
? "bg-background0 ascii-border"
: "hover:ascii-border"
}`}
>
<Clock className="h-4 w-4" />
{t("cronjobs.cronJobs")}
<span className="ml-1 text-xs bg-primary/20 text-primary px-2 py-0.5 rounded-full font-medium">
<span className="ml-1 text-xs bg-background2 px-2 py-0.5 ascii-border font-medium">
{cronJobs.length}
</span>
</button>
<button
onClick={() => setActiveTab("scripts")}
className={`flex items-center gap-2 px-4 py-2 text-sm font-medium transition-all duration-200 rounded-md flex-1 justify-center ${
activeTab === "scripts"
? "bg-primary/10 text-primary shadow-sm border border-primary/20"
: "text-muted-foreground hover:text-foreground hover:bg-muted/50"
}`}
className={`flex items-center gap-2 px-4 py-2 text-sm font-medium transition-all duration-200 flex-1 justify-center terminal-font ${activeTab === "scripts"
? "bg-background0 ascii-border"
: "hover:ascii-border"
}`}
>
<FileText className="h-4 w-4" />
{t("scripts.scripts")}
<span className="ml-1 text-xs bg-primary/20 text-primary px-2 py-0.5 rounded-full font-medium">
<span className="ml-1 text-xs bg-background2 px-2 py-0.5 ascii-border font-medium">
{scripts.length}
</span>
</button>

View File

@@ -112,8 +112,8 @@ export const LoginForm = ({
{hasPassword && hasOIDC
? t("login.signInWithPasswordOrSSO")
: hasOIDC
? t("login.signInWithSSO")
: t("login.enterPasswordToContinue")}
? t("login.signInWithSSO")
: t("login.enterPasswordToContinue")}
</CardDescription>
</CardHeader>
@@ -175,7 +175,7 @@ export const LoginForm = ({
<span className="w-full border-t" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
<span className="bg-background0 px-2 text-muted-foreground">
{t("login.orContinueWith")}
</span>
</div>
@@ -203,7 +203,7 @@ export const LoginForm = ({
</div>
{version && (
<div className="mt-6 pt-4 border-t border-border/50">
<div className="mt-6 pt-4 border-t border-border">
<div className="text-center text-xs text-muted-foreground">
Cr*nMaster {t("common.version", { version })}
</div>

View File

@@ -124,9 +124,9 @@ export const CreateTaskModal = ({
<button
type="button"
onClick={handleCustomCommand}
className={`p-4 rounded-lg border-2 transition-all ${!form.selectedScriptId
? "border-primary bg-primary/5 text-primary"
: "border-border bg-muted/30 text-muted-foreground hover:border-border/60"
className={`p-4 rounded-lg transition-all ${!form.selectedScriptId
? "border-border border-2"
: "border-border border"
}`}
>
<div className="flex items-center gap-3">
@@ -145,9 +145,9 @@ export const CreateTaskModal = ({
<button
type="button"
onClick={() => setIsSelectScriptModalOpen(true)}
className={`p-4 rounded-lg border-2 transition-all ${form.selectedScriptId
? "border-primary bg-primary/5 text-primary"
: "border-border bg-muted/30 text-muted-foreground hover:border-border/60"
className={`p-4 rounded-lg transition-all ${form.selectedScriptId
? "border-border border-2"
: "border-border border"
}`}
>
<div className="flex items-center gap-3">
@@ -178,7 +178,7 @@ export const CreateTaskModal = ({
<p className="text-sm text-muted-foreground mb-2">
{selectedScript.description}
</p>
<div className="bg-muted/30 p-2 rounded border border-border/30">
<div className="bg-muted/30 p-2 rounded border border-border">
<code className="text-xs font-mono text-foreground break-all">
{form.command}
</code>
@@ -222,7 +222,7 @@ export const CreateTaskModal = ({
? "/app/scripts/script_name.sh"
: "/usr/bin/command"
}
className="w-full h-24 p-2 border border-border rounded bg-background text-foreground font-mono text-sm resize-none focus:outline-none focus:ring-1 focus:ring-primary/20"
className="w-full h-24 p-2 border border-border rounded bg-background0 text-foreground font-mono text-sm resize-none focus:outline-none focus:ring-1 focus:ring-primary/20"
required
readOnly={!!form.selectedScriptId}
/>
@@ -249,11 +249,11 @@ export const CreateTaskModal = ({
value={form.comment}
onChange={(e) => onFormChange({ comment: e.target.value })}
placeholder={t("cronjobs.whatDoesThisTaskDo")}
className="bg-muted/30 border-border/50 focus:border-primary/50"
className="bg-muted/30 border-border focus:border-primary/50"
/>
</div>
<div className="border border-border/30 bg-muted/10 rounded-lg p-4">
<div className="border border-border bg-muted/10 rounded-lg p-4">
<div className="flex items-start gap-3">
<input
type="checkbox"
@@ -279,7 +279,7 @@ export const CreateTaskModal = ({
</div>
</div>
<div className="flex justify-end gap-2 pt-3 border-t border-border/50">
<div className="flex justify-end gap-2 pt-3 border-t border-border">
<Button
type="button"
variant="outline"

View File

@@ -25,7 +25,7 @@ export const DeleteScriptModal = ({
return (
<Modal isOpen={isOpen} onClose={onClose} title="Delete Script" size="sm">
<div className="space-y-3">
<div className="bg-muted/30 rounded p-2 border border-border/50">
<div className="bg-muted/30 rounded p-2 border border-border">
<div className="space-y-1">
<div className="flex items-center gap-2">
<FileText className="h-3 w-3 text-muted-foreground" />
@@ -45,7 +45,7 @@ export const DeleteScriptModal = ({
<div className="flex items-start gap-2">
<FileText className="h-3 w-3 text-muted-foreground mt-0.5 flex-shrink-0" />
<code className="text-xs font-mono bg-muted/30 px-1 py-0.5 rounded border border-border/30">
<code className="text-xs font-mono bg-muted/30 px-1 py-0.5 rounded border border-border">
{script.filename}
</code>
</div>
@@ -66,7 +66,7 @@ export const DeleteScriptModal = ({
</div>
</div>
<div className="flex justify-end gap-2 pt-2 border-t border-border/50">
<div className="flex justify-end gap-2 pt-2 border-t border-border">
<Button
variant="outline"
onClick={onClose}

View File

@@ -34,7 +34,7 @@ export const DeleteTaskModal = ({
size="sm"
>
<div className="space-y-3">
<div className="bg-muted/30 rounded p-2 border border-border/50">
<div className="bg-muted/30 rounded p-2 border border-border">
<div className="space-y-1">
<div className="flex items-center gap-2">
<Calendar className="h-3 w-3 text-muted-foreground" />
@@ -45,7 +45,7 @@ export const DeleteTaskModal = ({
<div className="flex items-start gap-2">
<Terminal className="h-3 w-3 text-muted-foreground mt-0.5 flex-shrink-0" />
<pre className="max-w-full overflow-x-auto text-xs font-medium text-foreground break-words bg-muted/30 px-1 py-0.5 rounded border border-border/30 flex-1 hide-scrollbar">
<pre className="max-w-full overflow-x-auto text-xs font-medium text-foreground break-words bg-muted/30 px-1 py-0.5 rounded border border-border flex-1 hide-scrollbar">
{job.command}
</pre>
</div>
@@ -75,7 +75,7 @@ export const DeleteTaskModal = ({
</div>
</div>
<div className="flex justify-end gap-2 pt-2 border-t border-border/50">
<div className="flex justify-end gap-2 pt-2 border-t border-border">
<Button variant="outline" onClick={onClose} className="btn-outline">
Cancel
</Button>

View File

@@ -59,7 +59,7 @@ export const EditTaskModal = ({
value={form.command}
onChange={(e) => onFormChange({ command: e.target.value })}
placeholder="/usr/bin/command"
className="font-mono bg-muted/30 border-border/50 focus:border-primary/50"
className="font-mono bg-muted/30 border-border focus:border-primary/50"
required
/>
<div className="absolute right-3 top-1/2 -translate-y-1/2">
@@ -80,11 +80,11 @@ export const EditTaskModal = ({
value={form.comment}
onChange={(e) => onFormChange({ comment: e.target.value })}
placeholder={t("cronjobs.whatDoesThisTaskDo")}
className="bg-muted/30 border-border/50 focus:border-primary/50"
className="bg-muted/30 border-border focus:border-primary/50"
/>
</div>
<div className="border border-border/30 bg-muted/10 rounded-lg p-4">
<div className="border border-border bg-muted/10 rounded-lg p-4">
<div className="flex items-start gap-3">
<input
type="checkbox"
@@ -108,7 +108,7 @@ export const EditTaskModal = ({
</div>
</div>
<div className="flex justify-end gap-2 pt-3 border-t border-border/50">
<div className="flex justify-end gap-2 pt-3 border-t border-border">
<Button
type="button"
variant="outline"

View File

@@ -69,7 +69,7 @@ Timestamp: ${error.timestamp}
<h4 className="text-sm font-medium text-foreground mb-2">
Details
</h4>
<div className="bg-muted/30 p-3 rounded border border-border/30">
<div className="bg-muted/30 p-3 rounded border border-border">
<pre className="text-sm font-mono text-foreground whitespace-pre-wrap break-words">
{error.details}
</pre>
@@ -82,7 +82,7 @@ Timestamp: ${error.timestamp}
<h4 className="text-sm font-medium text-foreground mb-2">
Command
</h4>
<div className="bg-muted/30 p-3 rounded border border-border/30">
<div className="bg-muted/30 p-3 rounded border border-border">
<code className="text-sm font-mono text-foreground break-all">
{error.command}
</code>
@@ -93,7 +93,7 @@ Timestamp: ${error.timestamp}
{error.output && (
<div>
<h4 className="text-sm font-medium text-foreground mb-2">Output</h4>
<div className="bg-muted/30 p-3 rounded border border-border/30 max-h-32 overflow-y-auto">
<div className="bg-muted/30 p-3 rounded border border-border max-h-32 overflow-y-auto">
<pre className="text-sm font-mono text-foreground whitespace-pre-wrap">
{error.output}
</pre>
@@ -118,7 +118,7 @@ Timestamp: ${error.timestamp}
Timestamp: {error.timestamp}
</div>
<div className="flex justify-end gap-2 pt-3 border-t border-border/50">
<div className="flex justify-end gap-2 pt-3 border-t border-border">
<Button
variant="outline"
onClick={handleCopyDetails}

View File

@@ -94,17 +94,16 @@ export const FiltersModal = ({
</Button>
{isScheduleDropdownOpen && (
<div className="absolute top-full left-0 right-0 mt-1 bg-background border border-border rounded-md shadow-lg z-50 min-w-[140px]">
<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]">
<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 py-2 text-sm hover:bg-accent transition-colors flex items-center gap-2 ${localScheduleMode === "cron"
? "bg-accent text-accent-foreground"
: ""
}`}
>
<Code className="h-3 w-3" />
{t("cronjobs.cronSyntax")}
@@ -114,11 +113,10 @@ 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 text-sm hover:bg-accent transition-colors flex items-center gap-2 ${localScheduleMode === "human"
? "bg-accent text-accent-foreground"
: ""
}`}
>
<MessageSquare className="h-3 w-3" />
{t("cronjobs.humanReadable")}
@@ -128,11 +126,10 @@ 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 text-sm hover:bg-accent transition-colors flex items-center gap-2 ${localScheduleMode === "both"
? "bg-accent text-accent-foreground"
: ""
}`}
>
<Code className="h-3 w-3" />
<MessageSquare className="h-3 w-3" />
@@ -144,7 +141,7 @@ export const FiltersModal = ({
</div>
</div>
<div className="flex justify-end gap-2 pt-4 border-t border-border">
<div className="flex justify-end gap-2 pt-4">
<Button variant="outline" onClick={onClose}>
{t("common.cancel")}
</Button>

View File

@@ -228,19 +228,19 @@ export const LiveLogModal = ({
<div className="flex items-center gap-3">
<span>{t("cronjobs.liveJobExecution")}{jobComment && `: ${jobComment}`}</span>
{status === "running" && (
<span className="flex items-center gap-1 text-sm text-blue-500">
<span className="flex items-center gap-1 text-sm text-status-info">
<Loader2 className="w-4 h-4 animate-spin" />
{t("cronjobs.running")}
</span>
)}
{status === "completed" && (
<span className="flex items-center gap-1 text-sm text-green-500">
<span className="flex items-center gap-1 text-sm text-status-success">
<CheckCircle2 className="w-4 h-4" />
{t("cronjobs.completed", { exitCode: exitCode ?? 0 })}
</span>
)}
{status === "failed" && (
<span className="flex items-center gap-1 text-sm text-red-500">
<span className="flex items-center gap-1 text-sm text-status-error">
<XCircle className="w-4 h-4" />
{t("cronjobs.jobFailed", { exitCode: exitCode ?? 1 })}
</span>
@@ -268,7 +268,7 @@ export const LiveLogModal = ({
id="maxLines"
value={maxLines}
onChange={(e) => setMaxLines(parseInt(e.target.value, 10))}
className="bg-background border border-border rounded px-2 py-1 text-sm"
className="bg-background0 border border-border rounded px-2 py-1 text-sm"
>
<option value="100">{t("cronjobs.nLines", { count: "100" })}</option>
<option value="500">{t("cronjobs.nLines", { count: "500" })}</option>
@@ -316,7 +316,7 @@ export const LiveLogModal = ({
)}
</div>
{truncated && !showFullLog && (
<div className="text-sm text-orange-500 flex items-center gap-1">
<div className="text-sm text-status-warning flex items-center gap-1 terminal-font">
<AlertTriangle className="h-4 w-4" />
{t("cronjobs.showingLastOf", {
lineCount: lineCount.toLocaleString(),
@@ -327,8 +327,8 @@ export const LiveLogModal = ({
</div>
{showSizeWarning && (
<div className="bg-orange-500/10 border border-orange-500/30 rounded-lg p-3 flex items-start gap-3">
<AlertTriangle className="h-4 w-4 text-orange-500 mt-0.5 flex-shrink-0" />
<div className="bg-background2 ascii-border p-3 flex items-start gap-3 terminal-font">
<AlertTriangle className="h-4 w-4 text-status-warning mt-0.5 flex-shrink-0" />
<div className="flex-1 min-w-0">
<p className="text-sm text-foreground">
<span className="font-medium">{t("cronjobs.largeLogFileDetected")}</span> ({formatFileSize(fileSize)})
@@ -340,7 +340,7 @@ export const LiveLogModal = ({
variant="ghost"
size="sm"
onClick={toggleTailMode}
className="text-orange-500 hover:text-orange-400 hover:bg-orange-500/10 h-auto py-1 px-2 text-xs"
className="text-status-warning hover:text-status-warning hover:bg-background2 h-auto py-1 px-2 text-xs"
title={tailMode ? t("cronjobs.showAllLines") : t("cronjobs.enableTailMode")}
>
{tailMode ? <Maximize2 className="h-3 w-3" /> : <Minimize2 className="h-3 w-3" />}
@@ -348,8 +348,8 @@ export const LiveLogModal = ({
</div>
)}
<div className="bg-black/90 dark:bg-black/60 rounded-lg p-4 max-h-[60vh] overflow-auto">
<pre className="text-xs font-mono text-green-400 whitespace-pre-wrap break-words">
<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">
{logContent || t("cronjobs.waitingForJobToStart")}
<div ref={logEndRef} />
</pre>

View File

@@ -208,11 +208,11 @@ export const LogsModal = ({
logs.map((log) => (
<div
key={log.filename}
className={`p-3 rounded border cursor-pointer transition-colors ${selectedLog === log.filename
? "border-primary bg-primary/10"
className={`p-3 ascii-border cursor-pointer transition-colors terminal-font ${selectedLog === log.filename
? "border-primary bg-background2"
: log.hasError
? "border-red-500/50 hover:border-red-500"
: "border-border hover:border-primary/50"
? "border-red-600 hover:border-red-600"
: "ascii-border hover:border-primary"
}`}
onClick={() => handleViewLog(log.filename)}
>
@@ -220,9 +220,9 @@ export const LogsModal = ({
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
{log.hasError ? (
<AlertCircle className="w-4 h-4 flex-shrink-0 text-red-500" />
<AlertCircle className="w-4 h-4 flex-shrink-0 text-status-error" />
) : log.exitCode === 0 ? (
<CheckCircle className="w-4 h-4 flex-shrink-0 text-green-500" />
<CheckCircle className="w-4 h-4 flex-shrink-0 text-status-success" />
) : (
<FileText className="w-4 h-4 flex-shrink-0" />
)}
@@ -236,9 +236,9 @@ export const LogsModal = ({
</p>
{log.exitCode !== undefined && (
<span
className={`text-xs px-1.5 py-0.5 rounded ${log.hasError
? "bg-red-500/10 text-red-600 dark:text-red-400"
: "bg-green-500/10 text-green-600 dark:text-green-400"
className={`text-xs px-1.5 py-0.5 ${log.hasError
? "bg-background2 text-status-error"
: "bg-background2 text-status-success"
}`}
>
Exit: {log.exitCode}
@@ -271,7 +271,7 @@ export const LogsModal = ({
{t("common.loading")}...
</div>
) : selectedLog ? (
<pre className="h-full overflow-auto bg-muted/50 p-4 rounded border border-border text-xs font-mono whitespace-pre-wrap">
<pre className="h-full overflow-auto bg-background2 p-4 ascii-border text-xs font-mono whitespace-pre-wrap terminal-font">
{logContent}
</pre>
) : (

View File

@@ -124,7 +124,7 @@ export const RestoreBackupModal = ({
{backups.map((backup) => (
<div
key={backup.filename}
className="glass-card p-3 border border-border/50 rounded-lg hover:bg-accent/30 transition-colors"
className="glass-card p-3 border border-border rounded-lg hover:bg-accent/30 transition-colors"
>
<div className="flex items-center gap-3">
<div className="flex-shrink-0">
@@ -136,7 +136,7 @@ export const RestoreBackupModal = ({
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2">
{commandCopied === backup.filename && (
<Check className="h-3 w-3 text-green-600 flex-shrink-0" />
<Check className="h-3 w-3 text-status-success flex-shrink-0" />
)}
<pre
onClick={(e) => {
@@ -145,7 +145,7 @@ export const RestoreBackupModal = ({
setCommandCopied(backup.filename);
setTimeout(() => setCommandCopied(null), 3000);
}}
className="flex-1 cursor-pointer overflow-hidden text-sm font-medium text-foreground bg-muted/30 px-2 py-1 rounded border border-border/30 truncate"
className="flex-1 cursor-pointer overflow-hidden text-sm font-medium text-foreground bg-muted/30 px-2 py-1 rounded border border-border truncate"
title={unwrapCommand(backup.job.command)}
>
{unwrapCommand(backup.job.command)}
@@ -204,7 +204,7 @@ export const RestoreBackupModal = ({
</div>
)}
<div className="flex justify-between gap-2 pt-4 border-t border-border/50">
<div className="flex justify-between gap-2 pt-4 border-t border-border">
<p className="text-sm text-muted-foreground">
{t("cronjobs.availableBackups")}: {backups.length}
</p>

View File

@@ -80,11 +80,11 @@ export const ScriptModal = ({
return (
<Modal isOpen={isOpen} onClose={onClose} title={title} size="2xl">
<form onSubmit={handleSubmit} className="space-y-6">
<form onSubmit={handleSubmit} className="space-y-6 terminal-font">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-foreground mb-2">
Script Name <span className="text-red-500">*</span>
<label className="block text-sm font-medium mb-2">
Script Name <span className="text-status-error">*</span>
</label>
<Input
value={form.name}
@@ -93,15 +93,15 @@ export const ScriptModal = ({
required
className={
!form.name.trim()
? "border-red-300 focus:border-red-500 focus:ring-red-500"
? "border-status-error focus:border-status-error"
: ""
}
/>
</div>
<div>
<label className="block text-sm font-medium text-foreground mb-2">
<label className="block text-sm font-medium mb-2">
Description{" "}
<span className="text-muted-foreground text-xs">(optional)</span>
<span className="text-xs opacity-60">(optional)</span>
</label>
<Input
value={form.description}
@@ -112,24 +112,24 @@ export const ScriptModal = ({
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 h-[500px]">
<div className="lg:col-span-1 bg-muted/20 rounded-lg p-4 flex flex-col h-full overflow-hidden">
<div className="lg:col-span-1 bg-background0 ascii-border p-4 flex flex-col h-full overflow-hidden">
<div className="flex items-center gap-2 mb-4 flex-shrink-0">
<Code className="h-4 w-4 text-primary" />
<h3 className="text-sm font-medium text-foreground">Snippets</h3>
<Code className="h-4 w-4" />
<h3 className="text-sm font-medium">Snippets</h3>
</div>
<div className="flex-1 overflow-y-auto min-h-0 !pr-0">
<div className="flex-1 overflow-y-auto min-h-0 !pr-0 tui-scrollbar">
<BashSnippetHelper onInsertSnippet={handleInsertSnippet} />
</div>
</div>
<div className="lg:col-span-2 flex flex-col h-full overflow-hidden">
<div className="flex items-center gap-2 mb-4 flex-shrink-0">
<FileText className="h-4 w-4 text-primary" />
<h3 className="text-sm font-medium text-foreground">
Script Content <span className="text-red-500">*</span>
<FileText className="h-4 w-4" />
<h3 className="text-sm font-medium">
Script Content <span className="text-status-error">*</span>
</h3>
{isDraft && (
<span className="ml-auto px-2 py-0.5 text-xs font-medium bg-blue-500/10 text-blue-500 border border-blue-500/30 rounded-full">
<span className="ml-auto px-2 py-0.5 text-xs font-medium bg-background0 text-status-info ascii-border">
{t("scripts.draft")}
</span>
)}
@@ -145,14 +145,14 @@ export const ScriptModal = ({
</div>
</div>
<div className="flex justify-between items-center gap-3 pt-4 border-t border-border/30">
<div className="flex justify-between items-center gap-3 pt-4 ascii-border border-t">
<div>
{isDraft && onClearDraft && (
<Button
type="button"
variant="ghost"
onClick={onClearDraft}
className="text-muted-foreground hover:text-foreground"
className="opacity-60 hover:opacity-100"
>
<Trash2 className="h-4 w-4 mr-2" />
{t("scripts.clearDraft")}

View File

@@ -124,7 +124,7 @@ export const SelectScriptModal = ({
{script.name}
</h4>
{selectedScriptId === script.id && (
<Check className="h-4 w-4 text-green-500 flex-shrink-0" />
<Check className="h-4 w-4 text-status-success flex-shrink-0" />
)}
</div>
<p className="text-sm text-muted-foreground line-clamp-2">
@@ -167,7 +167,7 @@ export const SelectScriptModal = ({
{t("scripts.commandPreview")}
</span>
</div>
<div className="bg-muted/30 p-3 rounded border border-border/30">
<div className="bg-muted/30 p-3 rounded border border-border">
<code className="text-sm font-mono text-foreground break-all">
{hostScriptPath}
</code>
@@ -178,7 +178,7 @@ export const SelectScriptModal = ({
<span className="text-sm font-medium text-foreground">
{t("scripts.scriptContent")}
</span>
<div className="bg-muted/30 p-3 rounded border border-border/30 mt-2 max-h-32 overflow-auto">
<div className="bg-muted/30 p-3 rounded border border-border mt-2 max-h-32 overflow-auto">
<pre className="text-xs font-mono text-foreground whitespace-pre-wrap">
{previewContent}
</pre>
@@ -195,7 +195,7 @@ export const SelectScriptModal = ({
</div>
</div>
<div className="flex justify-end gap-2 pt-3 border-t border-border/50">
<div className="flex justify-end gap-2 pt-3 border-t border-border">
<Button
type="button"
variant="outline"

View File

@@ -42,14 +42,14 @@ export const PWAInstallPrompt = (): JSX.Element | null => {
if (choice.outcome === "accepted") {
setDeferred(null);
}
} catch (_err) {}
} catch (_err) { }
}, [deferred]);
if (isInstalled || !deferred) return null;
return (
<button
className="px-3 py-1 rounded-md border border-border/50 bg-background/80 hover:bg-background/60"
className="px-3 py-1 rounded-md border border-border bg-background/80 hover:bg-background/60"
onClick={onInstall}
>
Install App

View File

@@ -5,7 +5,8 @@ import { EditorView, keymap } from "@codemirror/view";
import { EditorState, Transaction } from "@codemirror/state";
import { shell } from "@codemirror/legacy-modes/mode/shell";
import { StreamLanguage } from "@codemirror/language";
import { oneDark } from "@codemirror/theme-one-dark";
import { catppuccinMocha, catppuccinLatte } from './catppuccin-theme';
import { useTheme } from 'next-themes';
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
import { Terminal, Copy, Check } from "lucide-react";
@@ -27,6 +28,7 @@ export const BashEditor = ({
const [copied, setCopied] = useState(false);
const editorRef = useRef<HTMLDivElement>(null);
const editorViewRef = useRef<EditorView | null>(null);
const { theme } = useTheme();
const insertFourSpaces = ({
state,
@@ -99,13 +101,55 @@ export const BashEditor = ({
useEffect(() => {
if (!editorRef.current) return;
const isDark = theme === 'catppuccin-mocha';
const bashLanguage = StreamLanguage.define(shell);
// Get CSS variables from the document
const getThemeColors = () => {
const root = document.documentElement;
const style = getComputedStyle(root);
return {
background: style.getPropertyValue('--base').trim() || (isDark ? '#1e1e2e' : '#eff1f5'),
foreground: style.getPropertyValue('--text').trim() || (isDark ? '#cdd6f4' : '#4c4f69'),
border: style.getPropertyValue('--box-border-color').trim() || (isDark ? '#313244' : '#9ca0b0'),
surface: style.getPropertyValue('--surface0').trim() || (isDark ? '#313244' : '#ccd0da'),
};
};
const colors = getThemeColors();
const customTheme = EditorView.theme({
"&": {
backgroundColor: colors.background,
color: colors.foreground,
border: `1px solid ${colors.border}`,
borderRadius: '0',
},
".cm-content": {
caretColor: colors.foreground,
padding: '12px'
},
".cm-gutters": {
backgroundColor: colors.surface,
color: colors.foreground,
borderRight: `1px solid ${colors.border}`,
opacity: '0.6',
},
".cm-activeLineGutter": {
backgroundColor: colors.surface,
opacity: '1',
},
".cm-scroller": {
fontFamily: 'JetBrains Mono, Fira Code, monospace',
},
}, { dark: isDark });
const state = EditorState.create({
doc: value || placeholder,
extensions: [
bashLanguage,
oneDark,
customTheme,
keymap.of([
{ key: "Tab", run: insertFourSpaces },
{ key: "Shift-Tab", run: removeFourSpaces },
@@ -119,7 +163,7 @@ export const BashEditor = ({
"&": {
fontSize: "14px",
fontFamily:
'ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace',
'JetBrains Mono, Fira Code, ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace',
height: "100%",
maxHeight: "100%",
},
@@ -132,7 +176,7 @@ export const BashEditor = ({
},
".cm-scroller": {
fontFamily:
'ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace',
'JetBrains Mono, Fira Code, ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace',
height: "100%",
maxHeight: "100%",
},
@@ -150,7 +194,7 @@ export const BashEditor = ({
return () => {
view.destroy();
};
}, []);
}, [theme]);
useEffect(() => {
if (editorViewRef.current) {
@@ -181,7 +225,7 @@ export const BashEditor = ({
{label && (
<div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2">
<Terminal className="h-4 w-4 text-cyan-500" />
<Terminal className="h-4 w-4" />
<span className="text-sm font-medium">{label}</span>
</div>
<Button
@@ -200,8 +244,8 @@ export const BashEditor = ({
</Button>
</div>
)}
<div className="border border-border overflow-hidden h-full">
<div ref={editorRef} className="h-full rounded-lg" />
<div className="overflow-hidden h-full">
<div ref={editorRef} className="h-full" />
</div>
</div>
);

View File

@@ -171,7 +171,7 @@ export const BashSnippetHelper = ({
return (
<div
key={snippet.id}
className="bg-muted/30 rounded-lg border border-border/50 p-3 hover:bg-accent/30 transition-colors"
className="bg-muted/30 rounded-lg border border-border p-3 hover:bg-accent/30 transition-colors"
>
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">

View File

@@ -87,9 +87,9 @@ export const CronExpressionHelper = ({
/>
<div className="absolute right-3 top-1/2 -translate-y-1/2">
{explanation?.isValid ? (
<CheckCircle className="h-4 w-4 text-green-500" />
<CheckCircle className="h-4 w-4 text-status-success" />
) : value ? (
<AlertCircle className="h-4 w-4 text-red-500" />
<AlertCircle className="h-4 w-4 text-status-error" />
) : (
<Clock className="h-4 w-4 text-muted-foreground" />
)}
@@ -97,7 +97,7 @@ export const CronExpressionHelper = ({
</div>
{explanation && (
<div className="bg-muted/30 rounded p-2 border border-border/30">
<div className="bg-background2 p-2 ascii-border terminal-font">
<div className="space-y-1">
<div className="flex items-start gap-2">
<Info className="h-3 w-3 text-primary mt-0.5 flex-shrink-0" />
@@ -108,7 +108,7 @@ export const CronExpressionHelper = ({
: "Invalid Expression"}
</p>
{explanation.error && (
<p className="text-xs text-red-600 dark:text-red-400 mt-0.5">
<p className="text-xs text-status-error mt-0.5">
{explanation.error}
</p>
)}
@@ -117,7 +117,7 @@ export const CronExpressionHelper = ({
{explanation.isValid && explanation.nextRuns.length > 0 && (
<div className="flex items-start gap-2">
<Calendar className="h-3 w-3 text-blue-500 mt-0.5 flex-shrink-0" />
<Calendar className="h-3 w-3 text-status-info mt-0.5 flex-shrink-0" />
<div className="flex-1">
<p className="text-xs text-muted-foreground mb-1">
Next executions:
@@ -137,7 +137,7 @@ export const CronExpressionHelper = ({
)}
{showPatterns && (
<div className="bg-muted/30 rounded-lg border border-border/50">
<div className="bg-background0 ascii-border terminal-font">
<button
type="button"
onClick={(e) => {
@@ -145,7 +145,7 @@ export const CronExpressionHelper = ({
e.stopPropagation();
setShowPatternsPanel(!showPatternsPanel);
}}
className="w-full text-left p-3 hover:bg-accent/30 transition-colors rounded-t-lg"
className="w-full text-left p-3 hover:bg-background0 transition-colors"
>
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Quick Patterns</span>
@@ -160,7 +160,7 @@ export const CronExpressionHelper = ({
</button>
{showPatternsPanel && (
<div className="p-3 border-t border-border/50">
<div className="p-3 border-t border-border">
<div className="relative mb-3">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input

View File

@@ -207,7 +207,7 @@ export const ScriptsManager = ({
<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-background2 ascii-border">
<FileText className="h-5 w-5 text-primary" />
</div>
<div>
@@ -230,8 +230,8 @@ export const ScriptsManager = ({
</CardHeader>
<CardContent>
{scripts.length === 0 ? (
<div className="text-center py-16">
<div className="mx-auto w-20 h-20 bg-gradient-to-br from-primary/20 to-blue-500/20 rounded-full flex items-center justify-center mb-6">
<div className="text-center py-16 terminal-font">
<div className="mx-auto w-20 h-20 bg-background2 ascii-border flex items-center justify-center mb-6">
<FileText className="h-10 w-10 text-primary" />
</div>
<h3 className="text-xl font-semibold mb-3 brand-gradient">
@@ -254,7 +254,7 @@ export const ScriptsManager = ({
{scripts.map((script) => (
<div
key={script.id}
className="glass-card p-4 border border-border/50 rounded-lg hover:bg-accent/30 transition-colors"
className="glass-card p-4 ascii-border hover:bg-accent/30 transition-colors terminal-font"
>
<div className="flex items-start justify-between gap-4">
<div className="flex-1 min-w-0">
@@ -286,7 +286,7 @@ export const ScriptsManager = ({
aria-label="Copy script content to clipboard"
>
{copiedId === script.id ? (
<CheckCircle className="h-3 w-3 text-green-500" />
<CheckCircle className="h-3 w-3 text-status-success" />
) : (
<CopyIcon className="h-3 w-3" />
)}

View File

@@ -0,0 +1,36 @@
import { Extension } from '@codemirror/state';
import { EditorView } from '@codemirror/view';
export const catppuccinMocha: Extension = EditorView.theme({
'&': {
backgroundColor: '#1e1e2e',
color: '#cdd6f4',
fontSize: '14px',
fontFamily: 'JetBrains Mono, monospace',
border: '1px solid #45475a',
borderRadius: '0',
},
'.cm-content': { caretColor: '#f5e0dc', padding: '12px' },
'.cm-gutters': {
backgroundColor: '#181825',
color: '#6c7086',
borderRight: '1px solid #45475a',
},
}, { dark: true });
export const catppuccinLatte: Extension = EditorView.theme({
'&': {
backgroundColor: '#eff1f5',
color: '#4c4f69',
fontSize: '14px',
fontFamily: 'JetBrains Mono, monospace',
border: '1px solid #9ca0b0',
borderRadius: '0',
},
'.cm-content': { caretColor: '#dc8a78', padding: '12px' },
'.cm-gutters': {
backgroundColor: '#e6e9ef',
color: '#8c8fa1',
borderRight: '1px solid #9ca0b0',
},
}, { dark: false });

View File

@@ -20,14 +20,14 @@ export const PerformanceSummary = forwardRef<HTMLDivElement, PerformanceSummaryP
<div
ref={ref}
className={cn(
"p-3 bg-gradient-to-r from-purple-500/5 to-pink-500/5 border border-purple-500/20 rounded-lg glass-card",
"p-3 bg-background0 ascii-border glass-card terminal-font",
className
)}
{...props}
>
<div className="flex items-center gap-2 mb-3">
<Zap className="h-4 w-4 text-purple-500" />
<span className="text-sm font-medium text-purple-600 dark:text-purple-400">
<Zap className="h-4 w-4" />
<span className="text-sm font-medium">
Performance Summary
</span>
</div>

View File

@@ -173,7 +173,6 @@ export const SystemInfoCard = ({
icon: Clock,
label: t("sidebar.uptime"),
value: systemInfo.uptime,
color: "text-orange-500",
},
];
@@ -184,7 +183,6 @@ export const SystemInfoCard = ({
value: `${systemInfo.memory.used} / ${systemInfo.memory.total}`,
detail: `${systemInfo.memory.free} free`,
status: systemInfo.memory.status,
color: "text-cyan-500",
showProgress: true,
progressValue: systemInfo.memory.usage,
},
@@ -194,7 +192,6 @@ export const SystemInfoCard = ({
value: systemInfo.cpu.model,
detail: `${systemInfo.cpu.cores} cores`,
status: systemInfo.cpu.status,
color: "text-pink-500",
showProgress: true,
progressValue: systemInfo.cpu.usage,
},
@@ -206,7 +203,6 @@ export const SystemInfoCard = ({
? `${systemInfo.gpu.memory} VRAM`
: systemInfo.gpu.status,
status: systemInfo.gpu.status,
color: "text-indigo-500",
},
...(systemInfo.network
? [
@@ -216,7 +212,6 @@ export const SystemInfoCard = ({
value: `${systemInfo.network.latency}ms`,
detail: `${systemInfo.network.latency}ms latency • ${systemInfo.network.speed}`,
status: systemInfo.network.status,
color: "text-teal-500",
},
]
: []),
@@ -264,7 +259,6 @@ export const SystemInfoCard = ({
icon={item.icon}
label={item.label}
value={item.value}
color={item.color}
variant="basic"
/>
))}
@@ -284,7 +278,6 @@ export const SystemInfoCard = ({
value={item.value}
detail={item.detail}
status={item.status}
color={item.color}
variant="performance"
showProgress={item.showProgress}
progressValue={item.progressValue}

View File

@@ -22,27 +22,27 @@ export const SystemStatus = forwardRef<HTMLDivElement, SystemStatusProps>(
switch (lowerStatus) {
case "operational":
return {
bgColor: "bg-emerald-500/10",
borderColor: "border-emerald-500/20",
dotColor: "bg-emerald-500",
bgColor: "bg-background0",
borderColor: "ascii-border",
dotColor: "bg-status-success",
};
case "warning":
return {
bgColor: "bg-yellow-500/10",
borderColor: "border-yellow-500/20",
dotColor: "bg-yellow-500",
bgColor: "bg-background0",
borderColor: "ascii-border",
dotColor: "bg-status-warning",
};
case "critical":
return {
bgColor: "bg-destructive/10",
borderColor: "border-destructive/20",
dotColor: "bg-destructive",
bgColor: "bg-background0",
borderColor: "ascii-border",
dotColor: "bg-status-error",
};
default:
return {
bgColor: "bg-muted",
borderColor: "border-border",
dotColor: "bg-muted-foreground",
bgColor: "bg-background0",
borderColor: "ascii-border",
dotColor: "bg-status-success",
};
}
};
@@ -53,7 +53,7 @@ export const SystemStatus = forwardRef<HTMLDivElement, SystemStatusProps>(
<div
ref={ref}
className={cn(
"p-4 border border-border/50 rounded-lg glass-card",
"p-4 glass-card terminal-font",
config.bgColor,
config.borderColor,
className
@@ -61,7 +61,7 @@ export const SystemStatus = forwardRef<HTMLDivElement, SystemStatusProps>(
{...props}
>
<div className="flex items-center gap-3">
<div className={cn("w-3 h-3 rounded-full", config.dotColor)} />
<div className={cn("w-3 h-3", config.dotColor)} />
<div className="flex-1">
<div className="flex items-center gap-2">
<Activity className="h-4 w-4 text-muted-foreground" />

View File

@@ -1,9 +1,7 @@
'use client'
import { Moon, Sun } from 'lucide-react';
import { useTheme } from 'next-themes';
import { useEffect, useState } from 'react';
import { Button } from '@/app/_components/GlobalComponents/UIElements/Button';
export const ThemeToggle = () => {
const [mounted, setMounted] = useState(false);
@@ -13,19 +11,16 @@ export const ThemeToggle = () => {
setMounted(true);
}, []);
if (!mounted) {
return null;
}
if (!mounted) return null;
const isDark = theme === 'dark';
return (
<Button
variant="ghost"
size="icon"
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
<button
onClick={() => setTheme(isDark ? 'light' : 'dark')}
className="px-3 py-2 ascii-border terminal-font text-sm bg-background0"
>
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
{isDark ? 'LIGHT' : 'DARK'}
</button>
);
};

View File

@@ -79,15 +79,14 @@ export const UserFilter = ({
</div>
{isOpen && (
<div className="absolute top-full left-0 right-0 mt-1 bg-background border border-border rounded-md shadow-lg z-50 max-h-48 overflow-y-auto">
<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">
<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:bg-accent transition-colors ${!selectedUser ? "bg-accent text-accent-foreground" : ""
}`}
>
{t("common.allUsers")}
</button>
@@ -98,9 +97,8 @@ 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 hover:bg-accent transition-colors ${selectedUser === user ? "bg-accent text-accent-foreground" : ""
}`}
>
{user}
</button>

View File

@@ -69,7 +69,7 @@ export const UserSwitcher = ({
</Button>
{isOpen && (
<div className="absolute top-full left-0 right-0 mt-1 bg-background border border-border rounded-md shadow-lg z-50 max-h-48 overflow-y-auto">
<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">
{users.map((user) => (
<button
type="button"
@@ -80,9 +80,8 @@ 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 ? "bg-accent text-accent-foreground" : ""
}`}
>
{user}
</button>

View File

@@ -31,27 +31,24 @@ export const StatusBadge = forwardRef<HTMLDivElement, StatusBadgeProps>(
case "operational":
case "stable":
return {
color: "text-emerald-500",
bgColor: "bg-emerald-500/10",
borderColor: "border-emerald-500/20",
color: "text-status-success",
bgColor: "bg-background0",
icon: CheckCircle,
label: t("system.optimal"),
};
case "moderate":
case "warning":
return {
color: "text-yellow-500",
bgColor: "bg-yellow-500/10",
borderColor: "border-yellow-500/20",
color: "text-status-warning",
bgColor: "bg-background0",
icon: AlertTriangle,
label: t("system.warning"),
};
case "high":
case "slow":
return {
color: "text-orange-500",
bgColor: "bg-orange-500/10",
borderColor: "border-orange-500/20",
color: "text-status-warning",
bgColor: "bg-background0",
icon: AlertTriangle,
label: t("system.high"),
};
@@ -59,17 +56,15 @@ export const StatusBadge = forwardRef<HTMLDivElement, StatusBadgeProps>(
case "poor":
case "offline":
return {
color: "text-destructive",
bgColor: "bg-destructive/10",
borderColor: "border-destructive/20",
color: "text-status-error",
bgColor: "bg-background0",
icon: XCircle,
label: t("system.critical"),
};
default:
return {
color: "text-muted-foreground",
bgColor: "bg-muted",
borderColor: "border-border",
color: "",
bgColor: "bg-background0",
icon: Activity,
label: t("system.unknown"),
};
@@ -83,9 +78,8 @@ export const StatusBadge = forwardRef<HTMLDivElement, StatusBadgeProps>(
<div
ref={ref}
className={cn(
"inline-flex items-center gap-1.5 rounded-full border px-2 py-1",
"inline-flex items-center gap-1.5 ascii-border px-2 py-1 terminal-font",
config.bgColor,
config.borderColor,
{
"text-xs": size === "sm",
"text-sm": size === "md",

View File

@@ -1,72 +1,43 @@
import { cn } from '@/app/_utils/global-utils';
import { HTMLAttributes, forwardRef } from 'react';
export const Card = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
'rounded-lg border bg-card text-card-foreground shadow-sm',
className
)}
{...props}
/>
({ className = '', ...props }, ref) => (
<div ref={ref} className={`tui-card ${className}`} {...props} />
)
);
Card.displayName = 'Card';
const CardHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('flex flex-col space-y-1.5 p-6', className)}
{...props}
/>
export const CardHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className = '', ...props }, ref) => (
<div ref={ref} className={`p-4 border-b border-foreground1 ${className}`} {...props} />
)
);
CardHeader.displayName = 'CardHeader';
const CardTitle = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLHeadingElement>>(
({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn(
'text-2xl font-semibold leading-none tracking-tight',
className
)}
{...props}
/>
export const CardTitle = forwardRef<HTMLHeadingElement, HTMLAttributes<HTMLHeadingElement>>(
({ className = '', ...props }, ref) => (
<h3 ref={ref} className={`terminal-font font-bold uppercase ${className}`} {...props} />
)
);
CardTitle.displayName = 'CardTitle';
const CardDescription = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(
({ className, ...props }, ref) => (
<p
ref={ref}
className={cn('text-sm text-muted-foreground', className)}
{...props}
/>
export const CardDescription = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(
({ className = '', ...props }, ref) => (
<p ref={ref} className={`terminal-font text-sm ${className}`} {...props} />
)
);
CardDescription.displayName = 'CardDescription';
const CardContent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div ref={ref} className={cn('p-4 lg:p-6 pt-0', className)} {...props} />
export const CardContent = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className = '', ...props }, ref) => (
<div ref={ref} className={`p-4 ${className}`} {...props} />
)
);
CardContent.displayName = 'CardContent';
const CardFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => (
<div
ref={ref}
className={cn('flex items-center p-6 pt-0', className)}
{...props}
/>
export const CardFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className = '', ...props }, ref) => (
<div ref={ref} className={`flex items-center p-4 border-t border-foreground1 ${className}`} {...props} />
)
);
CardFooter.displayName = 'CardFooter';
export { CardHeader, CardFooter, CardTitle, CardDescription, CardContent };

View File

@@ -27,7 +27,7 @@ export const MetricCard = forwardRef<HTMLDivElement, MetricCardProps>(
value,
detail,
status,
color = "text-blue-500",
color,
variant = "basic",
showProgress = false,
progressValue = 0,
@@ -40,14 +40,14 @@ export const MetricCard = forwardRef<HTMLDivElement, MetricCardProps>(
<div
ref={ref}
className={cn(
"flex items-start gap-3 p-3 border border-border/50 rounded-lg hover:bg-accent/50 transition-colors duration-200 glass-card-hover",
"flex items-start gap-3 p-3 tui-card-mini transition-colors duration-200 terminal-font",
className
)}
{...props}
>
<div
className={cn(
"p-2 rounded-lg border border-border/50 flex-shrink-0 bg-card/50"
"p-2 ascii-border flex-shrink-0 bg-background0"
)}
>
<Icon className={cn("h-4 w-4", color)} />
@@ -55,7 +55,7 @@ export const MetricCard = forwardRef<HTMLDivElement, MetricCardProps>(
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<p className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
<p className="text-xs font-medium uppercase tracking-wide terminal-font">
{label}
</p>
{status && variant === "performance" && (
@@ -67,12 +67,12 @@ export const MetricCard = forwardRef<HTMLDivElement, MetricCardProps>(
<TruncatedText
text={value}
maxLength={40}
className="text-sm font-medium text-foreground"
className="text-sm font-medium terminal-font"
/>
</div>
{detail && (
<p className="text-xs text-muted-foreground mb-2">{detail}</p>
<p className="text-xs mb-2 terminal-font">{detail}</p>
)}
{showProgress && (

View File

@@ -1,17 +1,12 @@
import { cn } from '@/app/_utils/global-utils';
import { InputHTMLAttributes, forwardRef } from 'react';
export interface InputProps extends InputHTMLAttributes<HTMLInputElement> { }
export interface InputProps extends InputHTMLAttributes<HTMLInputElement> {}
export const Input = forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
({ className = '', ...props }, ref) => {
return (
<input
type={type}
className={cn(
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
className
)}
className={`terminal-font ascii-border px-3 py-2 bg-background0 w-full ${className}`}
ref={ref}
{...props}
/>

View File

@@ -1,4 +1,3 @@
import { cn } from '@/app/_utils/global-utils';
import { ButtonHTMLAttributes, forwardRef } from 'react';
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
@@ -7,30 +6,31 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
}
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant = 'default', size = 'default', ...props }, ref) => {
({ 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 variantClasses = {
default: 'bg-background1 hover:bg-background2',
destructive: 'bg-status-error text-white hover:bg-status-error',
outline: 'bg-background0 hover:bg-background1',
secondary: 'bg-background2 hover:bg-background1',
ghost: 'border-0 bg-transparent hover:bg-background1',
link: 'border-0 underline bg-transparent',
};
const sizeClasses = {
default: '',
sm: 'px-2 py-1 text-sm',
lg: 'px-6 py-3',
icon: 'p-2',
};
return (
<button
className={cn(
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
'bg-primary text-primary-foreground hover:bg-primary/90': variant === 'default',
'bg-destructive text-destructive-foreground hover:bg-destructive/90': variant === 'destructive',
'border border-input bg-background hover:bg-accent hover:text-accent-foreground': variant === 'outline',
'bg-secondary text-secondary-foreground hover:bg-secondary/80': variant === 'secondary',
'hover:bg-accent hover:text-accent-foreground': variant === 'ghost',
'text-primary underline-offset-4 hover:underline': variant === 'link',
},
{
'h-10 px-4 py-2': size === 'default',
'h-9 rounded-md px-3': size === 'sm',
'h-11 rounded-md px-8': size === 'lg',
'h-10 w-10': size === 'icon',
},
className
)}
className={`${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]} ${className}`}
ref={ref}
{...props}
/>
>
{children}
</button>
);
}
);

View File

@@ -98,7 +98,7 @@ export const DropdownMenu = ({
{isOpen && (
<div
className={`absolute right-0 w-56 rounded-lg border border-border/50 bg-background shadow-lg z-[9999] overflow-hidden ${
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"
}`}
>
@@ -112,8 +112,8 @@ export const DropdownMenu = ({
item.disabled
? "opacity-50 cursor-not-allowed"
: item.variant === "destructive"
? "text-destructive hover:bg-destructive/10"
: "text-foreground hover:bg-accent"
? "text-status-error hover:bg-background1"
: "hover:bg-background1"
}`}
>
{item.icon && (

View File

@@ -2,8 +2,7 @@
import { useEffect, useRef } from "react";
import { X } from "lucide-react";
import { cn } from "@/app/_utils/global-utils";
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
import { Button } from "./Button";
interface ModalProps {
isOpen: boolean;
@@ -26,99 +25,51 @@ export const Modal = ({
preventCloseOnClickOutside = false,
className = "",
}: ModalProps) => {
const modalRef = useRef<HTMLDivElement>(null);
const dialogRef = useRef<HTMLDialogElement>(null);
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === "Escape") {
onClose();
}
};
const dialog = dialogRef.current;
if (!dialog) return;
if (isOpen) {
document.addEventListener("keydown", handleEscape);
dialog.showModal();
document.body.style.overflow = "hidden";
}
return () => {
document.removeEventListener("keydown", handleEscape);
} else {
dialog.close();
document.body.style.overflow = "unset";
};
}, [isOpen, onClose]);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
modalRef.current &&
!modalRef.current.contains(event.target as Node) &&
!preventCloseOnClickOutside
) {
const target = event.target as Element;
const isClickingOnModal = target.closest('[data-modal="true"]');
const isClickingOnBackdrop =
target.classList.contains("modal-backdrop");
if (isClickingOnBackdrop) {
onClose();
}
}
};
if (isOpen) {
document.addEventListener("mousedown", handleClickOutside);
}
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [isOpen, onClose, preventCloseOnClickOutside]);
if (!isOpen) return null;
}, [isOpen]);
const sizeClasses = {
sm: "max-w-md",
md: "max-w-lg",
lg: "max-w-2xl",
xl: "max-w-4xl",
"2xl": "max-w-6xl",
"3xl": "max-w-8xl",
sm: "w-[600px]",
md: "w-[800px]",
lg: "w-[1000px]",
xl: "w-[1200px]",
"2xl": "w-[1400px]",
"3xl": "w-[90vw]",
};
return (
<div
className="fixed inset-0 z-50 flex items-end justify-center sm:items-center p-0 sm:p-4"
data-modal="true"
<dialog
ref={dialogRef}
className={`ascii-border terminal-font bg-background0 ${sizeClasses[size]} max-w-[95vw] ${className}`}
onClick={(e) => {
if (e.target === dialogRef.current && !preventCloseOnClickOutside) {
onClose();
}
}}
>
<div className="absolute inset-0 bg-black/50 backdrop-blur-sm modal-backdrop" />
<div
ref={modalRef}
className={cn(
"relative w-full bg-card border border-border shadow-lg",
"max-h-[85vh]",
"sm:rounded-lg sm:max-h-[90vh] sm:w-full",
sizeClasses[size],
className
<div className="ascii-border border-t-0 border-l-0 border-r-0 p-4 flex justify-between items-center bg-background0">
<h2 className="terminal-font font-bold uppercase">{title}</h2>
{showCloseButton && (
<Button variant="ghost" size="icon" onClick={onClose}>
<X className="h-4 w-4" />
</Button>
)}
>
<div className="flex items-center justify-between p-4 sm:p-6 border-b border-border sticky top-0 bg-card z-10">
<h2 className="text-lg font-semibold text-foreground">{title}</h2>
{showCloseButton && (
<Button
variant="ghost"
size="icon"
onClick={onClose}
className="h-8 w-8"
>
<X className="h-4 w-4" />
</Button>
)}
</div>
<div className="p-4 sm:p-6 overflow-y-auto max-h-[calc(80vh-100px)]">
{children}
</div>
</div>
</div>
<div className="p-4 max-h-[70vh] overflow-y-auto tui-scrollbar bg-background0">
{children}
</div>
</dialog>
);
};

View File

@@ -25,35 +25,29 @@ export const ProgressBar = forwardRef<HTMLDivElement, ProgressBarProps>(
const percentage = Math.min(Math.max((value / max) * 100, 0), 100);
const getColorClass = (percentage: number) => {
if (percentage >= 90) return "bg-destructive";
if (percentage >= 80) return "bg-orange-500";
if (percentage >= 70) return "bg-yellow-500";
return "bg-emerald-500";
if (percentage >= 90) return "bg-red-600";
if (percentage >= 80) return "bg-yellow-600";
if (percentage >= 70) return "bg-yellow-600";
return "bg-green-600";
};
const getGradientClass = (percentage: number) => {
if (percentage >= 90)
return "bg-gradient-to-r from-destructive to-red-600";
if (percentage >= 80)
return "bg-gradient-to-r from-orange-500 to-orange-600";
if (percentage >= 70)
return "bg-gradient-to-r from-yellow-500 to-yellow-600";
return "bg-gradient-to-r from-emerald-500 to-emerald-600";
return getColorClass(percentage);
};
return (
<div ref={ref} className={cn("w-full", className)} {...props}>
<div ref={ref} className={cn("w-full terminal-font", className)} {...props}>
{showLabel && (
<div className="flex justify-between items-center mb-1">
<span className="text-xs text-muted-foreground">Usage</span>
<span className="text-xs font-medium text-foreground">
<span className="text-xs">Usage</span>
<span className="text-xs font-medium">
{Math.round(percentage)}%
</span>
</div>
)}
<div
className={cn("w-full bg-muted rounded-full overflow-hidden", {
className={cn("w-full bg-background2 ascii-border overflow-hidden", {
"h-1.5": size === "sm",
"h-2": size === "md",
"h-3": size === "lg",

View File

@@ -1,7 +1,5 @@
"use client";
import { cn } from "@/app/_utils/global-utils";
interface SwitchProps {
checked: boolean;
onCheckedChange: (checked: boolean) => void;
@@ -9,27 +7,16 @@ interface SwitchProps {
disabled?: boolean;
}
export const Switch = ({
checked,
onCheckedChange,
className = "",
disabled = false,
}: SwitchProps) => {
export const Switch = ({ checked, onCheckedChange, className = "", disabled = false }: SwitchProps) => {
return (
<label
className={cn(
"relative inline-flex items-center cursor-pointer",
className
)}
>
<label className={className}>
<input
type="checkbox"
className="sr-only peer"
checked={checked}
onChange={(e) => onCheckedChange(e.target.checked)}
disabled={disabled}
className="terminal-font"
/>
<div className="w-9 h-5 bg-muted peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-primary/25 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-primary peer-disabled:opacity-50 peer-disabled:cursor-not-allowed"></div>
</label>
);
};

View File

@@ -37,12 +37,10 @@ const toastIcons = {
};
const toastStyles = {
success:
"border-green-500/20 bg-green-500/10 text-green-700 dark:text-green-400",
error: "border-red-500/20 bg-red-500/10 text-red-700 dark:text-red-400",
info: "border-blue-500/20 bg-blue-500/10 text-blue-700 dark:text-blue-400",
warning:
"border-yellow-500/20 bg-yellow-500/10 text-yellow-700 dark:text-yellow-400",
success: "ascii-border bg-background0 text-status-success",
error: "ascii-border bg-background0 text-status-error",
info: "ascii-border bg-background0 text-status-info",
warning: "ascii-border bg-background0 text-status-warning",
};
export const Toast = ({ toast, onRemove, onErrorClick }: ToastProps) => {
@@ -62,7 +60,7 @@ export const Toast = ({ toast, onRemove, onErrorClick }: ToastProps) => {
return (
<div
className={cn(
"flex items-start gap-3 p-4 rounded-lg border backdrop-blur-md transition-all duration-300 ease-in-out",
"flex items-start gap-3 p-4 terminal-font transition-all duration-300 ease-in-out",
toastStyles[toast.type],
isVisible ? "translate-x-0 opacity-100" : "translate-x-full opacity-0"
)}

View File

@@ -4,7 +4,21 @@ import { ThemeProvider as NextThemesProvider } from 'next-themes';
import { type ThemeProviderProps } from 'next-themes/dist/types';
export const ThemeProvider = ({ children, ...props }: ThemeProviderProps) => {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
return (
<NextThemesProvider
attribute="data-webtui-theme"
defaultTheme="light"
themes={['light', 'dark']}
value={{
light: 'catppuccin-latte',
dark: 'catppuccin-mocha',
}}
disableTransitionOnChange
{...props}
>
{children}
</NextThemesProvider>
);
}

View File

@@ -1,325 +1,187 @@
/* eslint-disable */
/* @ts-nocheck */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@300;400;500;600;700&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--font-sans: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--font-mono: 'JetBrains Mono', 'Fira Code', 'Consolas', 'Monaco', 'Courier New', monospace;
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 280 100% 60%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 280 100% 60%;
--radius: 0.75rem;
--chart-1: 280 100% 60%;
--chart-2: 160 84% 39%;
--chart-3: 30 100% 50%;
--chart-4: 340 100% 50%;
--chart-5: 200 100% 50%;
[data-webtui-theme="catppuccin-latte"] {
--box-border-color: #9ca0b0;
--table-border-color: #9ca0b0;
--separator-color: #9ca0b0;
}
.dark {
--background: 240 10% 8%;
--foreground: 0 0% 98%;
--card: 240 10% 12%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 12%;
--popover-foreground: 0 0% 98%;
--primary: 280 100% 40%;
--primary-foreground: 240 10% 8%;
--secondary: 240 3.7% 18%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 18%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 18%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 240 3.7% 25%;
--input: 240 3.7% 18%;
--ring: 280 100% 40%;
--chart-1: 280 100% 40%;
--chart-2: 160 84% 30%;
--chart-3: 30 100% 35%;
--chart-4: 340 100% 35%;
--chart-5: 200 100% 35%;
[data-webtui-theme="catppuccin-mocha"] {
--box-border-color: #313244;
--table-border-color: #313244;
--separator-color: #313244;
}
}
.hide-scrollbar::-webkit-scrollbar {
display: none;
}
.hide-scrollbar {
scrollbar-width: none;
}
.overflow-y-auto {
padding-right: 1em;
}
.overflow-y-auto::-webkit-scrollbar {
width: 4px;
}
.overflow-y-auto::-webkit-scrollbar-track {
background: transparent;
}
.overflow-y-auto::-webkit-scrollbar-thumb {
background-color: hsl(var(--primary) / 0.8);
border-radius: 3px;
}
.overflow-y-auto::-webkit-scrollbar-thumb:hover {
background-color: hsl(var(--primary) / 0.1);
}
@layer base {
* {
@apply border-border;
box-sizing: border-box;
}
body {
@apply bg-background text-foreground;
position: relative;
margin: 0;
padding: 0;
overflow-x: hidden;
font-family: var(--font-sans);
font-feature-settings: "cv02", "cv03", "cv04", "cv11";
font-variation-settings: normal;
}
code, pre, .font-mono {
font-family: var(--font-mono);
font-feature-settings: "liga" 1, "calt" 1;
}
.brand-text {
font-family: var(--font-mono);
font-weight: 600;
letter-spacing: -0.025em;
}
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-sans);
font-weight: 600;
}
button, input, textarea, select {
font-family: var(--font-sans);
}
.terminal-text {
font-family: var(--font-mono);
font-size: 0.875rem;
line-height: 1.5;
}
}
@layer components {
.hero-gradient {
background: linear-gradient(135deg,
hsl(280 100% 60% / 0.1) 0%,
hsl(160 84% 39% / 0.05) 25%,
hsl(30 100% 50% / 0.05) 50%,
hsl(340 100% 50% / 0.05) 75%,
hsl(200 100% 50% / 0.1) 100%);
}
.hero-gradient::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
radial-gradient(circle at 20% 80%, hsl(280 100% 60% / 0.15) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, hsl(160 84% 39% / 0.15) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, hsl(340 100% 50% / 0.1) 0%, transparent 50%);
pointer-events: none;
}
.dark .hero-gradient::before {
background:
radial-gradient(circle at 20% 80%, hsl(280 100% 50% / 0.08) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, hsl(160 84% 35% / 0.08) 0%, transparent 50%),
radial-gradient(circle at 40% 40%, hsl(340 100% 45% / 0.06) 0%, transparent 50%);
}
.glass-card {
@apply backdrop-blur-md bg-card/80 border border-border/50;
}
.dark .glass-card {
@apply backdrop-blur-md bg-card/80 border border-border/70;
}
.glass-card-hover {
@apply glass-card;
}
.brand-gradient {
@apply bg-gradient-to-r from-purple-500 via-pink-500 to-orange-500 bg-clip-text text-transparent;
}
.dark .brand-gradient {
@apply bg-gradient-to-r from-purple-600 via-pink-600 to-orange-600 bg-clip-text text-transparent;
}
.brand-gradient-alt {
@apply bg-gradient-to-r from-cyan-500 via-blue-500 to-purple-500 bg-clip-text text-transparent;
}
.dark .brand-gradient-alt {
@apply bg-gradient-to-r from-cyan-600 via-blue-600 to-purple-600 bg-clip-text text-transparent;
}
.glow-primary {
box-shadow: none;
}
.glow-primary:hover {
box-shadow: none;
}
.glow-cyan {
box-shadow: none;
}
.glow-orange {
box-shadow: none;
}
.glow-pink {
box-shadow: none;
}
.status-error {
@apply bg-red-500/20 text-red-700 dark:text-red-400 border-red-500/30;
}
.custom-scrollbar {
scrollbar-width: thin;
scrollbar-color: hsl(var(--muted-foreground) / 0.3) transparent;
}
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: transparent;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background-color: hsl(var(--muted-foreground) / 0.3);
border-radius: 3px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background-color: hsl(var(--muted-foreground) / 0.5);
}
.tooltip {
@apply absolute z-50 px-3 py-2 text-sm text-white bg-gray-900 rounded-lg shadow-lg opacity-0 invisible transition-all duration-200;
}
.tooltip.show {
@apply opacity-100 visible;
}
.text-responsive {
@apply text-sm sm:text-base lg:text-lg;
}
.text-responsive-lg {
@apply text-base sm:text-lg lg:text-xl;
}
.text-responsive-xl {
@apply text-lg sm:text-xl lg:text-2xl;
}
.btn-primary {
@apply bg-gradient-to-r from-purple-500 to-pink-500 text-white hover:from-purple-600 hover:to-pink-600 transition-all;
}
.dark .btn-primary {
@apply bg-gradient-to-r from-purple-600 to-pink-600 text-white hover:from-purple-700 hover:to-pink-700 transition-all;
}
.btn-secondary {
@apply bg-gradient-to-r from-cyan-500 to-blue-500 text-white hover:from-cyan-600 hover:to-blue-600 transition-all;
}
.dark .btn-secondary {
@apply bg-gradient-to-r from-cyan-600 to-blue-600 text-white hover:from-cyan-700 hover:to-blue-700 transition-all;
}
.btn-outline {
@apply border border-border bg-background hover:bg-accent hover:text-accent-foreground transition-colors;
}
.btn-ghost {
@apply hover:bg-accent hover:text-accent-foreground transition-colors;
}
.btn-destructive {
@apply bg-gradient-to-r from-red-500 to-pink-500 text-white hover:from-red-600 hover:to-pink-600 transition-all;
}
.dark .btn-destructive {
@apply bg-gradient-to-r from-red-600 to-pink-600 text-white hover:from-red-700 hover:to-pink-700 transition-all;
}
.neon-border {
border: 1px solid transparent;
background: linear-gradient(white, white) padding-box,
linear-gradient(45deg, hsl(280 100% 60%), hsl(160 84% 39%), hsl(30 100% 50%)) border-box;
}
.neon-border-dark {
border: 1px solid transparent;
background: linear-gradient(hsl(240 10% 3.9%), hsl(240 10% 3.9%)) padding-box,
linear-gradient(45deg, hsl(280 100% 70%), hsl(160 84% 45%), hsl(30 100% 60%)) border-box;
}
}
@layer utilities {
body.sidebar-collapsed main.lg\:ml-80 {
margin-left: 4rem !important;
.ascii-border {
border: 1px solid var(--box-border-color, var(--foreground2));
border-radius: 0;
}
.border-border {
border-color: var(--box-border-color, var(--foreground2))!important;
}
.tui-scrollbar {
scrollbar-width: thin;
scrollbar-color: var(--box-border-color, var(--foreground2)) var(--background1);
}
.tui-scrollbar::-webkit-scrollbar {
width: 8px;
}
.tui-scrollbar::-webkit-scrollbar-track {
background: var(--background1);
}
.tui-scrollbar::-webkit-scrollbar-thumb {
background: var(--box-border-color, var(--foreground2));
}
.bg-background0 {
background-color: var(--background0) !important;
}
.bg-background1 {
background-color: var(--background1) !important;
}
.bg-background2 {
background-color: var(--background2) !important;
}
.border-foreground1 {
border-color: var(--box-border-color, var(--foreground2)) !important;
}
.text-foreground0 {
color: var(--foreground0) !important;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
}
.dropdown-overflow-fix {
overflow: visible;
position: relative;
}
@layer components {
.tui-card {
background: var(--background0) !important;
border: 1px solid var(--box-border-color, var(--foreground2)) !important;
box-shadow: 8px 4px 0 var(--box-border-color, var(--foreground2));
border-radius: 0 !important;
}
.dropdown-overflow-fix > * {
position: relative;
}
.tui-card-mini {
background: var(--background0) !important;
border: 1px solid var(--box-border-color, var(--foreground2)) !important;
box-shadow: 2px 4px 0 var(--box-border-color, var(--foreground2));
border-radius: 0 !important;
}
.dropdown-overflow-fix .dropdown-container,
.dropdown-overflow-fix [class*="dropdown"] {
overflow: visible !important;
.terminal-log {
background: var(--background0) !important;
border: 1px solid var(--box-border-color, var(--foreground2)) !important;
font-family: 'JetBrains Mono', monospace;
font-size: 0.875rem;
line-height: 1.5;
padding: 1rem;
color: var(--foreground0);
}
.text-status-info {
color: var(--mauve);
}
.text-status-warning {
color: var(--yellow);
}
.text-status-success {
color: var(--green);
}
.text-status-error {
color: var(--red);
}
.bg-status-info {
background-color: var(--mauve);
}
.bg-status-warning {
background-color: var(--yellow);
}
.bg-status-success {
background-color: var(--green);
}
.bg-status-error {
background-color: var(--red);
}
dialog {
background: var(--background0) !important;
margin: auto;
padding: 0;
width: 90vw;
max-height: 90vh;
}
dialog::backdrop {
background: rgba(0, 0, 0, 0.8) !important;
}
dialog[open] {
display: flex;
flex-direction: column;
}
/* Sidebar layout adjustment */
.no-sidebar main {
margin-left: 0 !important;
}
body:not(.sidebar-collapsed) main {
margin-left: 0;
}
@media (min-width: 1024px) {
body:not(.sidebar-collapsed) main {
margin-left: 320px;
}
body.sidebar-collapsed main {
margin-left: 64px;
}
.no-sidebar main {
margin-left: 0 !important;
}
}
}

View File

@@ -1,5 +1,5 @@
import type { Metadata } from "next";
import { JetBrains_Mono, Inter } from "next/font/google";
import { JetBrains_Mono } from "next/font/google";
import "@/app/globals.css";
import { ThemeProvider } from "@/app/_providers/ThemeProvider";
import { ServiceWorkerRegister } from "@/app/_components/FeatureComponents/PWA/ServiceWorkerRegister";
@@ -13,12 +13,6 @@ const jetbrainsMono = JetBrains_Mono({
display: "swap",
});
const inter = Inter({
subsets: ["latin"],
variable: "--font-sans",
display: "swap",
});
export const metadata: Metadata = {
title: "Cr*nMaster - Cron Management made easy",
description:
@@ -58,7 +52,7 @@ export default async function RootLayout({
messages = await loadTranslationMessages(locale);
return (
<html lang="en" suppressHydrationWarning>
<html lang="en" suppressHydrationWarning data-webtui-theme="catppuccin-latte">
<head>
<meta name="application-name" content="Cr*nMaster" />
<meta name="apple-mobile-web-app-capable" content="yes" />
@@ -66,15 +60,23 @@ export default async function RootLayout({
<meta name="apple-mobile-web-app-title" content="Cr*nMaster" />
<meta name="mobile-web-app-capable" content="yes" />
<link rel="apple-touch-icon" href="/logo.png" />
<link rel="stylesheet" href="/webtui/base.css" />
<link rel="stylesheet" href="/webtui/theme-catppuccin.css" />
<script
dangerouslySetInnerHTML={{
__html: `
(function() {
const theme = localStorage.getItem('theme') || 'light';
const webtui = theme === 'dark' ? 'catppuccin-mocha' : 'catppuccin-latte';
document.documentElement.setAttribute('data-webtui-theme', webtui);
})();
`,
}}
/>
</head>
<body className={`${inter.variable} ${jetbrainsMono.variable} font-sans`}>
<body className={`${jetbrainsMono.variable} terminal-font`}>
<NextIntlClientProvider locale={locale} messages={messages}>
<ThemeProvider
attribute="class"
defaultTheme="dark"
enableSystem
disableTransitionOnChange
>
<ThemeProvider>
{children}
</ThemeProvider>
<ServiceWorkerRegister />

View File

@@ -9,6 +9,7 @@ import { PWAInstallPrompt } from "@/app/_components/FeatureComponents/PWA/PWAIns
import { WrapperScriptWarning } from "@/app/_components/FeatureComponents/System/WrapperScriptWarning";
import { getTranslations } from "@/app/_server/actions/translations";
import { SSEProvider } from "@/app/_contexts/SSEContext";
import { FcClock, FcDocument } from "react-icons/fc";
export const dynamic = "force-dynamic";
export const maxDuration = 300;
@@ -59,52 +60,49 @@ export default async function Home() {
},
};
const bodyClass = process.env.DISABLE_SYSTEM_STATS === "true" ? "no-sidebar" : "";
return (
<SSEProvider liveUpdatesEnabled={liveUpdatesEnabled}>
<div className="min-h-screen relative">
<div className="hero-gradient absolute inset-0 -z-10"></div>
<div className="relative z-10">
<header className="border-b border-border/50 bg-background/80 backdrop-blur-md sticky top-0 z-20 shadow-sm lg:h-[90px]">
<div className="container mx-auto px-4 py-4">
<div className="flex items-center justify-between lg:justify-center">
<div className="flex items-center gap-4">
<div className="relative">
<img src="/logo.png" alt="logo" className="w-14 h-14" />
<div className="absolute top-0 right-0 w-3 h-3 bg-emerald-500 rounded-full animate-pulse"></div>
</div>
<div>
<h1 className="text-xl sm:text-2xl lg:text-3xl font-bold brand-gradient brand-text">
Cr*nMaster
</h1>
<p className="text-xs text-muted-foreground font-mono tracking-wide">
{t("common.cronManagementMadeEasy")}
</p>
</div>
<div className={`min-h-screen bg-background0 ${bodyClass}`}>
<header className="ascii-border !border-r-0 sticky top-0 z-20 bg-background0 lg:h-[90px]">
<div className="container mx-auto px-4 py-4">
<div className="flex items-center justify-between lg:justify-center">
<div className="flex items-center gap-4">
<FcClock size={48} />
<div>
<h1 className="text-xl sm:text-2xl lg:text-3xl font-bold terminal-font uppercase">
Cr*nMaster
</h1>
<p className="text-xs terminal-font flex items-center gap-2">
<FcDocument size={16} />
{t("common.cronManagementMadeEasy")}
</p>
</div>
{process.env.AUTH_PASSWORD && (
<div className="lg:absolute lg:right-10">
<LogoutButton />
</div>
)}
</div>
{process.env.AUTH_PASSWORD && (
<div className="lg:absolute lg:right-10">
<LogoutButton />
</div>
)}
</div>
</header>
</div>
</header>
{process.env.DISABLE_SYSTEM_STATS !== "true" && (
<SystemInfoCard systemInfo={initialSystemInfo} />
)}
{process.env.DISABLE_SYSTEM_STATS !== "true" && (
<SystemInfoCard systemInfo={initialSystemInfo} />
)}
<main className={`${process.env.DISABLE_SYSTEM_STATS === "true" ? "lg:ml-0" : "lg:ml-80"} transition-all duration-300 ml-0 sidebar-collapsed:lg:ml-16`}>
<div className="container mx-auto px-4 py-8 lg:px-8">
<WrapperScriptWarning />
<TabbedInterface cronJobs={cronJobs} scripts={scripts} />
</div>
</main>
</div>
<main className="transition-all duration-300">
<div className="container mx-auto px-4 py-8 lg:px-8">
<WrapperScriptWarning />
<TabbedInterface cronJobs={cronJobs} scripts={scripts} />
</div>
</main>
<ToastContainer />
<div className="flex items-center gap-2 fixed bottom-4 left-4 lg:right-4 lg:left-auto z-10 bg-background/80 backdrop-blur-md border border-border/50 rounded-lg p-1">
<div className="flex items-center gap-2 fixed bottom-4 left-4 lg:right-4 lg:left-auto z-10 bg-background0 ascii-border p-1">
<ThemeToggle />
<PWAInstallPrompt />
</div>

View File

@@ -23,6 +23,8 @@
"@types/react": "^18",
"@types/react-dom": "^18",
"@types/react-syntax-highlighter": "^15.5.13",
"@webtui/css": "^0.1.5",
"@webtui/theme-catppuccin": "^0.0.3",
"autoprefixer": "^10.0.1",
"bcryptjs": "^2.4.3",
"clsx": "^2.0.0",
@@ -40,6 +42,7 @@
"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",
@@ -50,6 +53,7 @@
"@types/bcryptjs": "^2.4.6",
"@types/minimatch": "^6.0.0",
"eslint": "^8",
"eslint-config-next": "14.0.4"
"eslint-config-next": "14.0.4",
"postcss-import": "^16.1.1"
}
}

View File

@@ -1,5 +1,6 @@
module.exports = {
plugins: {
'postcss-import': {},
tailwindcss: {},
autoprefixer: {},
},

1
public/webtui/base.css Normal file
View File

@@ -0,0 +1 @@
@layer base{:root{--background0: #fff;--background1: #ddd;--background2: #bbb;--background3: #999;--foreground0: #000;--foreground1: #444;--foreground2: #888;--font-size: 16px;--line-height: 1.3;--font-weight-bold: 700;--font-weight-normal: 400;--font-family: monospace;--box-border-color: var(--foreground0);--table-border-color: var(--box-border-color);--separator-color: var(--box-border-color);--separator-background: transparent}[data-webtui-theme=dark]{--background0: #000;--background1: #222;--background2: #444;--background3: #666;--foreground0: #fff;--foreground1: #ccc;--foreground2: #999}body,html{background-color:var(--background0);color:var(--foreground0);font-family:var(--font-family);font-size:var(--font-size);font-weight:var(--font-weight-normal);line-height:var(--line-height, 1.5);font-variant-ligatures:common-ligatures}*{box-sizing:border-box;margin:0;padding:0;outline:none}}

View File

File diff suppressed because one or more lines are too long

View File

@@ -1700,6 +1700,16 @@
resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz#538b1e103bf8d9864e7b85cc96fa8d6fb6c40777"
integrity sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==
"@webtui/css@^0.1.5":
version "0.1.5"
resolved "https://registry.yarnpkg.com/@webtui/css/-/css-0.1.5.tgz#2a1d2fe50200ee1dfbbe87448acc0298c3a0d5a6"
integrity sha512-6+yk/XjW4JHaRUJpglYcvE9pcxay+PmoPFYeedHksCz0JHp3POW0LG2c6q1hSu2y7U4V0yHO7/eQBC3P0qCyIw==
"@webtui/theme-catppuccin@^0.0.3":
version "0.0.3"
resolved "https://registry.yarnpkg.com/@webtui/theme-catppuccin/-/theme-catppuccin-0.0.3.tgz#bc451e8334f697acd17aeb31b721c17012edd37a"
integrity sha512-mn3qpyIDYzPm9DoDX7Rs/Ma/3YawbAZEu6/Cemj4kTodr4eBFuEOmkkd1RtChsXGH75fZ054MIzZoVwYvA5W2g==
acorn-jsx@^5.3.2:
version "5.3.2"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
@@ -4160,6 +4170,15 @@ postcss-import@^15.1.0:
read-cache "^1.0.0"
resolve "^1.1.7"
postcss-import@^16.1.1:
version "16.1.1"
resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-16.1.1.tgz#cfbe79e6c9232b0dbbe1c18f35308825cfe8ff2a"
integrity sha512-2xVS1NCZAfjtVdvXiyegxzJ447GyqCeEI5V7ApgQVOWnros1p5lGNovJNapwPpMombyFBfqDwt7AD3n2l0KOfQ==
dependencies:
postcss-value-parser "^4.0.0"
read-cache "^1.0.0"
resolve "^1.1.7"
postcss-js@^4.0.1:
version "4.1.0"
resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.1.0.tgz#003b63c6edde948766e40f3daf7e997ae43a5ce6"
@@ -4282,6 +4301,11 @@ 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"