├── .eslintrc.json ├── .gitignore ├── Assets ├── env-file.png ├── proteus-header.png └── yaml-template.png ├── README.md ├── forge.config.ts ├── package-lock.json ├── package.json ├── proteus-image ├── Dockerfile ├── cronJobsModel.js ├── index.js ├── jobsModel.js └── package.json ├── src ├── App.tsx ├── Components │ ├── Archive │ │ ├── Archive.tsx │ │ ├── ArchiveJob.tsx │ │ ├── ArchiveJobHover.tsx │ │ └── Dropdown.tsx │ ├── Home │ │ ├── GridHeader.tsx │ │ ├── Home.tsx │ │ ├── HomeListJob.tsx │ │ ├── ScheduleInterval.tsx │ │ ├── ScheduleJob.tsx │ │ └── ScheduleJobHover.tsx │ ├── JobCreator │ │ ├── CronJobForm.tsx │ │ ├── JobCreator.tsx │ │ ├── JobForm.tsx │ │ └── JobTabs.tsx │ ├── Nav.tsx │ └── NoPage.tsx ├── Models │ ├── ArchivedJobsModel.ts │ └── CronjobModel.ts ├── Styles │ ├── archive.css │ ├── createjobs.css │ ├── home.css │ └── sidebar.css ├── ThemeContext.ts ├── images │ └── PROTEUS.png ├── index.css ├── index.html ├── index.ts ├── preload.ts ├── renderer.ts └── types.ts ├── test.yaml ├── tsconfig.json ├── webpack.main.config.ts ├── webpack.plugins.ts ├── webpack.renderer.config.ts ├── webpack.rules.ts └── website ├── 404.html ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.cjs ├── public └── vite.svg ├── src ├── App.css ├── App.jsx ├── assets │ ├── Archive.gif │ ├── Create.gif │ ├── Home.gif │ ├── PROTEUS.png │ ├── PROTEUS2.png │ ├── PROTEUS3.png │ ├── bianca2.png │ ├── eddy2.png │ ├── mark2.png │ ├── matt2.png │ ├── proteus.ico │ ├── protozoa1.ico │ ├── protozoa1.png │ └── react.svg ├── components │ ├── Contact.jsx │ ├── Features.jsx │ ├── GettingStarted.jsx │ ├── Home.jsx │ ├── Navbar.jsx │ └── Team.jsx ├── index.css └── main.jsx ├── tailwind.config.cjs └── vite.config.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/eslint-recommended", 10 | "plugin:@typescript-eslint/recommended", 11 | "plugin:import/recommended", 12 | "plugin:import/electron", 13 | "plugin:import/typescript" 14 | ], 15 | "parser": "@typescript-eslint/parser" 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | .DS_Store 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # TypeScript cache 43 | *.tsbuildinfo 44 | 45 | # Optional npm cache directory 46 | .npm 47 | 48 | # Optional eslint cache 49 | .eslintcache 50 | 51 | # Optional REPL history 52 | .node_repl_history 53 | 54 | # Output of 'npm pack' 55 | *.tgz 56 | 57 | # Yarn Integrity file 58 | .yarn-integrity 59 | 60 | # dotenv environment variables file 61 | .env 62 | .env.test 63 | 64 | # parcel-bundler cache (https://parceljs.org/) 65 | .cache 66 | 67 | # next.js build output 68 | .next 69 | 70 | # nuxt.js build output 71 | .nuxt 72 | 73 | # vuepress build output 74 | .vuepress/dist 75 | 76 | # Serverless directories 77 | .serverless/ 78 | 79 | # FuseBox cache 80 | .fusebox/ 81 | 82 | # DynamoDB Local files 83 | .dynamodb/ 84 | 85 | # Webpack 86 | .webpack/ 87 | 88 | # Electron-Forge 89 | out/ 90 | -------------------------------------------------------------------------------- /Assets/env-file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/proteus/c0e6d79c68e6f9280487762c97ff6d27f1deec2c/Assets/env-file.png -------------------------------------------------------------------------------- /Assets/proteus-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/proteus/c0e6d79c68e6f9280487762c97ff6d27f1deec2c/Assets/proteus-header.png -------------------------------------------------------------------------------- /Assets/yaml-template.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/proteus/c0e6d79c68e6f9280487762c97ff6d27f1deec2c/Assets/yaml-template.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ## [![banner](./Assets/proteus-header.png)](http://proteus-osp.app.s3-website-us-west-1.amazonaws.com/) 4 | 5 | [![React](https://img.shields.io/badge/react-%2320232a.svg?style=for-the-badge&logo=react&logoColor=%2361DAFB)](https://reactjs.org/) 6 | [![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white)](https://www.typescriptlang.org/) 7 | 8 | [![Electron.js](https://img.shields.io/badge/Electron-191970?style=for-the-badge&logo=Electron&logoColor=white)](https://www.electronjs.org/) 9 | [![AWS](https://img.shields.io/badge/Amazon_AWS-232F3E?style=for-the-badge&logo=amazon-aws&logoColor=white)](https://aws.amazon.com/) 10 | [![Docker](https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white)](https://www.docker.com/) 11 | [![Prometheus](https://img.shields.io/badge/Prometheus-E6522C?style=for-the-badge&logo=Prometheus&logoColor=white)](https://prometheus.io/) 12 | 13 | [![MongoDB](https://img.shields.io/badge/MongoDB-%234ea94b.svg?style=for-the-badge&logo=mongodb&logoColor=white)](https://www.mongodb.com/) 14 | [![License](https://img.shields.io/github/license/Ileriayo/markdown-badges?style=for-the-badge)](public/LICENSE) 15 | 16 | ### This Kubernetes Cluster Job and Cronjob visualizer and archiver delivers intuitive visualization of scheduled jobs, key metrics for all Jobs and Cronjobs deployed within your cluster, and the ability to seamlessly deploy new Jobs and Cronjobs via the application itself. 17 | 18 | [![Medium](https://img.shields.io/badge/Medium-12100E?style=for-the-badge&logo=medium&logoColor=white)](https://medium.com/@matt.henely8/proteus-8f3262cbedb9) 19 | [![LinkedIn](https://img.shields.io/badge/linkedin-%230077B5.svg?style=for-the-badge&logo=linkedin&logoColor=white)](https://www.linkedin.com/company/proteus-app/) 20 |
21 | 22 | Getting Started: 23 | 24 | Welcome to Proteus! This guide will help you get started using our app. Whether you're a first-time user or just need a refresher, we've got you covered. We'll walk you through the setup process and show you how to use Proteus to its fullest potential. 25 | If you're interested in more detailed information on Proteus' features and functionality, be sure to check out our documentation. But for now, let's dive in and get started with using our app! 26 | 27 | Prerequisites: 28 | - You must be able to connect to a currently deployed kubernetes cluster via kubectl 29 | - You must have Prometheus and Kube State Metrics deployed within your cluster 30 | - A mongoDB URI 31 | 32 | ## Quick Start: 33 | 34 | Steps: 35 | 36 | 1. Download our desktop application from GitHub or clone the repo, install dependencies, and npm start. 37 | 38 |
39 | ENV File 40 | 41 |
42 | 43 | 2. Create a .env file within your root directory and change the string 'YOUR DB_FILE HERE' to whatever your DB URI is. 44 | 45 | 3. Utilizing the provided yaml file template, replace "Your DB URI" on line 13 with your DB URI and replace "Your service IP" on line 15 with the service IP for the Prometheus Server pod within your cluster. Save this yaml file and record the file path as it will be necessary for the next step. 46 | 47 | 48 |
49 | YAML Template: 50 | 51 |
52 | 53 | YAML Template 54 |
55 | 56 | Feel free to change line 4 from "db-bridge" to whichever name you want your job to possess. 57 | 58 | 4. You will notice that line 10 of the yaml file includes a reference to ospproteus/proteus-image. This is an image hosted on Docker Hub which you will now deploy as a job within your cluster. To do so, deploy a new job using our application's "Create Job" form or by another means, such as via kubectl. 59 | - Using kubectl, copy the following into your terminal: 60 | kubectl apply -f "THE FILE PATH FOR THE YAML FILE" 61 | - It may take slightly longer the first time after following these steps for metrics to be scraped from your cluster to be displayed within the application, but all subsequent use of the application will be significantly quicker. 62 | 63 | After following these steps, the application will begin displaying all Job and Cronjobs within your cluster. 64 | 65 | Apple Silicon users may experience intermitent issues upon application start. Most, if not all issues can be resolved by closing the application and reopening it. 66 | 67 | 68 | ## Current Features 69 | 70 | 71 | ## Iteration Plans 72 | 73 | 74 | ## Connect with the Team! 75 | | Mark Bryan | Bianca Hua | Eddy Kaggia | Matt Henely | 76 | | :---: | :---: | :---: | :---: | 77 | | [![GitHub](https://skillicons.dev/icons?i=github)](https://github.com/mbryan13) [![LinkedIn](https://skillicons.dev/icons?i=linkedin)](https://www.linkedin.com/in/marklawbryan/) | [![GitHub](https://skillicons.dev/icons?i=github)](https://github.com/biancahua) [![LinkedIn](https://skillicons.dev/icons?i=linkedin)](https://www.linkedin.com/in/biancahua/) | [![GitHub](https://skillicons.dev/icons?i=github)](https://github.com/EddyKaggia) [![LinkedIn](https://skillicons.dev/icons?i=linkedin)](https://www.linkedin.com/in/eddy-kaggia/) | [![GitHub](https://skillicons.dev/icons?i=github)](https://github.com/mhenely) [![LinkedIn](https://skillicons.dev/icons?i=linkedin)](https://www.linkedin.com/in/matt-henely/) | 78 | 79 | 80 | -------------------------------------------------------------------------------- /forge.config.ts: -------------------------------------------------------------------------------- 1 | import type { ForgeConfig } from '@electron-forge/shared-types'; 2 | import { MakerSquirrel } from '@electron-forge/maker-squirrel'; 3 | import { MakerZIP } from '@electron-forge/maker-zip'; 4 | import { MakerDeb } from '@electron-forge/maker-deb'; 5 | import { MakerRpm } from '@electron-forge/maker-rpm'; 6 | import { WebpackPlugin } from '@electron-forge/plugin-webpack'; 7 | 8 | import { mainConfig } from './webpack.main.config'; 9 | import { rendererConfig } from './webpack.renderer.config'; 10 | 11 | const config: ForgeConfig = { 12 | packagerConfig: { 13 | name: 'proteus', 14 | executableName: 'proteus' 15 | }, 16 | rebuildConfig: {}, 17 | makers: [new MakerSquirrel({}), new MakerZIP({}, ['darwin']), new MakerRpm({}), new MakerDeb({})], 18 | plugins: [ 19 | new WebpackPlugin({ 20 | devContentSecurityPolicy: `default-src * self blob: data: gap:; style-src * self 'unsafe-inline' blob: data: gap:; script-src * 'self' 'unsafe-eval' 'unsafe-inline' blob: data: gap:; object-src * 'self' blob: data: gap:; img-src * self 'unsafe-inline' blob: data: gap:; connect-src self * 'unsafe-inline' blob: data: gap: localhost:; frame-src * self blob: data: gap:;`, 21 | mainConfig, 22 | renderer: { 23 | config: rendererConfig, 24 | entryPoints: [ 25 | { 26 | html: './src/index.html', 27 | js: './src/renderer.ts', 28 | name: 'main_window', 29 | preload: { 30 | js: './src/preload.ts', 31 | }, 32 | }, 33 | ], 34 | }, 35 | }), 36 | ], 37 | }; 38 | 39 | export default config; 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proteus", 3 | "productName": "proteus", 4 | "version": "1.0.0", 5 | "description": "My Electron application description", 6 | "main": ".webpack/main", 7 | "scripts": { 8 | "start": "electron-forge start", 9 | "package": "electron-forge package", 10 | "make": "electron-forge make", 11 | "publish": "electron-forge publish", 12 | "lint": "eslint --ext .ts,.tsx ." 13 | }, 14 | "keywords": [], 15 | "author": { 16 | "name": "Mark Bryan", 17 | "email": "marklawbryan@gmail.com" 18 | }, 19 | "license": "MIT", 20 | "devDependencies": { 21 | "@electron-forge/cli": "^6.0.4", 22 | "@electron-forge/maker-deb": "^6.0.4", 23 | "@electron-forge/maker-rpm": "^6.0.4", 24 | "@electron-forge/maker-squirrel": "^6.0.4", 25 | "@electron-forge/maker-zip": "^6.0.4", 26 | "@electron-forge/plugin-webpack": "^6.0.4", 27 | "@types/react": "^18.0.27", 28 | "@types/react-dom": "^18.0.10", 29 | "@typescript-eslint/eslint-plugin": "^5.50.0", 30 | "@typescript-eslint/parser": "^5.50.0", 31 | "@vercel/webpack-asset-relocator-loader": "^1.7.3", 32 | "css-loader": "^6.7.3", 33 | "electron": "22.2.0", 34 | "eslint": "^8.33.0", 35 | "eslint-plugin-import": "^2.27.5", 36 | "fork-ts-checker-webpack-plugin": "^7.3.0", 37 | "node-loader": "^2.0.0", 38 | "style-loader": "^3.3.1", 39 | "ts-loader": "^9.4.2", 40 | "ts-node": "^10.9.1", 41 | "typescript": "~4.5.4" 42 | }, 43 | "dependencies": { 44 | "dotenv": "^16.0.3", 45 | "electron-fetch": "^1.9.1", 46 | "electron-squirrel-startup": "^1.0.0", 47 | "mongoose": "^6.9.2", 48 | "react": "^18.2.0", 49 | "react-dom": "^18.2.0", 50 | "react-hover": "^3.0.1", 51 | "react-router-dom": "^6.8.1", 52 | "styled-components": "^5.3.6" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /proteus-image/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18.12.1 2 | WORKDIR /usr/src/app 3 | COPY . /usr/src/app 4 | RUN npm install 5 | EXPOSE 8080 6 | ENTRYPOINT ["node", "index.js"] -------------------------------------------------------------------------------- /proteus-image/cronJobsModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const cronJobsSchema = new Schema({ 5 | cronjob_name : { 6 | type: String, 7 | required: true 8 | }, 9 | kube_cronjob_created : { 10 | type: Date, 11 | required: true 12 | }, 13 | kube_cronjob_spec_failed_job_history_limit: { 14 | type: String, 15 | required: true 16 | }, 17 | kube_cronjob_spec_successful_job_history_limit: { 18 | type: String, 19 | required: true 20 | }, 21 | kube_cronjob_spec_suspend: { 22 | type: Boolean, 23 | required: true 24 | }, 25 | kube_cronjob_status_active: { 26 | type: Boolean, 27 | required: true 28 | }, 29 | kube_cronjob_status_last_successful_time: { 30 | type: Date, 31 | // required: true 32 | }, 33 | kube_cronjob_status_last_schedule_time: { 34 | type: Date, 35 | // required: true 36 | }, 37 | kube_cronjob_next_schedule_time: { 38 | type: Date, 39 | // required: true 40 | }, 41 | cronjob_node: { 42 | type: String, 43 | required: true 44 | }, 45 | cronjob_interval: { 46 | type: String, 47 | required: true 48 | }, 49 | kube_cronjob_annotations: { 50 | type: String, 51 | }, 52 | kube_cronjob_info: { 53 | type: String, 54 | }, 55 | kube_cronjob_labels: { 56 | type: String, 57 | }, 58 | kube_cronjob_metadata_resource_version: { 59 | type: String, 60 | }, 61 | 62 | 63 | }) 64 | 65 | module.exports = mongoose.model('CronJobs', cronJobsSchema); -------------------------------------------------------------------------------- /proteus-image/index.js: -------------------------------------------------------------------------------- 1 | const ArchivedJobs = require('./jobsModel'); 2 | const CronJobs = require('./cronJobsModel'); 3 | const mongoose = require('mongoose'); 4 | 5 | 6 | const fetchCronJobs = async () => { 7 | const newCronjobs = {}; 8 | try { 9 | const response = await (await fetch(`http://${process.env.SERVICE_IP}/api/v1/label/cronjob/values`)).json(); 10 | 11 | for(let i = 0; i < response.data.length; i++) { 12 | const name = response.data[i]; 13 | const cronjobOverview = (await (await fetch(`http://${process.env.SERVICE_IP}/api/v1/query?query={cronjob="${name}"}`)).json()).data.result; 14 | 15 | if(cronjobOverview.length !== 0) { 16 | for(const cronJob of cronjobOverview){ 17 | newCronjobs.cronjob_name = cronJob.metric.cronjob; 18 | newCronjobs[cronJob.metric.__name__] = cronJob.value[1]; 19 | delete newCronjobs.kube_cronjob_metadata_resource_version 20 | delete newCronjobs.kube_cronjob_labels 21 | delete newCronjobs.kube_cronjob_info 22 | delete newCronjobs.kube_cronjob_annotations 23 | } 24 | 25 | newCronjobs.kube_cronjob_spec_suspend === "0" ? newCronjobs.kube_cronjob_spec_suspend = false : newCronjobs.kube_cronjob_spec_suspend = true; 26 | 27 | newCronjobs.kube_cronjob_status_active === "0" ? newCronjobs.kube_cronjob_status_active = false : newCronjobs.kube_cronjob_status_active = true; 28 | 29 | newCronjobs.cronjob_node = cronjobOverview[0].metric.node; 30 | newCronjobs.cronjob_interval = (newCronjobs.kube_cronjob_next_schedule_time - newCronjobs.kube_cronjob_status_last_schedule_time)/60; 31 | 32 | const exists = await CronJobs.exists({cronjob_name: name}); 33 | if(exists === null) await CronJobs.create(newCronjobs); 34 | else CronJobs.replaceOne({cronjob_name: name}, newCronjobs, null, (err, docs) => console.log(err)); 35 | } 36 | 37 | } 38 | 39 | return newCronjobs; 40 | } catch (e) { 41 | console.log(e); 42 | } 43 | }; 44 | 45 | const archiveFetch = () => { 46 | 47 | const allJobNames = async () => { 48 | try { 49 | const response = await (await fetch(`http://${process.env.SERVICE_IP}/api/v1/label/job_name/values`)).json(); 50 | fetchingPastJobs(response.data, '5d'); 51 | fetchCronJobs(); 52 | } catch (err) { console.log(err); } 53 | }; 54 | 55 | allJobNames(); 56 | 57 | const fetchingPastJobs = async (jobs, time) => { 58 | const jobMetrics = ['kube_job_complete', 'kube_job_created', 'kube_job_status_active', 'kube_job_status_completion_time', 'kube_job_status_failed', 'kube_job_status_start_time', 'kube_job_status_succeeded', 'kube_job_owner']; 59 | for (let i = 0; i < jobs.length; i++) { 60 | 61 | try { 62 | const pJO = {}; 63 | const response = await (await fetch(`http://${process.env.SERVICE_IP}/api/v1/query?query={job_name="${jobs[i]}"}[${time}]`)).json(); 64 | if (response.data.result.length > 0) { 65 | response.data.result.forEach(metricObj => { 66 | if (!pJO['kube_job_namespace']) pJO['kube_job_namespace'] = metricObj.metric.namespace; 67 | if (metricObj.metric.__name__ === 'kube_job_complete' && metricObj.metric.condition === 'true'|| 68 | metricObj.metric.__name__ === 'kube_job_status_failed' || 69 | metricObj.metric.__name__ === 'kube_job_status_active' || 70 | metricObj.metric.__name__ === 'kube_job_status_succeeded') { 71 | if (metricObj.values[metricObj.values.length - 1][1] === '1') { 72 | pJO[metricObj.metric.__name__] = true; 73 | } else { 74 | pJO[metricObj.metric.__name__] = false; 75 | } 76 | } 77 | 78 | else if (metricObj.metric.__name__ === 'kube_job_created' || 79 | metricObj.metric.__name__ === 'kube_job_status_start_time') { 80 | pJO[metricObj.metric.__name__] = new Date((metricObj.values[metricObj.values.length - 1][1]) * 1000); 81 | } 82 | 83 | else if (metricObj.metric.__name__ === 'kube_job_owner') { 84 | pJO['cronjob_name'] = metricObj.metric.owner_name; 85 | pJO['node'] = metricObj.metric.node; 86 | pJO['instance'] = metricObj.metric.instance; 87 | } 88 | 89 | if(metricObj.metric.__name__ === 'kube_job_status_completion_time') { 90 | pJO['kube_job_status_completion_time'] = new Date((metricObj.values[metricObj.values.length - 1][1]) * 1000); 91 | 92 | } 93 | 94 | }); 95 | pJO.kube_name = jobs[i]; 96 | 97 | if(!pJO.kube_job_status_completion_time) { 98 | pJO.kube_job_status_completion_time = new Date(0); 99 | pJO.kube_job_runtime = -1; 100 | } else { 101 | pJO['kube_job_runtime'] = (pJO['kube_job_status_completion_time'] - pJO['kube_job_status_start_time']); 102 | } 103 | if(!pJO.kube_job_complete) pJO.kube_job_complete = false; 104 | 105 | const exists = await ArchivedJobs.exists({kube_name: jobs[i]}); 106 | if(exists === null) { 107 | await ArchivedJobs.create(pJO); 108 | } 109 | 110 | } 111 | 112 | } catch (err) { console.log(err); } 113 | } 114 | }; 115 | }; 116 | 117 | mongoose.connect(process.env.DB_URI, {useNewUrlParser: true, useUnifiedTopology: true}); 118 | mongoose.connection.once('open', () => { 119 | console.log('MongoDB database connection established successfully'); 120 | 121 | setInterval(fetchCronJobs, 60000); 122 | setInterval(archiveFetch, 300000); 123 | }); 124 | 125 | module.exports = { archiveFetch, fetchCronJobs }; -------------------------------------------------------------------------------- /proteus-image/jobsModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const archivedJobSchema = new Schema({ 5 | kube_name : { 6 | type: String, 7 | required: true, 8 | }, 9 | kube_job_created: { 10 | type: Date, 11 | required: true 12 | }, 13 | kube_job_complete: { 14 | type: Boolean, 15 | required: true 16 | }, 17 | kube_job_status_active: { 18 | type: Boolean, 19 | required: true 20 | }, 21 | kube_job_status_completion_time: { 22 | type: String, 23 | required: true 24 | }, 25 | kube_job_status_failed: { 26 | type: Boolean, 27 | required: true 28 | }, 29 | kube_job_status_start_time: { 30 | type: Date, 31 | required: true 32 | }, 33 | kube_job_status_succeeded: { 34 | type: Boolean, 35 | required: true 36 | }, 37 | cronjob_name: { 38 | type: String, 39 | required: false 40 | }, 41 | kube_job_namespace: { 42 | type: String, 43 | required: false 44 | }, 45 | node: { 46 | type: String, 47 | required: false 48 | }, 49 | instance: { 50 | type: String, 51 | required: false 52 | }, 53 | kube_job_runtime: { 54 | type: Number, 55 | required: true 56 | } 57 | }); 58 | 59 | module.exports = mongoose.model('ArchivedJobs', archivedJobSchema); -------------------------------------------------------------------------------- /proteus-image/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "proteus-image", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "nodemon index.js" 9 | }, 10 | "author": "\"Eddy Kaggia\"", 11 | "license": "ISC", 12 | "dependencies": { 13 | "cors": "^2.8.5", 14 | "mongoose": "^6.9.1", 15 | "mongosh": "^1.7.1", 16 | "nodemon": "^2.0.20" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import {render} from 'react-dom'; 2 | import { useState, useEffect } from 'react'; 3 | import { HashRouter, Route, Routes} from "react-router-dom"; 4 | import { themes, ThemeContext } from './ThemeContext'; 5 | import { Home } from './Components/Home/Home'; 6 | import { Archive } from './Components/Archive/Archive'; 7 | import { NoPage } from './Components/NoPage'; 8 | import { JobCreator } from './Components/JobCreator/JobCreator'; 9 | import { Nav } from './Components/Nav'; 10 | 11 | const App = () => { 12 | const [theme, setTheme] = useState('dark'); 13 | 14 | useEffect(() => { 15 | document.body.style.backgroundColor = themes[theme].bgPrimary; 16 | }, [theme]); 17 | 18 | const toggleTheme = () => { 19 | if(theme === 'dark') setTheme('light'); 20 | else setTheme('dark'); 21 | } 22 | 23 | return ( 24 | 25 | 26 |