From 7e3bd590e94d5ead28fe2c498864cc50408e829a Mon Sep 17 00:00:00 2001 From: fccview Date: Wed, 5 Nov 2025 18:10:35 +0000 Subject: [PATCH 01/21] update develop and improve pipelines --- .github/workflows/docker-build.yml | 52 ++++++++++++ .github/workflows/docker-publish.yml | 121 ++++++++++++++++++++++----- 2 files changed, 154 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/docker-build.yml diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml new file mode 100644 index 0000000..bef6c5a --- /dev/null +++ b/.github/workflows/docker-build.yml @@ -0,0 +1,52 @@ +name: Reusable Docker Build Logic + +on: + workflow_call: + inputs: + platform: { required: true, type: string } + suffix: { required: true, type: string } + runner: { required: true, type: string } + secrets: + token: { required: true } + +jobs: + build: + runs-on: ${{ inputs.runner }} + permissions: { contents: read, packages: write } + steps: + - { name: Checkout repository, uses: actions/checkout@v4 } + + - name: "Prepare repository name in lowercase" + id: repo + run: echo "name=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT + + - { name: Set up QEMU, uses: docker/setup-qemu-action@v3 } + - { name: Set up Docker Buildx, uses: docker/setup-buildx-action@v3 } + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.token }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ghcr.io/${{ steps.repo.outputs.name }} + tags: | + type=ref,event=branch,suffix=${{ inputs.suffix }} + type=ref,event=tag,suffix=${{ inputs.suffix }} + type=raw,value=latest,suffix=${{ inputs.suffix }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + platforms: ${{ inputs.platform }} + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max \ No newline at end of file diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index 0d1d491..0d3b2d2 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -1,27 +1,69 @@ -name: Docker +name: Build and Publish Multi-Platform Docker Image on: push: - branches: ["main", "legacy", "feature/*"] + branches: ["main", "develop"] tags: ["*"] - pull_request: - branches: ["main"] - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} jobs: - build: + build-amd64: runs-on: ubuntu-latest permissions: contents: read packages: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Prepare repository name in lowercase + id: repo + run: echo "name=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ghcr.io/${{ steps.repo.outputs.name }} + tags: | + type=ref,event=branch,suffix=-amd64 + type=ref,event=tag,suffix=-amd64 + type=raw,value=latest,suffix=-amd64,enable=${{ startsWith(github.ref, 'refs/tags/') }} + + - name: Build and push AMD64 Docker image + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/amd64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + build-arm64: + runs-on: ubuntu-22.04-arm + permissions: + contents: read + packages: write steps: - name: Checkout repository uses: actions/checkout@v4 + - name: Prepare repository name in lowercase + id: repo + run: echo "name=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT + - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -31,27 +73,68 @@ jobs: - name: Log in to the Container registry uses: docker/login-action@v3 with: - registry: ${{ env.REGISTRY }} + registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract metadata (tags, labels) for Docker + - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + images: | + ghcr.io/${{ steps.repo.outputs.name }} tags: | - type=ref,event=branch - type=ref,event=pr - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=sha + type=ref,event=branch,suffix=-arm64 + type=ref,event=tag,suffix=-arm64 + type=raw,value=latest,suffix=-arm64,enable=${{ startsWith(github.ref, 'refs/tags/') }} - - name: Build and push Docker image + - name: Build and push ARM64 Docker image uses: docker/build-push-action@v5 with: context: . - platforms: linux/amd64,linux/arm64 + platforms: linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + manifest: + needs: [build-amd64, build-arm64] + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Prepare repository name in lowercase + id: repo + run: echo "name=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for final manifest + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ghcr.io/${{ steps.repo.outputs.name }} + tags: | + type=ref,event=branch + type=ref,event=tag + type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/') }} + + - name: Create and push manifest list + run: | + echo "${{ steps.meta.outputs.tags }}" | while read -r tag; do + if [ -z "$tag" ]; then continue; fi + echo "Creating manifest for ${tag}" + docker buildx imagetools create --tag "${tag}" \ + "${tag}-amd64" \ + "${tag}-arm64" + done \ No newline at end of file From 14fba08cb29cd1bfe10e43fdd8ba7598af929cad Mon Sep 17 00:00:00 2001 From: fccview Date: Wed, 5 Nov 2025 18:31:47 +0000 Subject: [PATCH 02/21] start cleaning up and refactoring --- .../Cronjobs/CronJobList.tsx | 32 ++++++------------- .../Cronjobs/helpers/index.tsx | 2 +- .../Layout}/Sidebar.tsx | 0 .../Layout}/TabbedInterface.tsx | 22 ++++++------- .../LoginForm/LoginForm.tsx | 6 ++-- .../LoginForm}/LogoutButton.tsx | 2 +- .../Modals}/CloneScriptModal.tsx | 6 ++-- .../Modals}/CloneTaskModal.tsx | 6 ++-- .../Modals}/CreateScriptModal.tsx | 0 .../Modals}/CreateTaskModal.tsx | 18 +++++------ .../Modals}/DeleteScriptModal.tsx | 4 +-- .../Modals}/DeleteTaskModal.tsx | 4 +-- .../Modals}/EditScriptModal.tsx | 0 .../Modals}/EditTaskModal.tsx | 8 ++--- .../Modals}/ErrorDetailsModal.tsx | 6 ++-- .../Modals}/ScriptModal.tsx | 12 +++---- .../Modals}/SelectScriptModal.tsx | 10 +++--- .../PWA}/PWAInstallPrompt.tsx | 0 .../PWA}/ServiceWorkerRegister.tsx | 2 +- .../Scripts}/BashEditor.tsx | 2 +- .../Scripts}/BashSnippetHelper.tsx | 6 ++-- .../Scripts}/CronExpressionHelper.tsx | 6 ++-- .../Scripts}/ScriptsManager.tsx | 14 ++++---- .../System}/PerformanceSummary.tsx | 2 +- .../System}/SystemInfo.tsx | 8 ++--- .../System}/SystemStatus.tsx | 0 .../Theme}/ThemeToggle.tsx | 2 +- .../User}/UserFilter.tsx | 0 .../User}/UserSwitcher.tsx | 0 .../Badges}/ErrorBadge.tsx | 0 .../Badges}/StatusBadge.tsx | 0 .../{ui => GlobalComponents/Cards}/Card.tsx | 0 .../Cards}/MetricCard.tsx | 6 ++-- .../FormElements}/Input.tsx | 0 .../UIElements}/Button.tsx | 0 .../UIElements}/Modal.tsx | 2 +- .../UIElements}/ProgressBar.tsx | 0 .../UIElements}/Toast.tsx | 7 ++-- .../UIElements}/TruncatedText.tsx | 0 .../ui => _providers}/ThemeProvider.tsx | 0 app/layout.tsx | 4 +-- app/login/page.tsx | 2 +- app/page.tsx | 16 +++++----- 43 files changed, 101 insertions(+), 116 deletions(-) rename app/_components/{features => FeatureComponents}/Cronjobs/CronJobList.tsx (94%) rename app/_components/{features => FeatureComponents}/Cronjobs/helpers/index.tsx (99%) rename app/_components/{ui => FeatureComponents/Layout}/Sidebar.tsx (100%) rename app/_components/{ => FeatureComponents/Layout}/TabbedInterface.tsx (74%) rename app/_components/{features => FeatureComponents}/LoginForm/LoginForm.tsx (93%) rename app/_components/{ui => FeatureComponents/LoginForm}/LogoutButton.tsx (92%) rename app/_components/{modals => FeatureComponents/Modals}/CloneScriptModal.tsx (91%) rename app/_components/{modals => FeatureComponents/Modals}/CloneTaskModal.tsx (91%) rename app/_components/{modals => FeatureComponents/Modals}/CreateScriptModal.tsx (100%) rename app/_components/{modals => FeatureComponents/Modals}/CreateTaskModal.tsx (92%) rename app/_components/{modals => FeatureComponents/Modals}/DeleteScriptModal.tsx (95%) rename app/_components/{modals => FeatureComponents/Modals}/DeleteTaskModal.tsx (95%) rename app/_components/{modals => FeatureComponents/Modals}/EditScriptModal.tsx (100%) rename app/_components/{modals => FeatureComponents/Modals}/EditTaskModal.tsx (90%) rename app/_components/{modals => FeatureComponents/Modals}/ErrorDetailsModal.tsx (95%) rename app/_components/{modals => FeatureComponents/Modals}/ScriptModal.tsx (90%) rename app/_components/{modals => FeatureComponents/Modals}/SelectScriptModal.tsx (96%) rename app/_components/{ui => FeatureComponents/PWA}/PWAInstallPrompt.tsx (100%) rename app/_components/{ui => FeatureComponents/PWA}/ServiceWorkerRegister.tsx (96%) rename app/_components/{ => FeatureComponents/Scripts}/BashEditor.tsx (98%) rename app/_components/{ => FeatureComponents/Scripts}/BashSnippetHelper.tsx (97%) rename app/_components/{ => FeatureComponents/Scripts}/CronExpressionHelper.tsx (97%) rename app/_components/{ => FeatureComponents/Scripts}/ScriptsManager.tsx (94%) rename app/_components/{ui => FeatureComponents/System}/PerformanceSummary.tsx (95%) rename app/_components/{ => FeatureComponents/System}/SystemInfo.tsx (94%) rename app/_components/{ui => FeatureComponents/System}/SystemStatus.tsx (100%) rename app/_components/{ui => FeatureComponents/Theme}/ThemeToggle.tsx (92%) rename app/_components/{ui => FeatureComponents/User}/UserFilter.tsx (100%) rename app/_components/{ui => FeatureComponents/User}/UserSwitcher.tsx (100%) rename app/_components/{ui => GlobalComponents/Badges}/ErrorBadge.tsx (100%) rename app/_components/{ui => GlobalComponents/Badges}/StatusBadge.tsx (100%) rename app/_components/{ui => GlobalComponents/Cards}/Card.tsx (100%) rename app/_components/{ui => GlobalComponents/Cards}/MetricCard.tsx (90%) rename app/_components/{ui => GlobalComponents/FormElements}/Input.tsx (100%) rename app/_components/{ui => GlobalComponents/UIElements}/Button.tsx (100%) rename app/_components/{ui => GlobalComponents/UIElements}/Modal.tsx (97%) rename app/_components/{ui => GlobalComponents/UIElements}/ProgressBar.tsx (100%) rename app/_components/{ui => GlobalComponents/UIElements}/Toast.tsx (95%) rename app/_components/{ui => GlobalComponents/UIElements}/TruncatedText.tsx (100%) rename app/{_components/ui => _providers}/ThemeProvider.tsx (100%) diff --git a/app/_components/features/Cronjobs/CronJobList.tsx b/app/_components/FeatureComponents/Cronjobs/CronJobList.tsx similarity index 94% rename from app/_components/features/Cronjobs/CronJobList.tsx rename to app/_components/FeatureComponents/Cronjobs/CronJobList.tsx index 2508509..8e3d134 100644 --- a/app/_components/features/Cronjobs/CronJobList.tsx +++ b/app/_components/FeatureComponents/Cronjobs/CronJobList.tsx @@ -1,7 +1,7 @@ "use client"; -import { Card, CardContent, CardHeader, CardTitle } from "../../ui/Card"; -import { Button } from "../../ui/Button"; +import { Card, CardContent, CardHeader, CardTitle } from "@/app/_components/GlobalComponents/Cards/Card"; +import { Button } from "@/app/_components/GlobalComponents/UIElements/Button"; import { Trash2, Clock, @@ -14,33 +14,21 @@ import { Code, } from "lucide-react"; import { CronJob } from "@/app/_utils/system"; -import { - removeCronJob, - editCronJob, - createCronJob, - cloneCronJob, - pauseCronJobAction, - resumeCronJobAction, - runCronJob, -} from "@/app/_server/actions/cronjobs"; import { useState, useMemo, useEffect } from "react"; -import { CreateTaskModal } from "../../modals/CreateTaskModal"; -import { EditTaskModal } from "../../modals/EditTaskModal"; -import { DeleteTaskModal } from "../../modals/DeleteTaskModal"; -import { CloneTaskModal } from "../../modals/CloneTaskModal"; -import { UserFilter } from "../../ui/UserFilter"; -import { ErrorBadge } from "../../ui/ErrorBadge"; -import { ErrorDetailsModal } from "../../modals/ErrorDetailsModal"; +import { CreateTaskModal } from "@/app/_components/FeatureComponents/Modals/CreateTaskModal"; +import { EditTaskModal } from "@/app/_components/FeatureComponents/Modals/EditTaskModal"; +import { DeleteTaskModal } from "@/app/_components/FeatureComponents/Modals/DeleteTaskModal"; +import { CloneTaskModal } from "@/app/_components/FeatureComponents/Modals/CloneTaskModal"; +import { UserFilter } from "@/app/_components/FeatureComponents/User/UserFilter"; +import { ErrorBadge } from "@/app/_components/GlobalComponents/Badges/ErrorBadge"; +import { ErrorDetailsModal } from "@/app/_components/FeatureComponents/Modals/ErrorDetailsModal"; import { type Script } from "@/app/_server/actions/scripts"; -import { showToast } from "../../ui/Toast"; import { getJobErrorsByJobId, - setJobError, JobError, } from "@/app/_utils/errorState"; import { handleErrorClick, - refreshJobErrors, handleDelete, handleClone, handlePause, @@ -48,7 +36,7 @@ import { handleRun, handleEditSubmit, handleNewCronSubmit, -} from "./helpers"; +} from "@/app/_components/FeatureComponents/Cronjobs/helpers"; interface CronJobListProps { cronJobs: CronJob[]; diff --git a/app/_components/features/Cronjobs/helpers/index.tsx b/app/_components/FeatureComponents/Cronjobs/helpers/index.tsx similarity index 99% rename from app/_components/features/Cronjobs/helpers/index.tsx rename to app/_components/FeatureComponents/Cronjobs/helpers/index.tsx index 7460550..84e58ac 100644 --- a/app/_components/features/Cronjobs/helpers/index.tsx +++ b/app/_components/FeatureComponents/Cronjobs/helpers/index.tsx @@ -1,5 +1,5 @@ import { JobError, setJobError } from "@/app/_utils/errorState"; -import { showToast } from "@/app/_components/ui/Toast"; +import { showToast } from "@/app/_components/GlobalComponents/UIElements/Toast"; import { removeCronJob, editCronJob, diff --git a/app/_components/ui/Sidebar.tsx b/app/_components/FeatureComponents/Layout/Sidebar.tsx similarity index 100% rename from app/_components/ui/Sidebar.tsx rename to app/_components/FeatureComponents/Layout/Sidebar.tsx diff --git a/app/_components/TabbedInterface.tsx b/app/_components/FeatureComponents/Layout/TabbedInterface.tsx similarity index 74% rename from app/_components/TabbedInterface.tsx rename to app/_components/FeatureComponents/Layout/TabbedInterface.tsx index 44db262..b9355b4 100644 --- a/app/_components/TabbedInterface.tsx +++ b/app/_components/FeatureComponents/Layout/TabbedInterface.tsx @@ -1,8 +1,8 @@ "use client"; import { useState } from "react"; -import { CronJobList } from "./features/Cronjobs/CronJobList"; -import { ScriptsManager } from "./ScriptsManager"; +import { CronJobList } from "@/app/_components/FeatureComponents/Cronjobs/CronJobList"; +import { ScriptsManager } from "@/app/_components/FeatureComponents/Scripts/ScriptsManager"; import { CronJob } from "@/app/_utils/system"; import { type Script } from "@/app/_server/actions/scripts"; import { Clock, FileText } from "lucide-react"; @@ -26,11 +26,10 @@ export const TabbedInterface = ({
@@ -285,219 +99,74 @@ export const CronJobList = ({ cronJobs, scripts }: CronJobListProps) => { {filteredJobs.length === 0 ? ( -
-
- -
-

- {selectedUser - ? `No tasks for user ${selectedUser}` - : "No scheduled tasks yet"} -

-

- {selectedUser - ? `No scheduled tasks found for user ${selectedUser}. Try selecting a different user or create a new task.` - : "Create your first scheduled task to automate your system operations and boost productivity."} -

- -
+ setIsNewCronModalOpen(true)} + /> ) : (
{filteredJobs.map((job) => ( -
-
-
-
- - {job.schedule} - -
-
-                            {job.command}
-                          
-
-
- -
-
- - {job.user} -
- {job.paused && ( - - Paused - - )} - -
- - {job.comment && ( -

- {job.comment} -

- )} -
- -
- - - - {job.paused ? ( - - ) : ( - - )} - -
-
-
+ job={job} + errors={jobErrors[job.id] || []} + runningJobId={runningJobId} + deletingId={deletingId} + onRun={handleRunLocal} + onEdit={handleEdit} + onClone={confirmClone} + onResume={handleResumeLocal} + onPause={handlePauseLocal} + onDelete={confirmDelete} + onErrorClick={handleErrorClickLocal} + onErrorDismiss={refreshJobErrorsLocal} + /> ))}
)} - setIsNewCronModalOpen(false)} - onSubmit={handleNewCronSubmitLocal} + + + isNewCronModalOpen={isNewCronModalOpen} + onNewCronModalClose={() => setIsNewCronModalOpen(false)} + onNewCronSubmit={handleNewCronSubmitLocal} + newCronForm={newCronForm} + onNewCronFormChange={(updates) => setNewCronForm((prev) => ({ ...prev, ...updates })) } - /> - setIsEditModalOpen(false)} - onSubmit={handleEditSubmitLocal} - form={editForm} - onFormChange={(updates) => + isEditModalOpen={isEditModalOpen} + onEditModalClose={() => setIsEditModalOpen(false)} + onEditSubmit={handleEditSubmitLocal} + editForm={editForm} + onEditFormChange={(updates) => setEditForm((prev) => ({ ...prev, ...updates })) } - /> - setIsDeleteModalOpen(false)} - onConfirm={() => + isDeleteModalOpen={isDeleteModalOpen} + onDeleteModalClose={() => setIsDeleteModalOpen(false)} + onDeleteConfirm={() => jobToDelete ? handleDeleteLocal(jobToDelete.id) : undefined } - job={jobToDelete} - /> + jobToDelete={jobToDelete} - setIsCloneModalOpen(false)} - onConfirm={handleCloneLocal} + isCloneModalOpen={isCloneModalOpen} + onCloneModalClose={() => setIsCloneModalOpen(false)} + onCloneConfirm={handleCloneLocal} + jobToClone={jobToClone} isCloning={isCloning} - /> - {errorModalOpen && selectedError && ( - { - setErrorModalOpen(false); - setSelectedError(null); - }} - error={{ - title: selectedError.title, - message: selectedError.message, - details: selectedError.details, - command: selectedError.command, - output: selectedError.output, - stderr: selectedError.stderr, - timestamp: selectedError.timestamp, - jobId: selectedError.jobId, - }} - /> - )} + isErrorModalOpen={errorModalOpen} + onErrorModalClose={() => { + setErrorModalOpen(false); + setSelectedError(null); + }} + selectedError={selectedError} + /> ); -}; +}; \ No newline at end of file diff --git a/app/_components/FeatureComponents/Cronjobs/Parts/CronJobEmptyState.tsx b/app/_components/FeatureComponents/Cronjobs/Parts/CronJobEmptyState.tsx new file mode 100644 index 0000000..5c9b9fc --- /dev/null +++ b/app/_components/FeatureComponents/Cronjobs/Parts/CronJobEmptyState.tsx @@ -0,0 +1,40 @@ +"use client"; + +import { Button } from "@/app/_components/GlobalComponents/UIElements/Button"; +import { Clock, Plus } from "lucide-react"; + +interface CronJobEmptyStateProps { + selectedUser: string | null; + onNewTaskClick: () => void; +} + +export const CronJobEmptyState = ({ + selectedUser, + onNewTaskClick, +}: CronJobEmptyStateProps) => { + return ( +
+
+ +
+

+ {selectedUser + ? `No tasks for user ${selectedUser}` + : "No scheduled tasks yet"} +

+

+ {selectedUser + ? `No scheduled tasks found for user ${selectedUser}. Try selecting a different user or create a new task.` + : "Create your first scheduled task to automate your system operations and boost productivity."} +

+ +
+ ); +}; \ No newline at end of file diff --git a/app/_components/FeatureComponents/Cronjobs/Parts/CronJobItem.tsx b/app/_components/FeatureComponents/Cronjobs/Parts/CronJobItem.tsx new file mode 100644 index 0000000..430be9a --- /dev/null +++ b/app/_components/FeatureComponents/Cronjobs/Parts/CronJobItem.tsx @@ -0,0 +1,198 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { Button } from "@/app/_components/GlobalComponents/UIElements/Button"; +import { + Trash2, + Edit, + Files, + User, + Play, + Pause, + Code, + Info, +} from "lucide-react"; +import { CronJob } from "@/app/_utils/cronjob-utils"; +import { JobError } from "@/app/_utils/error-utils"; +import { ErrorBadge } from "@/app/_components/GlobalComponents/Badges/ErrorBadge"; +import { parseCronExpression, type CronExplanation } from "@/app/_utils/parser-utils"; +import { useLocale } from "next-intl"; +import { useTranslations } from "next-intl"; + +interface CronJobItemProps { + job: CronJob; + errors: JobError[]; + runningJobId: string | null; + deletingId: string | null; + onRun: (id: string) => void; + onEdit: (job: CronJob) => void; + onClone: (job: CronJob) => void; + onResume: (id: string) => void; + onPause: (id: string) => void; + onDelete: (job: CronJob) => void; + onErrorClick: (error: JobError) => void; + onErrorDismiss: () => void; +} + +export const CronJobItem = ({ + job, + errors, + runningJobId, + deletingId, + onRun, + onEdit, + onClone, + onResume, + onPause, + onDelete, + onErrorClick, + onErrorDismiss, +}: CronJobItemProps) => { + const [cronExplanation, setCronExplanation] = useState(null); + const locale = useLocale(); + const t = useTranslations(); + + useEffect(() => { + if (job.schedule) { + const explanation = parseCronExpression(job.schedule, locale); + setCronExplanation(explanation); + } else { + setCronExplanation(null); + } + }, [job.schedule]); + return ( +
+
+
+
+ + {job.schedule} + +
+
+                                {job.command}
+                            
+
+
+ + {cronExplanation?.isValid && ( +
+ +

+ {cronExplanation.humanReadable} +

+
+ )} + +
+
+ + {job.user} +
+ {job.paused && ( + + {t("cronjobs.paused")} + + )} + +
+ + {job.comment && ( +

+ {job.comment} +

+ )} +
+ +
+ + + + {job.paused ? ( + + ) : ( + + )} + +
+
+
+ ); +}; \ No newline at end of file diff --git a/app/_components/FeatureComponents/Layout/Sidebar.tsx b/app/_components/FeatureComponents/Layout/Sidebar.tsx index 7301b7f..0102260 100644 --- a/app/_components/FeatureComponents/Layout/Sidebar.tsx +++ b/app/_components/FeatureComponents/Layout/Sidebar.tsx @@ -11,10 +11,10 @@ import { HardDrive, Wifi, } from "lucide-react"; +import { useTranslations } from "next-intl"; export interface SidebarProps extends HTMLAttributes { children: React.ReactNode; - title?: string; defaultCollapsed?: boolean; quickStats?: { cpu: number; @@ -28,13 +28,13 @@ export const Sidebar = forwardRef( { className, children, - title = "System Overview", defaultCollapsed = false, quickStats, ...props }, ref ) => { + const t = useTranslations(); const [isCollapsed, setIsCollapsed] = useState(defaultCollapsed); const [isMobileOpen, setIsMobileOpen] = useState(false); @@ -113,7 +113,7 @@ export const Sidebar = forwardRef( {(!isCollapsed || !isCollapsed) && (

- {title} + {t("sidebar.systemOverview")}

)} diff --git a/app/_components/FeatureComponents/Layout/TabbedInterface.tsx b/app/_components/FeatureComponents/Layout/TabbedInterface.tsx index db842b6..f5c6d02 100644 --- a/app/_components/FeatureComponents/Layout/TabbedInterface.tsx +++ b/app/_components/FeatureComponents/Layout/TabbedInterface.tsx @@ -6,6 +6,7 @@ import { ScriptsManager } from "@/app/_components/FeatureComponents/Scripts/Scri import { CronJob } from "@/app/_utils/cronjob-utils"; import { Script } from "@/app/_utils/scripts-utils"; import { Clock, FileText } from "lucide-react"; +import { useTranslations } from "next-intl"; interface TabbedInterfaceProps { cronJobs: CronJob[]; @@ -19,6 +20,7 @@ export const TabbedInterface = ({ const [activeTab, setActiveTab] = useState<"cronjobs" | "scripts">( "cronjobs" ); + const t = useTranslations(); return (
@@ -32,7 +34,7 @@ export const TabbedInterface = ({ }`} > - Cron Jobs + {t("cronjobs.cronJobs")} {cronJobs.length} @@ -45,7 +47,7 @@ export const TabbedInterface = ({ }`} > - Scripts + {t("scripts.scripts")} {scripts.length} diff --git a/app/_components/FeatureComponents/Modals/CreateTaskModal.tsx b/app/_components/FeatureComponents/Modals/CreateTaskModal.tsx index c55fa57..e30c527 100644 --- a/app/_components/FeatureComponents/Modals/CreateTaskModal.tsx +++ b/app/_components/FeatureComponents/Modals/CreateTaskModal.tsx @@ -10,6 +10,7 @@ import { UserSwitcher } from "@/app/_components/FeatureComponents/User/UserSwitc import { Plus, Terminal, FileText, X } from "lucide-react"; import { getScriptContent } from "@/app/_server/actions/scripts"; import { getHostScriptPath } from "@/app/_server/actions/scripts"; +import { useTranslations } from "next-intl"; interface Script { id: string; @@ -46,6 +47,7 @@ export const CreateTaskModal = ({ useState(""); const [isSelectScriptModalOpen, setIsSelectScriptModalOpen] = useState(false); const selectedScript = scripts.find((s) => s.id === form.selectedScriptId); + const t = useTranslations(); useEffect(() => { const loadScriptContent = async () => { @@ -86,13 +88,13 @@ export const CreateTaskModal = ({
@@ -145,9 +147,9 @@ export const CreateTaskModal = ({
-
Saved Script
+
{t("scripts.savedScript")}
- Select from library + {t("scripts.selectFromLibrary")}
@@ -182,7 +184,7 @@ export const CreateTaskModal = ({ onClick={() => setIsSelectScriptModalOpen(true)} className="h-8 px-2 text-xs" > - Change + {t("common.change")}