mongo-scheduler-ui 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,52 @@
1
+ import { Activity, CheckCircle, XCircle, Clock, Loader, Ban } from 'lucide-react';
2
+ import type { JobStats } from '../types/job';
3
+
4
+ interface StatsCardProps {
5
+ stats: JobStats | null;
6
+ }
7
+
8
+ export default function StatsCards({ stats }: StatsCardProps) {
9
+ if (!stats) {
10
+ return (
11
+ <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4 mb-6">
12
+ {[...Array(6)].map((_, i) => (
13
+ <div key={i} className="bg-gray-800/50 rounded-lg p-4 animate-pulse">
14
+ <div className="h-4 bg-gray-700 rounded w-16 mb-2"></div>
15
+ <div className="h-8 bg-gray-700 rounded w-12"></div>
16
+ </div>
17
+ ))}
18
+ </div>
19
+ );
20
+ }
21
+
22
+ const cards = [
23
+ { label: 'Total', value: stats.total, icon: Activity, color: 'text-purple-400' },
24
+ { label: 'Pending', value: stats.pending, icon: Clock, color: 'text-blue-400' },
25
+ { label: 'Running', value: stats.running, icon: Loader, color: 'text-yellow-400' },
26
+ { label: 'Completed', value: stats.completed, icon: CheckCircle, color: 'text-green-400' },
27
+ { label: 'Failed', value: stats.failed, icon: XCircle, color: 'text-red-400' },
28
+ { label: 'Cancelled', value: stats.cancelled, icon: Ban, color: 'text-gray-400' },
29
+ ];
30
+
31
+ return (
32
+ <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4 mb-6">
33
+ {cards.map((card) => {
34
+ const Icon = card.icon;
35
+ return (
36
+ <div
37
+ key={card.label}
38
+ className="bg-gradient-to-br from-gray-800/80 to-gray-900/80 backdrop-blur-sm rounded-lg p-4 border border-gray-700/50 hover:border-gray-600/50 transition-all duration-200"
39
+ >
40
+ <div className="flex items-center justify-between mb-2">
41
+ <span className="text-sm text-gray-400">{card.label}</span>
42
+ <Icon className={`w-4 h-4 ${card.color}`} />
43
+ </div>
44
+ <div className={`text-2xl font-bold ${card.color}`}>
45
+ {card.value.toLocaleString()}
46
+ </div>
47
+ </div>
48
+ );
49
+ })}
50
+ </div>
51
+ );
52
+ }
package/src/index.css ADDED
@@ -0,0 +1,29 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ :root {
6
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
7
+ line-height: 1.5;
8
+ font-weight: 400;
9
+
10
+ color-scheme: dark;
11
+
12
+ background-color: #0a0a0a;
13
+ color: #ffffff;
14
+
15
+ font-synthesis: none;
16
+ text-rendering: optimizeLegibility;
17
+ -webkit-font-smoothing: antialiased;
18
+ -moz-osx-font-smoothing: grayscale;
19
+ }
20
+
21
+ * {
22
+ margin: 0;
23
+ padding: 0;
24
+ box-sizing: border-box;
25
+ }
26
+
27
+ body {
28
+ min-height: 100vh;
29
+ }
package/src/main.tsx ADDED
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import './index.css'
4
+ import App from './App.tsx'
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ )
@@ -0,0 +1,58 @@
1
+ import axios from "axios";
2
+ import type { Job, JobQuery, JobStats } from "../types/job";
3
+
4
+ const API_BASE_URL = import.meta.env.VITE_API_URL || "http://localhost:3000";
5
+
6
+ const api = axios.create({
7
+ baseURL: API_BASE_URL,
8
+ headers: {
9
+ "Content-Type": "application/json",
10
+ },
11
+ });
12
+
13
+ export const jobsApi = {
14
+ // Get all jobs with optional filters
15
+ getJobs: async (query?: JobQuery): Promise<Job[]> => {
16
+ const response = await api.get("/jobs", { params: query });
17
+ return response.data;
18
+ },
19
+
20
+ // Get job by ID
21
+ getJob: async (id: string): Promise<Job> => {
22
+ const response = await api.get(`/jobs/${id}`);
23
+ return response.data;
24
+ },
25
+
26
+ // Get job statistics
27
+ getStats: async (): Promise<JobStats> => {
28
+ const response = await api.get("/jobs/stats");
29
+ return response.data;
30
+ },
31
+
32
+ // Cancel a job
33
+ cancelJob: async (id: string): Promise<void> => {
34
+ await api.post(`/jobs/${id}/cancel`);
35
+ },
36
+
37
+ // Retry a failed job
38
+ retryJob: async (id: string): Promise<void> => {
39
+ await api.post(`/jobs/${id}/retry`);
40
+ },
41
+
42
+ // Create a new job
43
+ createJob: async (jobData: Partial<Job>): Promise<Job> => {
44
+ const response = await api.post("/jobs", jobData);
45
+ return response.data;
46
+ },
47
+
48
+ // Update an existing job
49
+ updateJob: async (id: string, jobData: Partial<Job>): Promise<Job> => {
50
+ const response = await api.put(`/jobs/${id}`, jobData);
51
+ return response.data;
52
+ },
53
+
54
+ // Delete a job
55
+ deleteJob: async (id: string): Promise<void> => {
56
+ await api.delete(`/jobs/${id}`);
57
+ },
58
+ };
@@ -0,0 +1,43 @@
1
+ export interface Job {
2
+ _id: string;
3
+ name: string;
4
+ data?: any;
5
+ status: "pending" | "running" | "completed" | "failed" | "cancelled";
6
+ attempts: number;
7
+ nextRunAt: Date | string;
8
+ lastRunAt?: Date | string;
9
+ lockedAt?: Date | string;
10
+ lockedBy?: string;
11
+ retry?: {
12
+ maxAttempts: number;
13
+ delay: number | ((attempt: number) => number);
14
+ };
15
+ repeat?: {
16
+ cron?: string;
17
+ every?: number;
18
+ timezone?: string;
19
+ };
20
+ lastError?: string;
21
+ createdAt: Date | string;
22
+ updatedAt: Date | string;
23
+ }
24
+
25
+ export interface JobQuery {
26
+ name?: string;
27
+ status?: string | string[];
28
+ limit?: number;
29
+ skip?: number;
30
+ sort?: {
31
+ field: "nextRunAt" | "createdAt" | "updatedAt";
32
+ order: "asc" | "desc";
33
+ };
34
+ }
35
+
36
+ export interface JobStats {
37
+ total: number;
38
+ pending: number;
39
+ running: number;
40
+ completed: number;
41
+ failed: number;
42
+ cancelled: number;
43
+ }
@@ -0,0 +1,31 @@
1
+ import { formatDistance, format } from "date-fns";
2
+
3
+ export const formatDate = (date: Date | string): string => {
4
+ return format(new Date(date), "MMM dd, yyyy HH:mm:ss");
5
+ };
6
+
7
+ export const formatRelativeTime = (date: Date | string): string => {
8
+ return formatDistance(new Date(date), new Date(), { addSuffix: true });
9
+ };
10
+
11
+ export const getStatusColor = (status: string): string => {
12
+ const colors = {
13
+ pending: "bg-blue-500/20 text-blue-400 border-blue-500/30",
14
+ running: "bg-yellow-500/20 text-yellow-400 border-yellow-500/30",
15
+ completed: "bg-green-500/20 text-green-400 border-green-500/30",
16
+ failed: "bg-red-500/20 text-red-400 border-red-500/30",
17
+ cancelled: "bg-gray-500/20 text-gray-400 border-gray-500/30",
18
+ };
19
+ return colors[status as keyof typeof colors] || colors.pending;
20
+ };
21
+
22
+ export const getStatusIcon = (status: string): string => {
23
+ const icons = {
24
+ pending: "⏱️",
25
+ running: "▶️",
26
+ completed: "✅",
27
+ failed: "❌",
28
+ cancelled: "🚫",
29
+ };
30
+ return icons[status as keyof typeof icons] || "⏱️";
31
+ };
@@ -0,0 +1,8 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
4
+ theme: {
5
+ extend: {},
6
+ },
7
+ plugins: [],
8
+ };
@@ -0,0 +1,28 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+ "target": "ES2022",
5
+ "useDefineForClassFields": true,
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "module": "ESNext",
8
+ "types": ["vite/client"],
9
+ "skipLibCheck": true,
10
+
11
+ /* Bundler mode */
12
+ "moduleResolution": "bundler",
13
+ "allowImportingTsExtensions": true,
14
+ "verbatimModuleSyntax": true,
15
+ "moduleDetection": "force",
16
+ "noEmit": true,
17
+ "jsx": "react-jsx",
18
+
19
+ /* Linting */
20
+ "strict": true,
21
+ "noUnusedLocals": true,
22
+ "noUnusedParameters": true,
23
+ "erasableSyntaxOnly": true,
24
+ "noFallthroughCasesInSwitch": true,
25
+ "noUncheckedSideEffectImports": true
26
+ },
27
+ "include": ["src"]
28
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ]
7
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
+ "target": "ES2023",
5
+ "lib": ["ES2023"],
6
+ "module": "ESNext",
7
+ "types": ["node"],
8
+ "skipLibCheck": true,
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "verbatimModuleSyntax": true,
14
+ "moduleDetection": "force",
15
+ "noEmit": true,
16
+
17
+ /* Linting */
18
+ "strict": true,
19
+ "noUnusedLocals": true,
20
+ "noUnusedParameters": true,
21
+ "erasableSyntaxOnly": true,
22
+ "noFallthroughCasesInSwitch": true,
23
+ "noUncheckedSideEffectImports": true
24
+ },
25
+ "include": ["vite.config.ts"]
26
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ // https://vite.dev/config/
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ })