Browse Source

feat: create scheduler to run cron jobs and setup periodic repo update

Nicolas Meienberger 2 years ago
parent
commit
747fee006d

+ 1 - 1
packages/dashboard/nodemon.json

@@ -1,5 +1,5 @@
 {
 {
-  "watch": ["server.ts"],
+  "watch": ["server.ts", "src/server"],
   "exec": "ts-node --project tsconfig.server.json server.ts",
   "exec": "ts-node --project tsconfig.server.json server.ts",
   "ext": "js ts"
   "ext": "js ts"
 }
 }

+ 2 - 2
packages/dashboard/package.json

@@ -4,7 +4,7 @@
   "private": true,
   "private": true,
   "scripts": {
   "scripts": {
     "prisma:pull": "prisma db pull",
     "prisma:pull": "prisma db pull",
-    "test": "dotenv -e .env.test -- ts-node ./run-migration.ts && jest --colors",
+    "test": "dotenv -e .env.test -- jest --colors",
     "test:client": "jest --colors --selectProjects client --",
     "test:client": "jest --colors --selectProjects client --",
     "test:server": "jest --colors --selectProjects server --",
     "test:server": "jest --colors --selectProjects server --",
     "postinstall": "prisma generate",
     "postinstall": "prisma generate",
@@ -45,7 +45,6 @@
     "redis": "^4.3.1",
     "redis": "^4.3.1",
     "remark-breaks": "^3.0.2",
     "remark-breaks": "^3.0.2",
     "remark-gfm": "^3.0.1",
     "remark-gfm": "^3.0.1",
-    "remark-mdx": "^2.1.1",
     "sass": "^1.55.0",
     "sass": "^1.55.0",
     "semver": "^7.3.7",
     "semver": "^7.3.7",
     "sharp": "0.30.7",
     "sharp": "0.30.7",
@@ -86,6 +85,7 @@
     "eslint-config-airbnb": "^19.0.4",
     "eslint-config-airbnb": "^19.0.4",
     "eslint-config-airbnb-typescript": "^17.0.0",
     "eslint-config-airbnb-typescript": "^17.0.0",
     "eslint-config-next": "13.1.1",
     "eslint-config-next": "13.1.1",
+    "eslint-config-prettier": "^8.6.0",
     "eslint-plugin-import": "^2.25.3",
     "eslint-plugin-import": "^2.25.3",
     "eslint-plugin-jest": "^27.1.7",
     "eslint-plugin-jest": "^27.1.7",
     "eslint-plugin-jsdoc": "^39.6.9",
     "eslint-plugin-jsdoc": "^39.6.9",

+ 8 - 9
packages/dashboard/run-migration.ts

@@ -2,20 +2,21 @@ import path from 'path';
 import pg from 'pg';
 import pg from 'pg';
 import { migrate } from '@runtipi/postgres-migrations';
 import { migrate } from '@runtipi/postgres-migrations';
 import { Logger } from './src/server/core/Logger';
 import { Logger } from './src/server/core/Logger';
+import { getConfig } from './src/server/core/TipiConfig';
 
 
 export const runPostgresMigrations = async (dbName?: string) => {
 export const runPostgresMigrations = async (dbName?: string) => {
   Logger.info('Starting database migration');
   Logger.info('Starting database migration');
 
 
-  const { POSTGRES_HOST, POSTGRES_DBNAME, POSTGRES_USERNAME, POSTGRES_PASSWORD, POSTGRES_PORT = 5432 } = process.env;
+  const { postgresHost, postgresDatabase, postgresUsername, postgresPassword, postgresPort } = getConfig();
 
 
-  Logger.info('Connecting to database', POSTGRES_DBNAME, 'on', POSTGRES_HOST, 'as', POSTGRES_USERNAME, 'on port', POSTGRES_PORT);
+  Logger.info(`Connecting to database ${postgresDatabase} on ${postgresHost} as ${postgresUsername} on port ${postgresPort}`);
 
 
   const client = new pg.Client({
   const client = new pg.Client({
-    user: POSTGRES_USERNAME,
-    host: POSTGRES_HOST,
-    database: dbName || POSTGRES_DBNAME,
-    password: POSTGRES_PASSWORD,
-    port: Number(POSTGRES_PORT),
+    user: postgresUsername,
+    host: postgresHost,
+    database: dbName || postgresDatabase,
+    password: postgresPassword,
+    port: Number(postgresPort),
   });
   });
   await client.connect();
   await client.connect();
 
 
@@ -44,5 +45,3 @@ export const runPostgresMigrations = async (dbName?: string) => {
   Logger.info('Migration complete');
   Logger.info('Migration complete');
   await client.end();
   await client.end();
 };
 };
-
-runPostgresMigrations();

+ 5 - 2
packages/dashboard/server.ts

@@ -32,13 +32,16 @@ nextApp.prepare().then(async () => {
 
 
     // Run database migrations
     // Run database migrations
     await runPostgresMigrations();
     await runPostgresMigrations();
-
-    // startJobs();
     setConfig('status', 'RUNNING');
     setConfig('status', 'RUNNING');
 
 
+    // Clone and update apps repo
     await EventDispatcher.dispatchEventAsync('clone_repo', [getConfig().appsRepoUrl]);
     await EventDispatcher.dispatchEventAsync('clone_repo', [getConfig().appsRepoUrl]);
     await EventDispatcher.dispatchEventAsync('update_repo', [getConfig().appsRepoUrl]);
     await EventDispatcher.dispatchEventAsync('update_repo', [getConfig().appsRepoUrl]);
 
 
+    // Scheduled events
+    EventDispatcher.scheduleEvent({ type: 'update_repo', args: [getConfig().appsRepoUrl], cronExpression: '*/30 * * * *' });
+    EventDispatcher.scheduleEvent({ type: 'system_info', args: [], cronExpression: '* * * * *' });
+
     appService.startAllApps();
     appService.startAllApps();
 
 
     Logger.info(`> Server listening at http://localhost:${port} as ${dev ? 'development' : process.env.NODE_ENV}`);
     Logger.info(`> Server listening at http://localhost:${port} as ${dev ? 'development' : process.env.NODE_ENV}`);

+ 36 - 14
packages/dashboard/src/server/core/EventDispatcher/EventDispatcher.ts

@@ -1,4 +1,5 @@
 /* eslint-disable vars-on-top */
 /* eslint-disable vars-on-top */
+import cron from 'node-cron';
 import fs from 'fs-extra';
 import fs from 'fs-extra';
 import { Logger } from '../Logger';
 import { Logger } from '../Logger';
 import { getConfig } from '../TipiConfig';
 import { getConfig } from '../TipiConfig';
@@ -27,7 +28,14 @@ type SystemEvent = {
   creationDate: Date;
   creationDate: Date;
 };
 };
 
 
-type EventStatusTypes = 'running' | 'success' | 'error' | 'waiting';
+const EVENT_STATUS = {
+  RUNNING: 'running',
+  SUCCESS: 'success',
+  ERROR: 'error',
+  WAITING: 'waiting',
+} as const;
+
+type EventStatus = typeof EVENT_STATUS[keyof typeof EVENT_STATUS]; // 'running' | 'success' | 'error' | 'waiting';
 
 
 const WATCH_FILE = '/runtipi/state/events';
 const WATCH_FILE = '/runtipi/state/events';
 
 
@@ -60,7 +68,8 @@ class EventDispatcher {
 
 
   /**
   /**
    * Generate a random task id
    * Generate a random task id
-   * @returns - Random id
+   *
+   * @returns {string} id - Randomly generated id
    */
    */
   static generateId() {
   static generateId() {
     return Math.random().toString(36).substring(2, 9);
     return Math.random().toString(36).substring(2, 9);
@@ -125,10 +134,11 @@ class EventDispatcher {
 
 
   /**
   /**
    * Check event status
    * Check event status
-   * @param id - Event id
-   * @returns - Event status
+   *
+   * @param {string} id - Event id
+   * @returns {EventStatus} - Event status
    */
    */
-  private getEventStatus(id: string): EventStatusTypes {
+  private getEventStatus(id: string): EventStatus {
     const event = this.queue.find((e) => e.id === id);
     const event = this.queue.find((e) => e.id === id);
 
 
     if (!event) {
     if (!event) {
@@ -148,16 +158,17 @@ class EventDispatcher {
       return 'waiting';
       return 'waiting';
     }
     }
 
 
-    const status = line.split(' ')[2] as EventStatusTypes;
+    const status = line.split(' ')[2] as EventStatus;
 
 
     return status;
     return status;
   }
   }
 
 
   /**
   /**
    * Dispatch an event to the queue
    * Dispatch an event to the queue
-   * @param type - Event type
-   * @param args - Event arguments
-   * @returns - Event object
+   *
+   * @param {EventType} type - Event type
+   * @param {[string]} args - Event arguments
+   * @returns {SystemEvent} event - Event object
    */
    */
   public dispatchEvent(type: EventType, args?: string[]): SystemEvent {
   public dispatchEvent(type: EventType, args?: string[]): SystemEvent {
     const event: SystemEvent = {
     const event: SystemEvent = {
@@ -173,10 +184,12 @@ class EventDispatcher {
   }
   }
 
 
   /**
   /**
-   * Clear event from queue
-   * @param id - Event id
+   * Clears an event from the queue
+   *
+   * @param {SystemEvent} event - The event to clear
+   * @param {EventStatus} status - The status to consider the event to
    */
    */
-  private clearEvent(event: SystemEvent, status: EventStatusTypes = 'success') {
+  private clearEvent(event: SystemEvent, status: EventStatus = 'success') {
     this.queue = this.queue.filter((e) => e.id !== event.id);
     this.queue = this.queue.filter((e) => e.id !== event.id);
     if (fs.existsSync(`/app/logs/${event.id}.log`)) {
     if (fs.existsSync(`/app/logs/${event.id}.log`)) {
       const log = fs.readFileSync(`/app/logs/${event.id}.log`, 'utf8');
       const log = fs.readFileSync(`/app/logs/${event.id}.log`, 'utf8');
@@ -192,8 +205,9 @@ class EventDispatcher {
 
 
   /**
   /**
    * Dispatch an event to the queue and wait for it to finish
    * Dispatch an event to the queue and wait for it to finish
-   * @param type - Event type
-   * @param args - Event arguments
+   *
+   * @param {EventType} type - Event type
+   * @param {[string[]]} args - Event arguments
    * @returns - Promise that resolves when the event is done
    * @returns - Promise that resolves when the event is done
    */
    */
   public async dispatchEventAsync(type: EventType, args?: string[]): Promise<{ success: boolean; stdout?: string }> {
   public async dispatchEventAsync(type: EventType, args?: string[]): Promise<{ success: boolean; stdout?: string }> {
@@ -231,6 +245,14 @@ class EventDispatcher {
     EventDispatcher.instance = null;
     EventDispatcher.instance = null;
     fs.writeFileSync(WATCH_FILE, '');
     fs.writeFileSync(WATCH_FILE, '');
   }
   }
+
+  public scheduleEvent(params: { type: EventType; args?: string[]; cronExpression: string }) {
+    const { type, args, cronExpression } = params;
+
+    cron.schedule(cronExpression, async () => {
+      this.dispatchEvent(type, args);
+    });
+  }
 }
 }
 
 
 export const EventDispatcherInstance = global.EventDispatcher || EventDispatcher.getInstance();
 export const EventDispatcherInstance = global.EventDispatcher || EventDispatcher.getInstance();