2 Commits

Author SHA1 Message Date
fccview
6fe92ef3fa continue rebranding 2025-12-31 22:23:43 +00:00
fccview
ebce8f698b start replacing all UI with terminal UI 2025-12-31 20:09:11 +00:00
61 changed files with 1089 additions and 1151 deletions

View File

@@ -9,16 +9,16 @@ import {
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
import { Switch } from "@/app/_components/GlobalComponents/UIElements/Switch";
import {
Clock,
Plus,
ClockIcon,
PlusIcon,
Archive,
ChevronDown,
Code,
MessageSquare,
Settings,
Loader2,
Filter,
} from "lucide-react";
CaretDownIcon,
CodeIcon,
ChatTextIcon,
GearIcon,
CircleNotchIcon,
FunnelIcon,
} from "@phosphor-icons/react";
import { CronJob } from "@/app/_utils/cronjob-utils";
import { Script } from "@/app/_utils/scripts-utils";
import { UserFilter } from "@/app/_components/FeatureComponents/User/UserFilter";
@@ -237,7 +237,7 @@ export const CronJobList = ({ cronJobs, scripts }: CronJobListProps) => {
<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">
<Clock className="h-5 w-5 text-primary" />
<ClockIcon className="h-5 w-5 text-primary" />
</div>
<div>
<CardTitle className="text-xl brand-gradient">
@@ -261,7 +261,7 @@ export const CronJobList = ({ cronJobs, scripts }: CronJobListProps) => {
className="btn-outline"
title={t("cronjobs.filters")}
>
<Filter className="h-4 w-4" />
<FunnelIcon className="h-4 w-4" />
</Button>
<Button
onClick={() => setIsBackupModalOpen(true)}
@@ -276,7 +276,7 @@ export const CronJobList = ({ cronJobs, scripts }: CronJobListProps) => {
onClick={() => setIsNewCronModalOpen(true)}
className="btn-primary glow-primary"
>
<Plus className="h-4 w-4 mr-2" />
<PlusIcon className="h-4 w-4 mr-2" />
{t("cronjobs.newTask")}
</Button>
</div>
@@ -301,7 +301,7 @@ export const CronJobList = ({ cronJobs, scripts }: CronJobListProps) => {
onNewTaskClick={() => setIsNewCronModalOpen(true)}
/>
) : (
<div className="space-y-3 max-h-[55vh] min-h-[55vh] overflow-y-auto">
<div className="space-y-4 max-h-[55vh] min-h-[55vh] overflow-y-auto tui-scrollbar">
{loadedSettings ? (
filteredJobs.map((job) =>
minimalMode ? (
@@ -347,7 +347,7 @@ export const CronJobList = ({ cronJobs, scripts }: CronJobListProps) => {
)
) : (
<div className="flex items-center justify-center h-full min-h-[55vh]">
<Loader2 className="h-8 w-8 animate-spin text-primary" />
<CircleNotchIcon className="h-8 w-8 animate-spin text-primary" />
</div>
)}
</div>

View File

@@ -1,7 +1,7 @@
"use client";
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
import { Clock, Plus } from "lucide-react";
import { ClockIcon, PlusIcon } from "@phosphor-icons/react";
interface CronJobEmptyStateProps {
selectedUser: string | null;
@@ -15,7 +15,7 @@ export const CronJobEmptyState = ({
return (
<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">
<Clock className="h-10 w-10 text-primary" />
<ClockIcon className="h-10 w-10 text-primary" />
</div>
<h3 className="text-xl font-semibold mb-3 brand-gradient">
{selectedUser
@@ -32,7 +32,7 @@ export const CronJobEmptyState = ({
className="btn-primary glow-primary"
size="lg"
>
<Plus className="h-5 w-5 mr-2" />
<PlusIcon className="h-5 w-5 mr-2" />
Create Your First Task
</Button>
</div>

View File

@@ -4,24 +4,24 @@ import { useState, useEffect } from "react";
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
import { DropdownMenu } from "@/app/_components/GlobalComponents/UIElements/DropdownMenu";
import {
Trash2,
Edit,
Files,
User,
Play,
Pause,
Code,
Info,
FileOutput,
FileX,
FileText,
AlertCircle,
CheckCircle,
AlertTriangle,
Download,
Hash,
Check,
} from "lucide-react";
TrashIcon,
PencilSimpleIcon,
FilesIcon,
UserIcon,
PlayIcon,
PauseIcon,
CodeIcon,
InfoIcon,
FileArrowDownIcon,
FileXIcon,
FileTextIcon,
WarningCircleIcon,
CheckCircleIcon,
WarningIcon,
DownloadIcon,
HashIcon,
CheckIcon,
} from "@phosphor-icons/react";
import { CronJob } from "@/app/_utils/cronjob-utils";
import { JobError } from "@/app/_utils/error-utils";
import { ErrorBadge } from "@/app/_components/GlobalComponents/Badges/ErrorBadge";
@@ -92,7 +92,7 @@ export const CronJobItem = ({
const dropdownMenuItems = [
{
label: t("cronjobs.editCronJob"),
icon: <Edit className="h-3 w-3" />,
icon: <PencilSimpleIcon className="h-3 w-3" />,
onClick: () => onEdit(job),
},
{
@@ -100,45 +100,45 @@ export const CronJobItem = ({
? t("cronjobs.disableLogging")
: t("cronjobs.enableLogging"),
icon: job.logsEnabled ? (
<FileX className="h-3 w-3" />
<FileXIcon className="h-3 w-3" />
) : (
<FileOutput className="h-3 w-3" />
<FileArrowDownIcon className="h-3 w-3" />
),
onClick: () => onToggleLogging(job.id),
},
...(job.logsEnabled
? [
{
label: t("cronjobs.viewLogs"),
icon: <FileText className="h-3 w-3" />,
onClick: () => onViewLogs(job),
},
]
{
label: t("cronjobs.viewLogs"),
icon: <FileTextIcon className="h-3 w-3" />,
onClick: () => onViewLogs(job),
},
]
: []),
{
label: job.paused
? t("cronjobs.resumeCronJob")
: t("cronjobs.pauseCronJob"),
icon: job.paused ? (
<Play className="h-3 w-3" />
<PlayIcon className="h-3 w-3" />
) : (
<Pause className="h-3 w-3" />
<PauseIcon className="h-3 w-3" />
),
onClick: () => (job.paused ? onResume(job.id) : onPause(job.id)),
},
{
label: t("cronjobs.cloneCronJob"),
icon: <Files className="h-3 w-3" />,
icon: <FilesIcon className="h-3 w-3" />,
onClick: () => onClone(job),
},
{
label: t("cronjobs.backupJob"),
icon: <Download className="h-3 w-3" />,
icon: <DownloadIcon className="h-3 w-3" />,
onClick: () => onBackup(job.id),
},
{
label: t("cronjobs.deleteCronJob"),
icon: <Trash2 className="h-3 w-3" />,
icon: <TrashIcon className="h-3 w-3" />,
onClick: () => onDelete(job),
variant: "destructive" as const,
disabled: deletingId === job.id,
@@ -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">
<InfoIcon 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" />
<CheckIcon 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">
<InfoIcon 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">
<User className="h-3 w-3" />
<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">
<UserIcon 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,22 +226,22 @@ export const CronJobItem = ({
}}
>
{showCopyConfirmation ? (
<Check className="h-3 w-3 text-green-600" />
<CheckIcon className="h-3 w-3 text-status-success" />
) : (
<Hash className="h-3 w-3" />
<HashIcon className="h-3 w-3" />
)}
<span className="font-mono">{job.id}</span>
</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>
<WarningCircleIcon 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" />
<CheckCircleIcon className="h-3 w-3 text-status-success" />
<span className="text-status-warning">{t("cronjobs.healthy")}</span>
<WarningIcon 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">
<CheckCircleIcon className="h-3 w-3 text-status-success" />
<span className="text-status-success">{t("cronjobs.healthy")}</span>
</div>
)}
@@ -315,7 +314,7 @@ export const CronJobItem = ({
{runningJobId === job.id ? (
<div className="h-3 w-3 animate-spin rounded-full border-2 border-current border-t-transparent" />
) : (
<Code className="h-3 w-3" />
<CodeIcon className="h-3 w-3" />
)}
</Button>
@@ -334,9 +333,9 @@ export const CronJobItem = ({
aria-label={t("cronjobs.pauseCronJob")}
>
{job.paused ? (
<Play className="h-3 w-3" />
<PlayIcon className="h-3 w-3" />
) : (
<Pause className="h-3 w-3" />
<PauseIcon className="h-3 w-3" />
)}
</Button>
@@ -363,9 +362,9 @@ export const CronJobItem = ({
}
>
{job.logsEnabled ? (
<FileText className="h-3 w-3" />
<FileTextIcon className="h-3 w-3" />
) : (
<FileOutput className="h-3 w-3" />
<FileArrowDownIcon className="h-3 w-3" />
)}
</Button>
</div>

View File

@@ -4,19 +4,19 @@ import { useState, useEffect } from "react";
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
import { DropdownMenu } from "@/app/_components/GlobalComponents/UIElements/DropdownMenu";
import {
Trash2,
Edit,
Files,
Play,
Pause,
Code,
Info,
Download,
Check,
FileX,
FileText,
FileOutput,
} from "lucide-react";
TrashIcon,
PencilSimpleIcon,
FilesIcon,
PlayIcon,
PauseIcon,
CodeIcon,
InfoIcon,
DownloadIcon,
CheckIcon,
FileXIcon,
FileTextIcon,
FileArrowDownIcon,
} from "@phosphor-icons/react";
import { CronJob } from "@/app/_utils/cronjob-utils";
import { JobError } from "@/app/_utils/error-utils";
import {
@@ -83,7 +83,7 @@ export const MinimalCronJobItem = ({
const dropdownMenuItems = [
{
label: t("cronjobs.editCronJob"),
icon: <Edit className="h-3 w-3" />,
icon: <PencilSimpleIcon className="h-3 w-3" />,
onClick: () => onEdit(job),
},
{
@@ -91,9 +91,9 @@ export const MinimalCronJobItem = ({
? t("cronjobs.disableLogging")
: t("cronjobs.enableLogging"),
icon: job.logsEnabled ? (
<FileX className="h-3 w-3" />
<FileXIcon className="h-3 w-3" />
) : (
<Code className="h-3 w-3" />
<CodeIcon className="h-3 w-3" />
),
onClick: () => onToggleLogging(job.id),
},
@@ -101,7 +101,7 @@ export const MinimalCronJobItem = ({
? [
{
label: t("cronjobs.viewLogs"),
icon: <Code className="h-3 w-3" />,
icon: <CodeIcon className="h-3 w-3" />,
onClick: () => onViewLogs(job),
},
]
@@ -111,25 +111,25 @@ export const MinimalCronJobItem = ({
? t("cronjobs.resumeCronJob")
: t("cronjobs.pauseCronJob"),
icon: job.paused ? (
<Play className="h-3 w-3" />
<PlayIcon className="h-3 w-3" />
) : (
<Pause className="h-3 w-3" />
<PauseIcon className="h-3 w-3" />
),
onClick: () => (job.paused ? onResume(job.id) : onPause(job.id)),
},
{
label: t("cronjobs.cloneCronJob"),
icon: <Files className="h-3 w-3" />,
icon: <FilesIcon className="h-3 w-3" />,
onClick: () => onClone(job),
},
{
label: t("cronjobs.backupJob"),
icon: <Download className="h-3 w-3" />,
icon: <DownloadIcon className="h-3 w-3" />,
onClick: () => onBackup(job.id),
},
{
label: t("cronjobs.deleteCronJob"),
icon: <Trash2 className="h-3 w-3" />,
icon: <TrashIcon className="h-3 w-3" />,
onClick: () => onDelete(job),
variant: "destructive" as const,
disabled: deletingId === job.id,
@@ -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-background0 text-status-warning 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">
<InfoIcon 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" />
<InfoIcon 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" />
<CheckIcon 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,31 +225,32 @@ 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" />
) : (
<Code className="h-3 w-3" />
<CodeIcon className="h-3 w-3" />
)}
</Button>
<Button
variant="ghost"
variant="outline"
size="sm"
onClick={() => {
if (job.paused) {
@@ -258,18 +259,19 @@ 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" />
<PlayIcon className="h-3 w-3" />
) : (
<Pause className="h-3 w-3" />
<PauseIcon className="h-3 w-3" />
)}
</Button>
<Button
variant="ghost"
variant="outline"
size="sm"
onClick={() => {
if (job.logsEnabled) {
@@ -278,17 +280,22 @@ 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" />
<FileTextIcon className="h-3 w-3" />
) : (
<FileOutput className="h-3 w-3" />
<FileArrowDownIcon className="h-3 w-3" />
)}
</Button>

View File

@@ -2,15 +2,15 @@ import { cn } from "@/app/_utils/global-utils";
import { HTMLAttributes, forwardRef, useState, useEffect } from "react";
import React from "react";
import {
ChevronLeft,
ChevronRight,
Server,
Menu,
X,
Cpu,
HardDrive,
Wifi,
} from "lucide-react";
CaretLeftIcon,
CaretRightIcon,
HardDrivesIcon,
ListIcon,
XIcon,
CpuIcon,
HardDriveIcon,
WifiHighIcon,
} from "@phosphor-icons/react";
import { useTranslations } from "next-intl";
export interface SidebarProps extends HTMLAttributes<HTMLDivElement> {
@@ -54,18 +54,18 @@ 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" />
<XIcon className="h-5 w-5" />
) : (
<Menu className="h-5 w-5" />
<ListIcon className="h-5 w-5" />
)}
</button>
<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,27 +92,27 @@ 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" />
<CaretRightIcon className="h-3 w-3" />
) : (
<ChevronLeft className="h-3 w-3" />
<CaretLeftIcon className="h-3 w-3" />
)}
</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">
<HardDrivesIcon 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>
)}
@@ -121,7 +121,7 @@ export const Sidebar = forwardRef<HTMLDivElement, SidebarProps>(
<div
className={cn(
"overflow-y-auto custom-scrollbar",
"overflow-y-auto tui-scrollbar",
isCollapsed ? "lg:p-2" : "p-4",
"h-full lg:h-[calc(100vh-88px-80px)]"
)}
@@ -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">
<CpuIcon 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">
<HardDriveIcon 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">
<WifiHighIcon 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,9 +163,9 @@ 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" />
<HardDrivesIcon className="h-4 w-4 text-muted-foreground" />
</div>
);
}

View File

@@ -5,7 +5,7 @@ import { CronJobList } from "@/app/_components/FeatureComponents/Cronjobs/CronJo
import { ScriptsManager } from "@/app/_components/FeatureComponents/Scripts/ScriptsManager";
import { CronJob } from "@/app/_utils/cronjob-utils";
import { Script } from "@/app/_utils/scripts-utils";
import { Clock, FileText } from "lucide-react";
import { ClockIcon, FileTextIcon } from "@phosphor-icons/react";
import { useTranslations } from "next-intl";
interface TabbedInterfaceProps {
@@ -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 flex-1 justify-center terminal-font ${activeTab === "cronjobs"
? "bg-background0 ascii-border"
: "hover:ascii-border"
}`}
>
<Clock className="h-4 w-4" />
<ClockIcon 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 flex-1 justify-center terminal-font ${activeTab === "scripts"
? "bg-background0 ascii-border"
: "hover:ascii-border"
}`}
>
<FileText className="h-4 w-4" />
<FileTextIcon 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

@@ -12,7 +12,7 @@ import {
CardDescription,
CardContent,
} from "@/app/_components/GlobalComponents/Cards/Card";
import { Lock, Eye, EyeOff, Shield, AlertTriangle, Loader2 } from "lucide-react";
import { LockIcon, EyeIcon, EyeSlashIcon, ShieldIcon, WarningIcon, CircleNotchIcon } from "@phosphor-icons/react";
interface LoginFormProps {
hasPassword?: boolean;
@@ -88,7 +88,7 @@ export const LoginForm = ({
<Card className="w-full max-w-md shadow-xl">
<CardContent className="pt-6">
<div className="flex flex-col items-center justify-center py-8 space-y-4">
<Loader2 className="w-12 h-12 text-primary animate-spin" />
<CircleNotchIcon className="w-12 h-12 text-primary animate-spin" />
<div className="text-center">
<p className="text-lg font-medium">{t("login.redirectingToOIDC")}</p>
<p className="text-sm text-muted-foreground mt-1">
@@ -105,15 +105,15 @@ export const LoginForm = ({
<Card className="w-full max-w-md shadow-xl">
<CardHeader className="text-center">
<div className="mx-auto w-16 h-16 bg-primary/10 rounded-full flex items-center justify-center mb-4">
<Lock className="w-8 h-8 text-primary" />
<LockIcon className="w-8 h-8 text-primary" />
</div>
<CardTitle>{t("login.welcomeTitle")}</CardTitle>
<CardDescription>
{hasPassword && hasOIDC
? t("login.signInWithPasswordOrSSO")
: hasOIDC
? t("login.signInWithSSO")
: t("login.enterPasswordToContinue")}
? t("login.signInWithSSO")
: t("login.enterPasswordToContinue")}
</CardDescription>
</CardHeader>
@@ -121,7 +121,7 @@ export const LoginForm = ({
{!hasPassword && !hasOIDC && (
<div className="mb-4 p-3 bg-amber-500/10 border border-amber-500/20 rounded-md">
<div className="flex items-start space-x-2">
<AlertTriangle className="w-4 h-4 text-amber-500 mt-0.5 flex-shrink-0" />
<WarningIcon className="w-4 h-4 text-amber-500 mt-0.5 flex-shrink-0" />
<div className="text-sm text-amber-700 dark:text-amber-400">
<div className="font-medium">
{t("login.authenticationNotConfigured")}
@@ -152,9 +152,9 @@ export const LoginForm = ({
disabled={isLoading}
>
{showPassword ? (
<EyeOff className="w-4 h-4" />
<EyeSlashIcon className="w-4 h-4" />
) : (
<Eye className="w-4 h-4" />
<EyeIcon className="w-4 h-4" />
)}
</button>
</div>
@@ -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>
@@ -190,7 +190,7 @@ export const LoginForm = ({
onClick={handleOIDCLogin}
disabled={isLoading}
>
<Shield className="w-4 h-4 mr-2" />
<ShieldIcon className="w-4 h-4 mr-2" />
{isLoading ? t("login.redirecting") : t("login.signInWithSSO")}
</Button>
)}
@@ -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

@@ -3,7 +3,7 @@
import { useState } from "react";
import { useRouter } from "next/navigation";
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
import { LogOut } from "lucide-react";
import { SignOutIcon } from "@phosphor-icons/react";
export const LogoutButton = () => {
const [isLoading, setIsLoading] = useState(false);
@@ -35,7 +35,7 @@ export const LogoutButton = () => {
disabled={isLoading}
title="Logout"
>
<LogOut className="h-[1.2rem] w-[1.2rem]" />
<SignOutIcon className="h-[1.2rem] w-[1.2rem]" />
<span className="sr-only">Logout</span>
</Button>
);

View File

@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
import { Copy } from "lucide-react";
import { CopyIcon } from "@phosphor-icons/react";
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
import { Modal } from "@/app/_components/GlobalComponents/UIElements/Modal";
import { Input } from "@/app/_components/GlobalComponents/FormElements/Input";
@@ -89,7 +89,7 @@ export const CloneScriptModal = ({
</>
) : (
<>
<Copy className="h-4 w-4 mr-2" />
<CopyIcon className="h-4 w-4 mr-2" />
Clone Script
</>
)}

View File

@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
import { Copy } from "lucide-react";
import { CopyIcon } from "@phosphor-icons/react";
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
import { Modal } from "@/app/_components/GlobalComponents/UIElements/Modal";
import { Input } from "@/app/_components/GlobalComponents/FormElements/Input";
@@ -89,7 +89,7 @@ export const CloneTaskModal = ({
</>
) : (
<>
<Copy className="h-4 w-4 mr-2" />
<CopyIcon className="h-4 w-4 mr-2" />
Clone Cron Job
</>
)}

View File

@@ -1,6 +1,6 @@
"use client";
import { Plus } from "lucide-react";
import { PlusIcon } from "@phosphor-icons/react";
import { ScriptModal } from "@/app/_components/FeatureComponents/Modals/ScriptModal";
interface CreateScriptModalProps {
@@ -35,7 +35,7 @@ export const CreateScriptModal = ({
onSubmit={onSubmit}
title="Create New Script"
submitButtonText="Create Script"
submitButtonIcon={<Plus className="h-4 w-4 mr-2" />}
submitButtonIcon={<PlusIcon className="h-4 w-4 mr-2" />}
form={form}
onFormChange={onFormChange}
isDraft={isDraft}

View File

@@ -7,7 +7,7 @@ import { Input } from "@/app/_components/GlobalComponents/FormElements/Input";
import { CronExpressionHelper } from "@/app/_components/FeatureComponents/Scripts/CronExpressionHelper";
import { SelectScriptModal } from "@/app/_components/FeatureComponents/Modals/SelectScriptModal";
import { UserSwitcher } from "@/app/_components/FeatureComponents/User/UserSwitcher";
import { Plus, Terminal, FileText, X, FileOutput } from "lucide-react";
import { PlusIcon, TerminalIcon, FileTextIcon, XIcon, FileArrowDownIcon } from "@phosphor-icons/react";
import { getScriptContent } from "@/app/_server/actions/scripts";
import { getHostScriptPath } from "@/app/_server/actions/scripts";
import { useTranslations } from "next-intl";
@@ -100,7 +100,7 @@ export const CreateTaskModal = ({
</label>
<UserSwitcher
selectedUser={form.user}
onUserChange={(user) => onFormChange({ user })}
onUserChange={(user: string) => onFormChange({ user })}
/>
</div>
@@ -124,13 +124,13 @@ 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">
<Terminal className="h-5 w-5" />
<TerminalIcon className="h-5 w-5" />
<div className="text-left">
<div className="font-medium">
{t("cronjobs.customCommand")}
@@ -145,13 +145,13 @@ 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">
<FileText className="h-5 w-5" />
<FileTextIcon className="h-5 w-5" />
<div className="text-left">
<div className="font-medium">
{t("scripts.savedScript")}
@@ -170,7 +170,7 @@ export const CreateTaskModal = ({
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<FileText className="h-4 w-4 text-primary" />
<FileTextIcon className="h-4 w-4 text-primary" />
<h4 className="font-medium text-foreground">
{selectedScript.name}
</h4>
@@ -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>
@@ -201,7 +201,7 @@ export const CreateTaskModal = ({
onClick={handleClearScript}
className="h-8 w-8 p-0 text-muted-foreground hover:text-foreground"
>
<X className="h-4 w-4" />
<XIcon className="h-4 w-4" />
</Button>
</div>
</div>
@@ -222,12 +222,12 @@ 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}
/>
<div className="absolute right-3 top-2">
<Terminal className="h-4 w-4 text-muted-foreground" />
<TerminalIcon className="h-4 w-4 text-muted-foreground" />
</div>
</div>
{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"
@@ -269,7 +269,7 @@ export const CreateTaskModal = ({
htmlFor="logsEnabled"
className="flex items-center gap-2 text-sm font-medium text-foreground cursor-pointer"
>
<FileOutput className="h-4 w-4 text-primary" />
<FileArrowDownIcon className="h-4 w-4 text-primary" />
{t("cronjobs.enableLogging")}
</label>
<p className="text-xs text-muted-foreground mt-1">
@@ -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"
@@ -289,7 +289,7 @@ export const CreateTaskModal = ({
{t("common.cancel")}
</Button>
<Button type="submit" className="btn-primary glow-primary">
<Plus className="h-4 w-4 mr-2" />
<PlusIcon className="h-4 w-4 mr-2" />
{t("cronjobs.createTask")}
</Button>
</div>

View File

@@ -2,7 +2,7 @@
import { Modal } from "@/app/_components/GlobalComponents/UIElements/Modal";
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
import { FileText, AlertCircle, Trash2 } from "lucide-react";
import { FileTextIcon, WarningCircleIcon, TrashIcon } from "@phosphor-icons/react";
import { Script } from "@/app/_utils/scripts-utils";
interface DeleteScriptModalProps {
@@ -25,10 +25,10 @@ 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" />
<FileTextIcon className="h-3 w-3 text-muted-foreground" />
<span className="text-xs font-medium text-foreground">
{script.name}
</span>
@@ -36,7 +36,7 @@ export const DeleteScriptModal = ({
{script.description && (
<div className="flex items-start gap-2">
<FileText className="h-3 w-3 text-muted-foreground mt-0.5 flex-shrink-0" />
<FileTextIcon className="h-3 w-3 text-muted-foreground mt-0.5 flex-shrink-0" />
<p className="text-xs text-muted-foreground break-words italic">
{script.description}
</p>
@@ -44,8 +44,8 @@ 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">
<FileTextIcon 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">
{script.filename}
</code>
</div>
@@ -54,7 +54,7 @@ export const DeleteScriptModal = ({
<div className="bg-destructive/5 border border-destructive/20 rounded p-2">
<div className="flex items-start gap-2">
<AlertCircle className="h-4 w-4 text-destructive mt-0.5 flex-shrink-0" />
<WarningCircleIcon className="h-4 w-4 text-destructive mt-0.5 flex-shrink-0" />
<div>
<p className="text-xs font-medium text-destructive mb-0.5">
This action cannot be undone
@@ -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}
@@ -88,7 +88,7 @@ export const DeleteScriptModal = ({
</>
) : (
<>
<Trash2 className="h-4 w-4 mr-2" />
<TrashIcon className="h-4 w-4 mr-2" />
Delete Script
</>
)}

View File

@@ -4,11 +4,11 @@ import { Modal } from "@/app/_components/GlobalComponents/UIElements/Modal";
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
import {
Calendar,
Terminal,
MessageSquare,
AlertCircle,
Trash2,
} from "lucide-react";
TerminalIcon,
ChatTextIcon,
WarningCircleIcon,
TrashIcon,
} from "@phosphor-icons/react";
import { CronJob } from "@/app/_utils/cronjob-utils";
interface DeleteTaskModalProps {
@@ -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" />
@@ -44,15 +44,15 @@ export const DeleteTaskModal = ({
</div>
<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">
<TerminalIcon 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 flex-1 hide-scrollbar">
{job.command}
</pre>
</div>
{job.comment && (
<div className="flex items-start gap-2">
<MessageSquare className="h-3 w-3 text-muted-foreground mt-0.5 flex-shrink-0" />
<ChatTextIcon className="h-3 w-3 text-muted-foreground mt-0.5 flex-shrink-0" />
<p className="text-xs text-muted-foreground break-words italic">
{job.comment}
</p>
@@ -63,7 +63,7 @@ export const DeleteTaskModal = ({
<div className="bg-destructive/5 border border-destructive/20 rounded p-2">
<div className="flex items-start gap-2">
<AlertCircle className="h-4 w-4 text-destructive mt-0.5 flex-shrink-0" />
<WarningCircleIcon className="h-4 w-4 text-destructive mt-0.5 flex-shrink-0" />
<div>
<p className="text-xs font-medium text-destructive mb-0.5">
This action cannot be undone
@@ -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>
@@ -84,7 +84,7 @@ export const DeleteTaskModal = ({
onClick={onConfirm}
className="btn-destructive"
>
<Trash2 className="h-4 w-4 mr-2" />
<TrashIcon className="h-4 w-4 mr-2" />
Delete Task
</Button>
</div>

View File

@@ -1,6 +1,6 @@
"use client";
import { Edit } from "lucide-react";
import { PencilSimpleIcon } from "@phosphor-icons/react";
import { Script } from "@/app/_utils/scripts-utils";
import { ScriptModal } from "@/app/_components/FeatureComponents/Modals/ScriptModal";
@@ -34,9 +34,9 @@ export const EditScriptModal = ({
isOpen={isOpen}
onClose={onClose}
onSubmit={onSubmit}
title="Edit Script"
title="PencilSimpleIcon Script"
submitButtonText="Update Script"
submitButtonIcon={<Edit className="h-4 w-4 mr-2" />}
submitButtonIcon={<PencilSimpleIcon className="h-4 w-4 mr-2" />}
form={form}
onFormChange={onFormChange}
additionalFormData={{ id: script.id }}

View File

@@ -4,7 +4,7 @@ import { Modal } from "@/app/_components/GlobalComponents/UIElements/Modal";
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
import { Input } from "@/app/_components/GlobalComponents/FormElements/Input";
import { CronExpressionHelper } from "@/app/_components/FeatureComponents/Scripts/CronExpressionHelper";
import { Edit, Terminal, FileOutput } from "lucide-react";
import { PencilSimpleIcon, TerminalIcon, FileArrowDownIcon } from "@phosphor-icons/react";
import { useTranslations } from "next-intl";
interface EditTaskModalProps {
@@ -59,11 +59,11 @@ 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">
<Terminal className="h-4 w-4 text-muted-foreground" />
<TerminalIcon className="h-4 w-4 text-muted-foreground" />
</div>
</div>
</div>
@@ -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"
@@ -98,7 +98,7 @@ export const EditTaskModal = ({
htmlFor="logsEnabled"
className="flex items-center gap-2 text-sm font-medium text-foreground cursor-pointer"
>
<FileOutput className="h-4 w-4 text-primary" />
<FileArrowDownIcon className="h-4 w-4 text-primary" />
{t("cronjobs.enableLogging")}
</label>
<p className="text-xs text-muted-foreground mt-1">
@@ -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"
@@ -118,7 +118,7 @@ export const EditTaskModal = ({
Cancel
</Button>
<Button type="submit" className="btn-primary glow-primary">
<Edit className="h-4 w-4 mr-2" />
<PencilSimpleIcon className="h-4 w-4 mr-2" />
Update Task
</Button>
</div>

View File

@@ -2,7 +2,7 @@
import { Modal } from "@/app/_components/GlobalComponents/UIElements/Modal";
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
import { AlertCircle, Copy, X } from "lucide-react";
import { WarningCircleIcon, CopyIcon, XIcon } from "@phosphor-icons/react";
import { showToast } from "@/app/_components/GlobalComponents/UIElements/Toast";
interface ErrorDetails {
@@ -54,7 +54,7 @@ Timestamp: ${error.timestamp}
<div className="space-y-4">
<div className="bg-destructive/5 border border-destructive/20 rounded-lg p-4">
<div className="flex items-start gap-3">
<AlertCircle className="h-5 w-5 text-destructive mt-0.5 flex-shrink-0" />
<WarningCircleIcon className="h-5 w-5 text-destructive mt-0.5 flex-shrink-0" />
<div className="flex-1">
<h3 className="font-medium text-destructive mb-1">
{error.title}
@@ -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 tui-scrollbar">
<pre className="text-sm font-mono text-foreground whitespace-pre-wrap">
{error.output}
</pre>
@@ -106,7 +106,7 @@ Timestamp: ${error.timestamp}
<h4 className="text-sm font-medium text-foreground mb-2">
Error Output
</h4>
<div className="bg-destructive/5 p-3 rounded border border-destructive/20 max-h-32 overflow-y-auto">
<div className="bg-destructive/5 p-3 rounded border border-destructive/20 max-h-32 overflow-y-auto tui-scrollbar">
<pre className="text-sm font-mono text-destructive whitespace-pre-wrap">
{error.stderr}
</pre>
@@ -118,14 +118,14 @@ 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}
className="btn-outline"
>
<Copy className="h-4 w-4 mr-2" />
Copy Details
<CopyIcon className="h-4 w-4 mr-2" />
CopyIcon Details
</Button>
<Button onClick={onClose} className="btn-primary">
Close

View File

@@ -3,7 +3,7 @@
import { useState, useEffect } from "react";
import { Modal } from "@/app/_components/GlobalComponents/UIElements/Modal";
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
import { ChevronDown, Code, MessageSquare } from "lucide-react";
import { CaretDownIcon, CodeIcon, ChatTextIcon } from "@phosphor-icons/react";
import { UserFilter } from "@/app/_components/FeatureComponents/User/UserFilter";
import { useTranslations } from "next-intl";
@@ -72,15 +72,15 @@ export const FiltersModal = ({
>
<div className="flex items-center">
{localScheduleMode === "cron" && (
<Code className="h-4 w-4 mr-2" />
<CodeIcon className="h-4 w-4 mr-2" />
)}
{localScheduleMode === "human" && (
<MessageSquare className="h-4 w-4 mr-2" />
<ChatTextIcon className="h-4 w-4 mr-2" />
)}
{localScheduleMode === "both" && (
<>
<Code className="h-4 w-4 mr-1" />
<MessageSquare className="h-4 w-4 mr-2" />
<CodeIcon className="h-4 w-4 mr-1" />
<ChatTextIcon className="h-4 w-4 mr-2" />
</>
)}
<span>
@@ -90,23 +90,22 @@ export const FiltersModal = ({
{localScheduleMode === "both" && t("cronjobs.both")}
</span>
</div>
<ChevronDown className="h-4 w-4 ml-2" />
<CaretDownIcon className="h-4 w-4 ml-2" />
</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" />
<CodeIcon className="h-3 w-3" />
{t("cronjobs.cronSyntax")}
</button>
<button
@@ -114,13 +113,12 @@ 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" />
<ChatTextIcon className="h-3 w-3" />
{t("cronjobs.humanReadable")}
</button>
<button
@@ -128,14 +126,13 @@ 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" />
<CodeIcon className="h-3 w-3" />
<ChatTextIcon className="h-3 w-3" />
{t("cronjobs.both")}
</button>
</div>
@@ -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

@@ -1,7 +1,7 @@
"use client";
import { useState, useEffect, useRef, useCallback } from "react";
import { Loader2, CheckCircle2, XCircle, AlertTriangle, Minimize2, Maximize2 } from "lucide-react";
import { CircleNotchIcon, CheckCircleIcon, XCircleIcon, WarningIcon, ArrowsInIcon, ArrowsOutIcon } from "@phosphor-icons/react";
import { Modal } from "@/app/_components/GlobalComponents/UIElements/Modal";
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
import { useSSEContext } from "@/app/_contexts/SSEContext";
@@ -228,20 +228,20 @@ 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">
<Loader2 className="w-4 h-4 animate-spin" />
<span className="flex items-center gap-1 text-sm text-status-info">
<CircleNotchIcon 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">
<CheckCircle2 className="w-4 h-4" />
<span className="flex items-center gap-1 text-sm text-status-success">
<CheckCircleIcon 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">
<XCircle className="w-4 h-4" />
<span className="flex items-center gap-1 text-sm text-status-error">
<XCircleIcon 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,8 +316,8 @@ export const LiveLogModal = ({
)}
</div>
{truncated && !showFullLog && (
<div className="text-sm text-orange-500 flex items-center gap-1">
<AlertTriangle className="h-4 w-4" />
<div className="text-sm text-status-warning flex items-center gap-1 terminal-font">
<WarningIcon className="h-4 w-4" />
{t("cronjobs.showingLastOf", {
lineCount: lineCount.toLocaleString(),
totalLines: totalLines.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">
<WarningIcon 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,16 +340,16 @@ 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" />}
{tailMode ? <ArrowsOutIcon className="h-3 w-3" /> : <ArrowsInIcon className="h-3 w-3" />}
</Button>
</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

@@ -3,7 +3,7 @@
import { useState, useEffect } from "react";
import { Modal } from "@/app/_components/GlobalComponents/UIElements/Modal";
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
import { FileText, Trash2, Eye, X, RefreshCw, AlertCircle, CheckCircle } from "lucide-react";
import { FileTextIcon, TrashIcon, EyeIcon, XIcon, ArrowsClockwiseIcon, WarningCircleIcon, CheckCircleIcon } from "@phosphor-icons/react";
import { useTranslations } from "next-intl";
import {
getJobLogs,
@@ -173,7 +173,7 @@ export const LogsModal = ({
className="btn-primary glow-primary"
size="sm"
>
<RefreshCw
<ArrowsClockwiseIcon
className={`w-4 h-4 mr-2 ${isLoadingLogs ? "animate-spin" : ""
}`}
/>
@@ -185,7 +185,7 @@ export const LogsModal = ({
className="btn-destructive glow-primary"
size="sm"
>
<Trash2 className="w-4 h-4 mr-2" />
<TrashIcon className="w-4 h-4 mr-2" />
{t("cronjobs.deleteAll")}
</Button>
)}
@@ -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,11 +220,11 @@ 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" />
<WarningCircleIcon 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" />
<CheckCircleIcon className="w-4 h-4 flex-shrink-0 text-status-success" />
) : (
<FileText className="w-4 h-4 flex-shrink-0" />
<FileTextIcon className="w-4 h-4 flex-shrink-0" />
)}
<span className="text-sm font-medium truncate">
{formatTimestamp(log.timestamp)}
@@ -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}
@@ -254,7 +254,7 @@ export const LogsModal = ({
className="btn-destructive glow-primary p-1 h-auto"
size="sm"
>
<Trash2 className="w-3 h-3" />
<TrashIcon className="w-3 h-3" />
</Button>
</div>
</div>
@@ -271,13 +271,13 @@ 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>
) : (
<div className="h-full flex items-center justify-center text-muted-foreground">
<div className="text-center">
<Eye className="w-12 h-12 mx-auto mb-2 opacity-50" />
<EyeIcon className="w-12 h-12 mx-auto mb-2 opacity-50" />
<p>{t("cronjobs.selectLogToView")}</p>
</div>
</div>
@@ -288,7 +288,7 @@ export const LogsModal = ({
<div className="mt-4 pt-4 border-t border-border flex justify-end">
<Button onClick={onClose} className="btn-primary glow-primary">
<X className="w-4 h-4 mr-2" />
<XIcon className="w-4 h-4 mr-2" />
{t("common.close")}
</Button>
</div>

View File

@@ -4,14 +4,14 @@ import { useState, useEffect } from "react";
import { Modal } from "@/app/_components/GlobalComponents/UIElements/Modal";
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
import {
Upload,
Trash2,
Calendar,
User,
Download,
RefreshCw,
Check,
} from "lucide-react";
UploadIcon,
TrashIcon,
CalendarIcon,
UserIcon,
DownloadIcon,
ArrowsClockwiseIcon,
CheckIcon,
} from "@phosphor-icons/react";
import { useTranslations } from "next-intl";
import { CronJob } from "@/app/_utils/cronjob-utils";
import { unwrapCommand } from "@/app/_utils/wrapper-utils-client";
@@ -92,7 +92,7 @@ export const RestoreBackupModal = ({
onClick={onBackupAll}
className="btn-outline flex-1"
>
<Download className="h-4 w-4 mr-2" />
<DownloadIcon className="h-4 w-4 mr-2" />
{t("cronjobs.backupAll")}
</Button>
{backups.length > 0 && (
@@ -101,7 +101,7 @@ export const RestoreBackupModal = ({
onClick={handleRestoreAll}
className="btn-primary flex-1"
>
<Upload className="h-4 w-4 mr-2" />
<UploadIcon className="h-4 w-4 mr-2" />
{t("cronjobs.restoreAll")}
</Button>
)}
@@ -111,7 +111,7 @@ export const RestoreBackupModal = ({
className="btn-outline"
title={t("common.refresh")}
>
<RefreshCw className="h-4 w-4" />
<ArrowsClockwiseIcon className="h-4 w-4" />
</Button>
</div>
@@ -120,15 +120,15 @@ export const RestoreBackupModal = ({
<p>{t("cronjobs.noBackupsFound")}</p>
</div>
) : (
<div className="space-y-2 max-h-[500px] overflow-y-auto">
<div className="space-y-3 max-h-[500px] overflow-y-auto tui-scrollbar">
{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="tui-card p-3 terminal-font"
>
<div className="flex items-center gap-3">
<div className="flex-shrink-0">
<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-background0 text-status-warning px-1.5 py-0.5 terminal-font ascii-border">
{backup.job.schedule}
</code>
</div>
@@ -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" />
<CheckIcon 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 terminal-font bg-background1 px-2 py-1 ascii-border truncate"
title={unwrapCommand(backup.job.command)}
>
{unwrapCommand(backup.job.command)}
@@ -155,47 +155,47 @@ export const RestoreBackupModal = ({
<div className="flex items-center gap-3 text-xs text-muted-foreground flex-shrink-0">
<div className="flex items-center gap-1">
<User className="h-3 w-3" />
<UserIcon className="h-3 w-3" />
<span>{backup.job.user}</span>
</div>
<div className="flex items-center gap-1">
<Calendar className="h-3 w-3" />
<CalendarIcon className="h-3 w-3" />
<span>{formatDate(backup.backedUpAt)}</span>
</div>
</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={() => {
onRestore(backup.filename);
onClose();
}}
className="h-7 w-7 p-0"
className="btn-outline h-8 px-3"
title={t("cronjobs.restoreThisBackup")}
>
<Upload className="h-3 w-3" />
<UploadIcon className="h-3 w-3" />
</Button>
<Button
variant="ghost"
variant="outline"
size="sm"
onClick={() => handleDelete(backup.filename)}
disabled={deletingFilename === backup.filename}
className="h-7 w-7 p-0 text-destructive hover:text-destructive"
className="btn-destructive h-8 px-3"
title={t("cronjobs.deleteBackup")}
>
{deletingFilename === backup.filename ? (
<div className="h-3 w-3 animate-spin rounded-full border-2 border-current border-t-transparent" />
) : (
<Trash2 className="h-3 w-3" />
<TrashIcon className="h-3 w-3" />
)}
</Button>
</div>
</div>
{backup.job.comment && (
<p className="text-xs text-muted-foreground italic mt-2 ml-0">
<p className="text-xs text-muted-foreground italic mt-2">
{backup.job.comment}
</p>
)}
@@ -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

@@ -6,7 +6,7 @@ import { Input } from "@/app/_components/GlobalComponents/FormElements/Input";
import { BashEditor } from "@/app/_components/FeatureComponents/Scripts/BashEditor";
import { BashSnippetHelper } from "@/app/_components/FeatureComponents/Scripts/BashSnippetHelper";
import { showToast } from "@/app/_components/GlobalComponents/UIElements/Toast";
import { FileText, Code, Info, Trash2 } from "lucide-react";
import { FileTextIcon, CodeIcon, InfoIcon, TrashIcon } from "@phosphor-icons/react";
import { useTranslations } from "next-intl";
interface ScriptModalProps {
@@ -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>
<CodeIcon 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>
<FileTextIcon 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,16 +145,16 @@ 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" />
<TrashIcon className="h-4 w-4 mr-2" />
{t("scripts.clearDraft")}
</Button>
)}

View File

@@ -4,7 +4,7 @@ import { useEffect, useState } from "react";
import { Modal } from "@/app/_components/GlobalComponents/UIElements/Modal";
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
import { Input } from "@/app/_components/GlobalComponents/FormElements/Input";
import { FileText, Search, Check, Terminal } from "lucide-react";
import { FileTextIcon, MagnifyingGlassIcon, CheckIcon, TerminalIcon } from "@phosphor-icons/react";
import { Script } from "@/app/_utils/scripts-utils";
import { getScriptContent } from "@/app/_server/actions/scripts";
import { getHostScriptPath } from "@/app/_server/actions/scripts";
@@ -84,7 +84,7 @@ export const SelectScriptModal = ({
>
<div className="space-y-4">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<MagnifyingGlassIcon className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
@@ -119,12 +119,12 @@ export const SelectScriptModal = ({
<div className="flex items-start justify-between">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<FileText className="h-4 w-4 text-primary flex-shrink-0" />
<FileTextIcon className="h-4 w-4 text-primary flex-shrink-0" />
<h4 className="font-medium text-foreground truncate">
{script.name}
</h4>
{selectedScriptId === script.id && (
<Check className="h-4 w-4 text-green-500 flex-shrink-0" />
<CheckIcon className="h-4 w-4 text-status-success flex-shrink-0" />
)}
</div>
<p className="text-sm text-muted-foreground line-clamp-2">
@@ -148,7 +148,7 @@ export const SelectScriptModal = ({
{t("scripts.scriptPreview")}
</h3>
</div>
<div className="p-4 h-full max-h-80 overflow-y-auto">
<div className="p-4 h-full max-h-80 overflow-y-auto tui-scrollbar">
{previewScript ? (
<div className="space-y-4">
<div>
@@ -162,12 +162,12 @@ export const SelectScriptModal = ({
<div>
<div className="flex items-center gap-2 mb-2">
<Terminal className="h-4 w-4 text-primary" />
<TerminalIcon className="h-4 w-4 text-primary" />
<span className="text-sm font-medium text-foreground">
{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 tui-scrollbar">
<pre className="text-xs font-mono text-foreground whitespace-pre-wrap">
{previewContent}
</pre>
@@ -187,7 +187,7 @@ export const SelectScriptModal = ({
</div>
) : (
<div className="text-center text-muted-foreground py-8">
<FileText className="h-12 w-12 mx-auto mb-4 opacity-50" />
<FileTextIcon className="h-12 w-12 mx-auto mb-4 opacity-50" />
<p>{t("scripts.selectScriptToPreview")}</p>
</div>
)}
@@ -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"
@@ -210,7 +210,7 @@ export const SelectScriptModal = ({
disabled={!previewScript}
className="btn-primary glow-primary"
>
<Check className="h-4 w-4 mr-2" />
<CheckIcon className="h-4 w-4 mr-2" />
{t("scripts.selectScript")}
</Button>
</div>

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,9 +5,10 @@ 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";
import { TerminalIcon, CopyIcon, CheckIcon } from "@phosphor-icons/react";
interface BashEditorProps {
value: string;
@@ -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,54 @@ export const BashEditor = ({
useEffect(() => {
if (!editorRef.current) return;
const isDark = theme === 'catppuccin-mocha';
const bashLanguage = StreamLanguage.define(shell);
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 CodeIcon, 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 +162,7 @@ export const BashEditor = ({
"&": {
fontSize: "14px",
fontFamily:
'ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace',
'JetBrains Mono, Fira CodeIcon, ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace',
height: "100%",
maxHeight: "100%",
},
@@ -132,7 +175,7 @@ export const BashEditor = ({
},
".cm-scroller": {
fontFamily:
'ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace',
'JetBrains Mono, Fira CodeIcon, ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace',
height: "100%",
maxHeight: "100%",
},
@@ -150,7 +193,7 @@ export const BashEditor = ({
return () => {
view.destroy();
};
}, []);
}, [theme]);
useEffect(() => {
if (editorViewRef.current) {
@@ -181,7 +224,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" />
<TerminalIcon className="h-4 w-4" />
<span className="text-sm font-medium">{label}</span>
</div>
<Button
@@ -192,16 +235,16 @@ export const BashEditor = ({
className="btn-outline h-7 px-2"
>
{copied ? (
<Check className="h-3 w-3 mr-1" />
<CheckIcon className="h-3 w-3 mr-1" />
) : (
<Copy className="h-3 w-3 mr-1" />
<CopyIcon className="h-3 w-3 mr-1" />
)}
{copied ? "Copied!" : "Copy"}
{copied ? "Copied!" : "CopyIcon"}
</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

@@ -4,15 +4,15 @@ import { useState, useEffect } from "react";
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
import { Input } from "@/app/_components/GlobalComponents/FormElements/Input";
import {
Search,
FileText,
MagnifyingGlassIcon,
FileTextIcon,
FolderOpen,
Code,
Settings,
CodeIcon,
GearIcon,
Database,
Copy,
Check,
} from "lucide-react";
CopyIcon,
CheckIcon,
} from "@phosphor-icons/react";
import {
fetchSnippets,
fetchSnippetCategories,
@@ -25,13 +25,13 @@ interface BashSnippetHelperProps {
}
const categoryIcons = {
"File Operations": FileText,
Loops: Code,
Conditionals: Code,
"System Operations": Settings,
"File Operations": FileTextIcon,
Loops: CodeIcon,
Conditionals: CodeIcon,
"System Operations": GearIcon,
"Database Operations": Database,
"User Examples": FolderOpen,
"Custom Scripts": Code,
"UserIcon Examples": FolderOpen,
"Custom Scripts": CodeIcon,
};
export const BashSnippetHelper = ({
@@ -109,7 +109,7 @@ export const BashSnippetHelper = ({
return (
<div className="space-y-3">
<div className="text-center py-8">
<Code className="h-8 w-8 text-muted-foreground mx-auto mb-2 animate-spin" />
<CodeIcon className="h-8 w-8 text-muted-foreground mx-auto mb-2 animate-spin" />
<p className="text-sm text-muted-foreground">Loading snippets...</p>
</div>
</div>
@@ -119,17 +119,17 @@ export const BashSnippetHelper = ({
return (
<div className="space-y-3">
<div className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<MagnifyingGlassIcon className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search bash snippets..."
placeholder="MagnifyingGlassIcon bash snippets..."
className="pl-9"
/>
</div>
{!searchQuery && (
<div className="overflow-x-auto">
<div className="overflow-x-auto tui-scrollbar">
<div className="flex gap-1 pb-2 min-w-max">
<Button
type="button"
@@ -142,7 +142,7 @@ export const BashSnippetHelper = ({
</Button>
{categories.map((category) => {
const Icon =
categoryIcons[category as keyof typeof categoryIcons] || Code;
categoryIcons[category as keyof typeof categoryIcons] || CodeIcon;
return (
<Button
key={category}
@@ -163,15 +163,15 @@ export const BashSnippetHelper = ({
</div>
)}
<div className="space-y-2 overflow-y-auto !pr-0 custom-scrollbar">
<div className="space-y-2 overflow-y-auto !pr-0 tui-scrollbar">
{filteredSnippets.map((snippet) => {
const Icon =
categoryIcons[snippet.category as keyof typeof categoryIcons] ||
Code;
CodeIcon;
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">
@@ -181,7 +181,7 @@ export const BashSnippetHelper = ({
</h4>
{snippet.source === "user" && (
<span className="inline-block px-1.5 py-0.5 text-xs bg-green-100 text-green-700 rounded border border-green-200">
User
UserIcon
</span>
)}
</div>
@@ -212,9 +212,9 @@ export const BashSnippetHelper = ({
className="h-6 w-8 p-0 text-xs"
>
{copiedId === snippet.id ? (
<Check className="h-3 w-3" />
<CheckIcon className="h-3 w-3" />
) : (
<Copy className="h-3 w-3" />
<CopyIcon className="h-3 w-3" />
)}
</Button>
<Button
@@ -234,7 +234,7 @@ export const BashSnippetHelper = ({
{filteredSnippets.length === 0 && (
<div className="text-center py-8">
<Code className="h-8 w-8 text-muted-foreground mx-auto mb-2" />
<CodeIcon className="h-8 w-8 text-muted-foreground mx-auto mb-2" />
<p className="text-sm text-muted-foreground">
{searchQuery
? `No snippets found for "${searchQuery}"`

View File

@@ -9,15 +9,15 @@ import {
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
import { Input } from "@/app/_components/GlobalComponents/FormElements/Input";
import {
Clock,
Info,
CheckCircle,
AlertCircle,
ClockIcon,
InfoIcon,
CheckCircleIcon,
WarningCircleIcon,
Calendar,
ChevronDown,
ChevronUp,
Search,
} from "lucide-react";
CaretDownIcon,
CaretUpIcon,
MagnifyingGlassIcon,
} from "@phosphor-icons/react";
import { useLocale } from "next-intl";
interface CronExpressionHelperProps {
@@ -87,20 +87,20 @@ 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" />
<CheckCircleIcon className="h-4 w-4 text-status-success" />
) : value ? (
<AlertCircle className="h-4 w-4 text-red-500" />
<WarningCircleIcon className="h-4 w-4 text-status-error" />
) : (
<Clock className="h-4 w-4 text-muted-foreground" />
<ClockIcon className="h-4 w-4 text-muted-foreground" />
)}
</div>
</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" />
<InfoIcon className="h-3 w-3 text-primary mt-0.5 flex-shrink-0" />
<div className="flex-1">
<p className="text-xs italic text-muted-foreground">
{explanation.isValid
@@ -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,33 +145,33 @@ 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>
<div className="p-1">
{showPatternsPanel ? (
<ChevronUp className="h-4 w-4" />
<CaretUpIcon className="h-4 w-4" />
) : (
<ChevronDown className="h-4 w-4" />
<CaretDownIcon className="h-4 w-4" />
)}
</div>
</div>
</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" />
<MagnifyingGlassIcon className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
value={patternSearch}
onChange={(e) => setPatternSearch(e.target.value)}
placeholder="Search patterns..."
placeholder="MagnifyingGlassIcon patterns..."
className="pl-9"
/>
</div>
<div className="space-y-3 max-h-64 overflow-y-auto custom-scrollbar">
<div className="space-y-3 max-h-64 overflow-y-auto tui-scrollbar">
{filteredPatterns.map((category) => (
<div key={category.category} className="space-y-2">
<h4 className="font-medium text-foreground text-sm">

View File

@@ -4,15 +4,14 @@ import { useState, useEffect } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/app/_components/GlobalComponents/Cards/Card";
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
import {
FileText,
Plus,
Edit,
Trash2,
Copy,
Copy as CopyIcon,
CheckCircle,
Files,
} from "lucide-react";
FileTextIcon,
PlusIcon,
PencilSimpleIcon,
TrashIcon,
CopyIcon,
CheckCircleIcon,
FilesIcon,
} from "@phosphor-icons/react";
import { Script } from "@/app/_utils/scripts-utils";
import {
createScript,
@@ -207,8 +206,8 @@ 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">
<FileText className="h-5 w-5 text-primary" />
<div className="p-2 bg-background2 ascii-border">
<FileTextIcon className="h-5 w-5 text-primary" />
</div>
<div>
<CardTitle className="text-xl brand-gradient">
@@ -223,16 +222,16 @@ export const ScriptsManager = ({
onClick={() => setIsCreateModalOpen(true)}
className="btn-primary glow-primary"
>
<Plus className="h-4 w-4 mr-2" />
<PlusIcon className="h-4 w-4 mr-2" />
{t("scripts.newScript")}
</Button>
</div>
</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">
<FileText className="h-10 w-10 text-primary" />
<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">
<FileTextIcon className="h-10 w-10 text-primary" />
</div>
<h3 className="text-xl font-semibold mb-3 brand-gradient">
{t("scripts.noScriptsYet")}
@@ -245,7 +244,7 @@ export const ScriptsManager = ({
className="btn-primary glow-primary"
size="lg"
>
<Plus className="h-5 w-5 mr-2" />
<PlusIcon className="h-5 w-5 mr-2" />
{t("scripts.createYourFirstScript")}
</Button>
</div>
@@ -254,7 +253,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">
@@ -282,11 +281,11 @@ export const ScriptsManager = ({
size="sm"
onClick={() => handleCopy(script)}
className="btn-outline h-8 px-3"
title="Copy script content to clipboard"
aria-label="Copy script content to clipboard"
title="CopyIcon script content to clipboard"
aria-label="CopyIcon script content to clipboard"
>
{copiedId === script.id ? (
<CheckCircle className="h-3 w-3 text-green-500" />
<CheckCircleIcon className="h-3 w-3 text-status-success" />
) : (
<CopyIcon className="h-3 w-3" />
)}
@@ -302,7 +301,7 @@ export const ScriptsManager = ({
title="Clone script"
aria-label="Clone script"
>
<Files className="h-3 w-3" />
<FilesIcon className="h-3 w-3" />
</Button>
<Button
variant="outline"
@@ -320,10 +319,10 @@ export const ScriptsManager = ({
setIsEditModalOpen(true);
}}
className="btn-outline h-8 px-3"
title="Edit script"
aria-label="Edit script"
title="PencilSimpleIcon script"
aria-label="PencilSimpleIcon script"
>
<Edit className="h-3 w-3" />
<PencilSimpleIcon className="h-3 w-3" />
</Button>
<Button
variant="destructive"
@@ -336,7 +335,7 @@ export const ScriptsManager = ({
title="Delete script"
aria-label="Delete script"
>
<Trash2 className="h-3 w-3" />
<TrashIcon className="h-3 w-3" />
</Button>
</div>
</div>

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

@@ -1,6 +1,6 @@
import { cn } from "@/app/_utils/global-utils";
import { HTMLAttributes, forwardRef } from "react";
import { Zap } from "lucide-react";
import { LightningIcon } from "@phosphor-icons/react";
import { StatusBadge } from "@/app/_components/GlobalComponents/Badges/StatusBadge";
export interface PerformanceMetric {
@@ -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">
<LightningIcon className="h-4 w-4" />
<span className="text-sm font-medium">
Performance Summary
</span>
</div>

View File

@@ -4,7 +4,7 @@ import { MetricCard } from "@/app/_components/GlobalComponents/Cards/MetricCard"
import { SystemStatus } from "@/app/_components/FeatureComponents/System/SystemStatus";
import { PerformanceSummary } from "@/app/_components/FeatureComponents/System/PerformanceSummary";
import { Sidebar } from "@/app/_components/FeatureComponents/Layout/Sidebar";
import { Clock, HardDrive, Cpu, Monitor, Wifi } from "lucide-react";
import { ClockIcon, HardDriveIcon, CpuIcon, MonitorIcon, WifiHighIcon } from "@phosphor-icons/react";
interface SystemInfoType {
hostname: string;
@@ -170,53 +170,48 @@ export const SystemInfoCard = ({
const basicInfoItems = [
{
icon: Clock,
icon: ClockIcon,
label: t("sidebar.uptime"),
value: systemInfo.uptime,
color: "text-orange-500",
},
];
const performanceItems = [
{
icon: HardDrive,
icon: HardDriveIcon,
label: t("sidebar.memory"),
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,
},
{
icon: Cpu,
icon: CpuIcon,
label: t("sidebar.cpu"),
value: systemInfo.cpu.model,
detail: `${systemInfo.cpu.cores} cores`,
status: systemInfo.cpu.status,
color: "text-pink-500",
showProgress: true,
progressValue: systemInfo.cpu.usage,
},
{
icon: Monitor,
icon: MonitorIcon,
label: t("sidebar.gpu"),
value: systemInfo.gpu.model,
detail: systemInfo.gpu.memory
? `${systemInfo.gpu.memory} VRAM`
: systemInfo.gpu.status,
status: systemInfo.gpu.status,
color: "text-indigo-500",
},
...(systemInfo.network
? [
{
icon: Wifi,
icon: WifiHighIcon,
label: t("sidebar.network"),
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"
/>
))}
@@ -275,7 +269,7 @@ export const SystemInfoCard = ({
<h3 className="text-xs font-semibold text-foreground mb-2 uppercase tracking-wide">
{t("sidebar.performanceMetrics")}
</h3>
<div className="space-y-2">
<div className="space-y-4">
{performanceItems.map((item) => (
<MetricCard
key={item.label}
@@ -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

@@ -1,6 +1,6 @@
import { cn } from "@/app/_utils/global-utils";
import { HTMLAttributes, forwardRef } from "react";
import { Activity } from "lucide-react";
import { PulseIcon } from "@phosphor-icons/react";
import { useTranslations } from "next-intl";
export interface SystemStatusProps extends HTMLAttributes<HTMLDivElement> {
@@ -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,10 +61,10 @@ 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" />
<PulseIcon className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium text-foreground">
{t("system.systemStatus")}: {status}
</span>

View File

@@ -2,7 +2,7 @@
import { useState, useEffect } from "react";
import { useTranslations } from "next-intl";
import { AlertTriangle, X } from "lucide-react";
import { WarningIcon, XIcon } from "@phosphor-icons/react";
export const WrapperScriptWarning = () => {
const [isVisible, setIsVisible] = useState(false);
@@ -46,7 +46,7 @@ export const WrapperScriptWarning = () => {
<div className="bg-amber-500/10 border border-amber-500/20 rounded-lg p-4 mb-4">
<div className="flex items-start justify-between">
<div className="flex items-start space-x-3">
<AlertTriangle className="w-5 h-5 text-amber-500 mt-0.5 flex-shrink-0" />
<WarningIcon className="w-5 h-5 text-amber-500 mt-0.5 flex-shrink-0" />
<div className="flex-1">
<h3 className="text-sm font-medium text-amber-800 dark:text-amber-400">
{t("warnings.wrapperScriptModified")}
@@ -61,7 +61,7 @@ export const WrapperScriptWarning = () => {
className="text-amber-600 dark:text-amber-400 hover:text-amber-800 dark:hover:text-amber-300 transition-colors ml-4"
aria-label="Dismiss warning"
>
<X className="w-4 h-4" />
<XIcon className="w-4 h-4" />
</button>
</div>
</div>

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

@@ -2,7 +2,7 @@
import { useState, useEffect } from "react";
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
import { ChevronDown, User, X } from "lucide-react";
import { CaretDownIcon, UserIcon, XIcon } from "@phosphor-icons/react";
import { fetchAvailableUsers } from "@/app/_server/actions/cronjobs";
import { useTranslations } from "next-intl";
@@ -42,7 +42,7 @@ export const UserFilter = ({
<div
className={`flex items-center gap-2 px-3 py-2 bg-muted/50 rounded-md ${className}`}
>
<User className="h-4 w-4 text-muted-foreground" />
<UserIcon className="h-4 w-4 text-muted-foreground" />
<span className="text-sm text-muted-foreground">Loading users...</span>
</div>
);
@@ -57,14 +57,14 @@ export const UserFilter = ({
className="flex-1 justify-between"
>
<div className="flex items-center gap-2">
<User className="h-4 w-4" />
<UserIcon className="h-4 w-4" />
<span className="text-sm">
{selectedUser
? `${t("common.userWithUsername", { user: selectedUser })}`
: t("common.allUsers")}
</span>
</div>
<ChevronDown className="h-4 w-4" />
<CaretDownIcon className="h-4 w-4" />
</Button>
{selectedUser && (
<Button
@@ -73,21 +73,20 @@ export const UserFilter = ({
onClick={() => onUserChange(null)}
className="p-2 h-8 w-8 flex-shrink-0"
>
<X className="h-3 w-3" />
<XIcon className="h-3 w-3" />
</Button>
)}
</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 tui-scrollbar">
<button
onClick={() => {
onUserChange(null);
setIsOpen(false);
}}
className={`w-full text-left px-3 py-2 text-sm hover:bg-accent transition-colors ${
!selectedUser ? "bg-accent text-accent-foreground" : ""
}`}
className={`w-full text-left px-3 py-2 text-sm hover: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

@@ -2,7 +2,7 @@
import { useState, useEffect } from "react";
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
import { ChevronDown, User } from "lucide-react";
import { CaretDownIcon, UserIcon } from "@phosphor-icons/react";
import { fetchAvailableUsers } from "@/app/_server/actions/cronjobs";
interface UserSwitcherProps {
@@ -43,7 +43,7 @@ export const UserSwitcher = ({
<div
className={`flex items-center gap-2 px-3 py-2 bg-muted/50 rounded-md ${className}`}
>
<User className="h-4 w-4 text-muted-foreground" />
<UserIcon className="h-4 w-4 text-muted-foreground" />
<span className="text-sm text-muted-foreground">Loading users...</span>
</div>
);
@@ -62,14 +62,14 @@ export const UserSwitcher = ({
className="w-full justify-between"
>
<div className="flex items-center gap-2">
<User className="h-4 w-4" />
<UserIcon className="h-4 w-4" />
<span className="text-sm">{selectedUser || "Select user"}</span>
</div>
<ChevronDown className="h-4 w-4" />
<CaretDownIcon className="h-4 w-4" />
</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 tui-scrollbar">
{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

@@ -1,6 +1,6 @@
"use client";
import { AlertCircle, X } from "lucide-react";
import { WarningCircleIcon, XIcon } from "@phosphor-icons/react";
import { JobError, removeJobError } from "@/app/_utils/error-utils";
interface ErrorBadgeProps {
@@ -30,7 +30,7 @@ export const ErrorBadge = ({
className="flex items-center gap-1 px-2 py-1 bg-destructive/10 text-destructive border border-destructive/20 rounded text-xs hover:bg-destructive/20 transition-colors"
title={error.message}
>
<AlertCircle className="h-3 w-3" />
<WarningCircleIcon className="h-3 w-3" />
<span className="hidden sm:inline">Error</span>
</button>
<button
@@ -38,7 +38,7 @@ export const ErrorBadge = ({
className="p-1 text-destructive hover:bg-destructive/10 rounded transition-colors"
title="Dismiss error"
>
<X className="h-3 w-3" />
<XIcon className="h-3 w-3" />
</button>
</div>
))}

View File

@@ -1,6 +1,6 @@
import { cn } from "@/app/_utils/global-utils";
import { HTMLAttributes, forwardRef } from "react";
import { CheckCircle, AlertTriangle, XCircle, Activity } from "lucide-react";
import { CheckCircleIcon, WarningIcon, XCircleIcon, PulseIcon } from "@phosphor-icons/react";
import { useTranslations } from "next-intl";
export interface StatusBadgeProps extends HTMLAttributes<HTMLDivElement> {
@@ -31,46 +31,41 @@ 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",
icon: CheckCircle,
color: "text-status-success",
bgColor: "bg-background0",
icon: CheckCircleIcon,
label: t("system.optimal"),
};
case "moderate":
case "warning":
return {
color: "text-yellow-500",
bgColor: "bg-yellow-500/10",
borderColor: "border-yellow-500/20",
icon: AlertTriangle,
color: "text-status-warning",
bgColor: "bg-background0",
icon: WarningIcon,
label: t("system.warning"),
};
case "high":
case "slow":
return {
color: "text-orange-500",
bgColor: "bg-orange-500/10",
borderColor: "border-orange-500/20",
icon: AlertTriangle,
color: "text-status-warning",
bgColor: "bg-background0",
icon: WarningIcon,
label: t("system.high"),
};
case "critical":
case "poor":
case "offline":
return {
color: "text-destructive",
bgColor: "bg-destructive/10",
borderColor: "border-destructive/20",
icon: XCircle,
color: "text-status-error",
bgColor: "bg-background0",
icon: XCircleIcon,
label: t("system.critical"),
};
default:
return {
color: "text-muted-foreground",
bgColor: "bg-muted",
borderColor: "border-border",
icon: Activity,
color: "",
bgColor: "bg-background0",
icon: PulseIcon,
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

@@ -1,12 +1,12 @@
import { cn } from "@/app/_utils/global-utils";
import { HTMLAttributes, forwardRef } from "react";
import { LucideIcon } from "lucide-react";
import { HTMLAttributes, forwardRef, ComponentType } from "react";
import { IconProps } from "@phosphor-icons/react";
import { StatusBadge } from "@/app/_components/GlobalComponents/Badges/StatusBadge";
import { ProgressBar } from "@/app/_components/GlobalComponents/UIElements/ProgressBar";
import { TruncatedText } from "@/app/_components/GlobalComponents/UIElements/TruncatedText";
export interface MetricCardProps extends HTMLAttributes<HTMLDivElement> {
icon: LucideIcon;
icon: ComponentType<IconProps>;
label: string;
value: string;
detail?: string;
@@ -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

@@ -0,0 +1,46 @@
"use client";
import { AsteriskIcon, TerminalIcon } from "@phosphor-icons/react";
interface LogoProps {
size?: number;
showGlow?: boolean;
}
export const Logo = ({ size = 48, showGlow = false }: LogoProps) => {
const iconSize = size * 0.8;
const asteriskSize = size * 0.4;
const asteriskOffset = size * 0.08;
return (
<div
className="relative flex items-center justify-center flex-shrink-0"
style={{ width: size, height: size }}
>
{showGlow && (
<div
className="absolute inset-0 bg-gradient-to-br from-primary/20 via-primary/10 to-transparent blur-xl"
style={{ width: size, height: size }}
/>
)}
<div className="relative z-10 flex items-center justify-center w-full h-full">
<TerminalIcon
className="text-primary drop-shadow-[0_0_10px_rgba(var(--primary-rgb),0.4)]"
weight="duotone"
style={{ width: iconSize, height: iconSize }}
/>
<AsteriskIcon
className="text-primary absolute drop-shadow-[0_0_8px_rgba(var(--primary-rgb),0.6)]"
weight="bold"
style={{
width: asteriskSize,
height: asteriskSize,
top: -asteriskOffset,
right: -asteriskOffset
}}
/>
</div>
</div>
);
};

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

@@ -2,7 +2,7 @@
import { useState, useRef, useEffect, ReactNode } from "react";
import { Button } from "@/app/_components/GlobalComponents/UIElements/Button";
import { MoreVertical } from "lucide-react";
import { DotsThreeVerticalIcon } from "@phosphor-icons/react";
const DROPDOWN_HEIGHT = 200;
@@ -25,7 +25,7 @@ interface DropdownMenuProps {
export const DropdownMenu = ({
items,
triggerLabel,
triggerIcon = <MoreVertical className="h-3 w-3" />,
triggerIcon = <DotsThreeVerticalIcon className="h-3 w-3" />,
triggerClassName = "btn-outline h-8 px-3",
onOpenChange,
}: DropdownMenuProps) => {
@@ -98,9 +98,8 @@ 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 ${
positionAbove ? "bottom-full mb-2" : "top-full mt-2"
}`}
className={`absolute right-0 w-56 ascii-border bg-background0 shadow-lg z-[9999] overflow-hidden terminal-font ${positionAbove ? "bottom-full mb-2" : "top-full mt-2"
}`}
>
<div className="py-1">
{items.map((item, index) => (
@@ -108,13 +107,12 @@ export const DropdownMenu = ({
key={index}
onClick={() => handleItemClick(item)}
disabled={item.disabled}
className={`w-full flex items-center gap-3 px-4 py-2 text-sm transition-colors ${
item.disabled
? "opacity-50 cursor-not-allowed"
: item.variant === "destructive"
? "text-destructive hover:bg-destructive/10"
: "text-foreground hover:bg-accent"
}`}
className={`w-full flex items-center gap-3 px-4 py-2 text-sm transition-colors ${item.disabled
? "opacity-50 cursor-not-allowed"
: item.variant === "destructive"
? "text-status-error hover:bg-background1"
: "hover:bg-background1"
}`}
>
{item.icon && (
<span className="flex-shrink-0">{item.icon}</span>

View File

@@ -1,9 +1,8 @@
"use client";
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 { XIcon } from "@phosphor-icons/react";
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}>
<XIcon 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

@@ -1,7 +1,7 @@
"use client";
import { useEffect, useState } from "react";
import { X, CheckCircle, AlertCircle, Info, AlertTriangle } from "lucide-react";
import { XIcon, CheckCircleIcon, WarningCircleIcon, InfoIcon, WarningIcon } from "@phosphor-icons/react";
import { cn } from "@/app/_utils/global-utils";
import { ErrorDetailsModal } from "@/app/_components/FeatureComponents/Modals/ErrorDetailsModal";
@@ -30,19 +30,17 @@ interface ToastProps {
}
const toastIcons = {
success: CheckCircle,
error: AlertCircle,
info: Info,
warning: AlertTriangle,
success: CheckCircleIcon,
error: WarningCircleIcon,
info: InfoIcon,
warning: WarningIcon,
};
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"
)}
@@ -92,7 +90,7 @@ export const Toast = ({ toast, onRemove, onErrorClick }: ToastProps) => {
}}
className="flex-shrink-0 p-1 rounded-md hover:bg-black/10 transition-colors"
>
<X className="h-4 w-4" />
<XIcon className="h-4 w-4" />
</button>
</div>
);

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,209 @@
/* 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;
}
/* Force scrollbars to always be visible */
::-webkit-scrollbar {
-webkit-appearance: none;
}
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: auto !important;
}
.tui-scrollbar::-webkit-scrollbar {
-webkit-appearance: none;
width: 10px;
height: 10px;
background-color: var(--background1) !important;
}
.tui-scrollbar::-webkit-scrollbar-thumb {
background-color: var(--background0) !important;
border: 1px solid var(--box-border-color, var(--foreground2));
visibility: visible !important;
opacity: 1 !important;
min-height: 40px !important;
height: 40px !important;
}
.tui-scrollbar::-webkit-scrollbar-thumb:hover {
background-color: var(--primary) !important;
box-shadow: 0 0 8px var(--primary);
}
.tui-scrollbar::-webkit-scrollbar-track {
background: var(--background1);
border-left: 1px solid var(--box-border-color, var(--foreground2));
}
.tui-scrollbar::-webkit-scrollbar-corner {
background: var(--background1);
}
.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

@@ -1,12 +1,24 @@
import { Asterisk, Terminal } from "lucide-react";
"use client";
export default async function Logo() {
import { AsteriskIcon, TerminalIcon } from "@phosphor-icons/react";
export default function Logo() {
return (
<div className="m-auto mt-20 relative w-[600px] h-[600px]">
<div className="p-3 bg-gradient-to-br from-purple-500 via-pink-500 to-orange-500 rounded-[200px] w-full h-full">
<div className="relative">
<Terminal className="h-[350px] w-[350px] text-white relative top-[120px] left-[120px]" />
<Asterisk className="h-[200px] w-[200px] text-white absolute top-14 right-[90px]" />
<div className="min-h-screen bg-background0 flex items-center justify-center p-8">
<div className="relative">
<div className="w-[600px] h-[600px] ascii-border bg-background1 p-16 flex items-center justify-center">
<div className="absolute w-[600px] h-[600px] bg-gradient-to-br from-primary/20 via-primary/10 to-transparent blur-3xl" />
<div className="relative z-10 flex items-center justify-center w-full h-full">
<TerminalIcon
className="h-80 w-80 text-primary drop-shadow-[0_0_40px_rgba(var(--primary-rgb),0.6)]"
weight="duotone"
/>
<AsteriskIcon
className="h-40 w-40 text-primary absolute -top-4 -right-4 drop-shadow-[0_0_30px_rgba(var(--primary-rgb),0.8)]"
weight="bold"
/>
</div>
</div>
</div>
</div>

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 { Logo } from "@/app/_components/GlobalComponents/Logo/Logo";
export const dynamic = "force-dynamic";
export const maxDuration = 300;
@@ -59,52 +60,48 @@ 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">
<Logo size={48} showGlow={true} />
<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">
{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

@@ -19,10 +19,13 @@
"@codemirror/state": "^6.5.2",
"@codemirror/theme-one-dark": "^6.1.3",
"@codemirror/view": "^6.38.1",
"@phosphor-icons/react": "^2.1.10",
"@types/node": "^20",
"@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",
@@ -30,7 +33,6 @@
"cron-parser": "^5.3.0",
"cronstrue": "^3.2.0",
"jose": "^6.1.1",
"lucide-react": "^0.294.0",
"minimatch": "^10.0.3",
"next": "14.2.35",
"next-intl": "^4.4.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: {},
},

BIN
public/logo-legacy.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 34 KiB

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

@@ -1289,6 +1289,11 @@
"@parcel/watcher-win32-ia32" "2.5.1"
"@parcel/watcher-win32-x64" "2.5.1"
"@phosphor-icons/react@^2.1.10":
version "2.1.10"
resolved "https://registry.yarnpkg.com/@phosphor-icons/react/-/react-2.1.10.tgz#3a97ec5b7a4b8d53afeb29125bc17e74ed2daf92"
integrity sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA==
"@rollup/plugin-babel@^5.2.0":
version "5.3.1"
resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283"
@@ -1700,6 +1705,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"
@@ -3714,11 +3729,6 @@ lru-cache@^5.1.1:
dependencies:
yallist "^3.0.2"
lucide-react@^0.294.0:
version "0.294.0"
resolved "https://registry.yarnpkg.com/lucide-react/-/lucide-react-0.294.0.tgz#dc406e1e7e2f722cf93218fe5b31cf3c95778817"
integrity sha512-V7o0/VECSGbLHn3/1O67FUgBwWB+hmzshrgDVRJQhMh8uj5D3HBuIvhuAmQTtlupILSplwIZg5FTc4tTKMA2SA==
luxon@^3.7.1:
version "3.7.2"
resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.7.2.tgz#d697e48f478553cca187a0f8436aff468e3ba0ba"
@@ -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"