10 |
17 | div>
18 | );
19 | };
20 |
21 |
--------------------------------------------------------------------------------
/src/Components/Home/GridHeader.tsx:
--------------------------------------------------------------------------------
1 | import {useContext} from 'react';
2 | import { ThemeContext } from '../../ThemeContext';
3 |
4 | export const GridHeader = ({metric, invert, handleSort}: any) => {
5 | const theme = useContext(ThemeContext);
6 | return (
7 |
8 |
handleSort('cronjob_name')}>
9 |
Name
10 | {metric === "cronjob_name" && invert === 1 &&
}
11 | {metric === "cronjob_name" && invert === -1 &&
}
12 |
13 |
handleSort('kube_cronjob_next_schedule_time')}>
14 |
Next
15 | {metric === "kube_cronjob_next_schedule_time" && invert === 1 &&
}
16 | {metric === "kube_cronjob_next_schedule_time" && invert === -1 &&
}
17 |
18 |
handleSort('cronjob_interval')}>
19 |
Interval
20 | {metric === "cronjob_interval" && invert === 1 &&
}
21 | {metric === "cronjob_interval" && invert === -1 &&
}
22 |
23 |
handleSort('kube_cronjob_created')}>
24 |
Created
25 | {metric === "kube_cronjob_created" && invert === 1 &&
}
26 | {metric === "kube_cronjob_created" && invert === -1 &&
}
27 |
28 |
handleSort('cronjob_node')}>
29 |
Node
30 | {metric === "cronjob_node" && invert === 1 &&
}
31 | {metric === "cronjob_node" && invert === -1 &&
}
32 |
33 |
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/src/Components/Home/Home.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useContext } from 'react';
2 | import { ThemeContext } from '../../ThemeContext';
3 | import '../../Styles/home.css';
4 | import { ScheduleInterval } from './ScheduleInterval';
5 | import { JobMetrics, ScheduleJobObject, HoursObject, ScheduleCronJob, HomeHoverState } from '../../types';
6 | import { HomeListJob } from './HomeListJob';
7 | import { ScheduleJobHover } from './ScheduleJobHover';
8 | import { GridHeader } from './GridHeader';
9 |
10 | export const Home = () => {
11 | const theme = useContext(ThemeContext);
12 | const [hours, setHours] = useState({ startIndex: 0, jobs: [[],[],[],[],[],[],[],[],[],[],[],[]]});
13 | const [cronjobs, setCronJobs] = useState([]);
14 | const [hover, setHover] = useState
({});
15 | const [hoveredCronjob, setHoveredCronjob] = useState();
16 | const [sort, setSort] = useState({metric: "kube_cronjob_next_schedule_time", invert: 1, isMetric: 1, isName: 0});
17 | const [dayStart, setDayStart] = useState(findStartOfDay(Date.now()));
18 |
19 | function handleSort(metric: string): void {
20 | setSort(prevSort => {
21 | const newSort = {...prevSort};
22 | if(prevSort.metric === metric) newSort.invert *= -1;
23 | else newSort.invert = 1;
24 | newSort.metric = metric;
25 | return newSort;
26 | });
27 | }
28 |
29 | function findStartOfDay(time : number): Date {
30 | const date = new Date(time);
31 | return new Date(date.getFullYear(), date.getMonth(), date.getDate());
32 | }
33 |
34 | function getDayOfWeek(date: Date): string {
35 | const daysOfWeek = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
36 | const dayIndex = date.getDay();
37 | return daysOfWeek[dayIndex];
38 | }
39 |
40 | function getMonth(date: Date): string {
41 | const months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
42 | const monthIndex = date.getMonth();
43 | return months[monthIndex];
44 | }
45 |
46 | // Custom comparison function that takes into account numbers
47 | function sortingFunction(cronjob1: ScheduleCronJob, cronjob2: ScheduleCronJob) {
48 | if(sort.metric === "cronjob_name") {
49 | // Convert the strings to numbers if possible
50 | const name1 = cronjob1.cronjob_name;
51 | const name2 = cronjob2.cronjob_name;
52 | const numA = parseInt(name1.match(/\d+/), 10);
53 | const numB = parseInt(name2.match(/\d+/), 10);
54 |
55 | if (!isNaN(numA) && !isNaN(numB)) {
56 | return sort.invert * (numA - numB);
57 | }
58 | // If one or both strings don't contain numbers, compare them as strings
59 | return sort.invert * (name1.localeCompare(name2));
60 | }
61 | return sort.invert * (cronjob1[sort.metric] - cronjob2[sort.metric]);
62 | }
63 |
64 | const createIntervalDisplay = () => {
65 | const times = ["12AM", "2AM", "4AM", "6AM", "8AM", "10AM", "12PM", "2PM", "4PM", "6PM", "8PM", "10PM"];
66 | const intervalDisplay = [];
67 | let count = 0, intervalIndex = hours.startIndex;
68 | while(count < hours.jobs.length) {
69 | if(intervalIndex === hours.jobs.length) intervalIndex = 0;
70 | intervalDisplay.push({times[intervalIndex]}
);
71 | intervalIndex++;
72 | count++;
73 | }
74 | return intervalDisplay;
75 | }
76 |
77 | const renderHover = (name: string, time: number, x: number, y: number): void => {
78 | if(!name) setHover({active:false});
79 | else setHover({name, time, x: x + 52, y: y + 96, active: true});
80 | }
81 |
82 | const renderIntervals = (): React.ReactElement[] => {
83 | const intervals = [];
84 | let count = 0, intervalIndex = hours.startIndex;
85 | while(count < hours.jobs.length) {
86 | if(intervalIndex === hours.jobs.length) intervalIndex = 0;
87 | const today: Date = new Date(dayStart.getTime());
88 | today.setHours(intervalIndex * 2);
89 | intervals.push();
90 | intervalIndex++;
91 | count++;
92 | }
93 | return intervals;
94 | }
95 | useEffect(() => {
96 | const fetchCronJobs = async () => {
97 | try {
98 | const result = await window.electronAPI.fetchCronJobs();
99 | const generateMockJobs = (n: number): void => {
100 | for(let i = 0; i < n; i++) {
101 | const mockJob = {...result[1]};
102 | const now = new Date();
103 | now.setHours(now.getHours() - (now.getHours() % 2));
104 | now.setMinutes(0);
105 | now.setSeconds(0);
106 | mockJob.cronjob_name = `mockJob${i}`;
107 | mockJob.kube_cronjob_next_schedule_time = now.getTime()/1000;
108 | mockJob.kube_cronjob_next_schedule_time += Math.random() * 100000;
109 | result.push(mockJob);
110 | }
111 | }
112 | // generateMockJobs(50);
113 | setCronJobs(result);
114 | } catch (e) { console.log(e)}
115 | }
116 | fetchCronJobs();
117 | },[]);
118 |
119 | console.log(cronjobs);
120 |
121 | useEffect(() => {
122 | const binUpcomingJobs = (): void => {
123 | const newHours: HoursObject = {startIndex:0, jobs: [[],[],[],[],[],[],[],[],[],[],[],[]]};
124 | const today = new Date();
125 |
126 | newHours.startIndex = Math.floor(today.getHours()/2);
127 | const colors = {"lightblue": false,"lightgreen": false,"lightcoral": false,"lightseagreen": false,"lightSalmon": false, "lemonChiffon": false, "paleturquoise": false,"lightpink": false, "lightgoldenrodyellow": false, "lightskyblue": false, "lightsteelblue": false, "mediumaquamarine": false, "mediumorchid": false, "mediumpurple": false, "olivedrab": false, "palevioletred": false, "peachpuff": false};
128 | cronjobs.forEach((cronjob: ScheduleCronJob): void => {
129 | const nextScheduledTime = cronjob.kube_cronjob_next_schedule_time * 1000;
130 | if(nextScheduledTime >= dayStart.getTime()) {
131 | const getTimeBin = (date: Date): number => {
132 | const hour = date.getHours();
133 | let shift = 0;
134 | if(hour % 2 === 1) {
135 | shift = 1;
136 | scheduledJob.shifted = true;
137 | }
138 | return Math.floor(hour / 2) + shift;
139 | }
140 | const assignColor = () => {
141 | for(const color in colors) {
142 | if(colors[color] === false) {
143 | colors[color] = true;
144 | return color;
145 | }
146 | }
147 | }
148 | const scheduledJob: ScheduleJobObject = {
149 | name: cronjob.cronjob_name,
150 | time: new Date(nextScheduledTime),
151 | color: assignColor()
152 | };
153 | if(cronjob.cronjob_name === hoveredCronjob) scheduledJob.hovered = true;
154 | if(hoveredCronjob && hoveredCronjob !== cronjob.cronjob_name) scheduledJob.opacity = 0.2;
155 |
156 | let timeBin = getTimeBin(scheduledJob.time);
157 | const dayDifference = scheduledJob.time.getDate() - today.getDate();
158 | if((timeBin < newHours.startIndex && dayDifference === 1) || (timeBin >= newHours.startIndex && dayDifference === 0)) {
159 | if(timeBin === 12) timeBin = 0;
160 | newHours.jobs[timeBin].push(scheduledJob);
161 | }
162 | }
163 | });
164 | setHours(newHours);
165 | }
166 | binUpcomingJobs();
167 | }, [cronjobs, hoveredCronjob]);
168 |
169 | return (
170 |
171 |
172 |
173 |
cluster: eks-cluster-01
174 |
namespace: default
175 |
176 |
177 |
PROTEUS
178 |
179 |
180 |
181 |
{getDayOfWeek(dayStart)}, {getMonth(dayStart)} {dayStart.getDate()}
182 |
183 | {renderIntervals()}
184 |
185 |
{createIntervalDisplay()}
186 |
187 |
188 |
189 |
190 | {cronjobs.filter(cronjob => cronjob.kube_cronjob_spec_suspend === false).sort((cronjob1, cronjob2) => sortingFunction(cronjob1, cronjob2)).concat(...cronjobs.filter(cronjob => cronjob.kube_cronjob_spec_suspend === true)).map((cronjob: ScheduleCronJob): React.ReactElement => {
191 | return ;
192 | })}
193 |
194 |
195 | {hover.active &&
}
196 |
197 | )
198 | }
199 |
200 | module.exports = { Home };
201 |
--------------------------------------------------------------------------------
/src/Components/Home/HomeListJob.tsx:
--------------------------------------------------------------------------------
1 | import {useContext, useRef} from 'react';
2 | import { ThemeContext } from '../../ThemeContext';
3 | import { HomeListJobProps } from '../../types';
4 |
5 |
6 | export const HomeListJob = ({name, isHovered, createdDate, interval, node, isActive, isSuspended, nextScheduledDate, setHoveredCronjob}: HomeListJobProps) => {
7 | const ref = useRef(null);
8 | const theme = useContext(ThemeContext);
9 |
10 | const handleHover = (status: string): void => {
11 | if(status === 'enter') {
12 | ref.current.style.border = `1px solid ${theme.bgListJobBorderHover}`;
13 | ref.current.style.filter = theme.bgListJobBrightnessHover;
14 | }
15 | else if(status === 'exit') {
16 | ref.current.style.border = `1px solid ${theme.borderSecondary}`;
17 | ref.current.style.filter = "brightness(100%)";
18 | }
19 | }
20 |
21 | const formatTime = (minutes: number): string => {
22 | let result = ``;
23 | const hours = Math.floor(minutes / 60);
24 | minutes %= 60;
25 | if(minutes === 0) return `${hours} hours`;
26 | if(hours > 0) result += `${hours} hours `;
27 | result += `${minutes} minutes `;
28 | const seconds = minutes % Math.floor(minutes) * 60;
29 | if(seconds > 0) result += `${seconds} seconds`;
30 | return result;
31 | }
32 |
33 | return (
34 | {handleHover('enter'); setHoveredCronjob(name)}} onMouseLeave={() => {handleHover('exit'); setHoveredCronjob()}} className='home-job-list-grid home-job'>
35 |
{name}
36 |
{isSuspended ? 'Suspended' : nextScheduledDate.toLocaleString()}
37 |
{typeof(interval) === "number" ? formatTime(interval) : "-"}
38 |
{createdDate.toLocaleString()}
39 |
{node}
40 |
41 | )
42 | }
43 |
--------------------------------------------------------------------------------
/src/Components/Home/ScheduleInterval.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useRef, useState } from 'react'
2 | import { ScheduleJob } from './ScheduleJob';
3 | import { ScheduleIntervalProps } from '../../types';
4 | import { ThemeContext } from '../../ThemeContext';
5 |
6 | export const ScheduleInterval = (props: ScheduleIntervalProps) => {
7 | const { startTime, jobs, renderHover, boxNumber } = props;
8 | const theme = useContext(ThemeContext);
9 | const ref = useRef(null);
10 |
11 | const calcNudge = (jobDate: Date): number => {
12 | let startDate = new Date(startTime);
13 | if(jobDate.getDate() - startDate.getDate() > 0) {
14 | const hour = jobDate.getHours();
15 | const roundedHour = hour + (hour % 2);
16 | startDate = new Date(jobDate.getTime());
17 | startDate.setHours(roundedHour);
18 | startDate.setMinutes(0);
19 | startDate.setSeconds(0);
20 | }
21 | const nudge = 100*(jobDate.getTime() - startDate.getTime())/7200000;
22 | return nudge;
23 | }
24 |
25 | const calcTimeNudge = () => {
26 | function findScheduleStart(): Date {
27 | const now = new Date();
28 | now.setHours(now.getHours() - (now.getHours() % 2));
29 | now.setMinutes(0);
30 | now.setSeconds(0);
31 | return now;
32 | }
33 | const start = findScheduleStart();
34 | const nudge = 100*(Date.now() - start.getTime())/7200000;
35 | return nudge;
36 | }
37 |
38 | return (
39 |
40 | {jobs.map((job: object): React.ReactElement => {
41 | return
43 | })}
44 | {boxNumber === 0 && }
45 |
46 | )
47 | }
48 |
--------------------------------------------------------------------------------
/src/Components/Home/ScheduleJob.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useState } from 'react';
2 | import { ScheduleJobProps } from '../../types';
3 |
4 | export const ScheduleJob = (props: ScheduleJobProps) => {
5 | const { nudge, color, name, time, renderHover, boxNumber, boxWidth, opacity } = props;
6 | const ref = useRef(null);
7 | return (
8 |
9 |
renderHover(name, time, boxNumber * boxWidth + ref.current.offsetLeft, ref.current.offsetTop)} onMouseLeave={() => renderHover()} className='home-schedule-job' style={{left: `${nudge}%`, backgroundColor: color, opacity}}>
10 |
11 |
12 |
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/src/Components/Home/ScheduleJobHover.tsx:
--------------------------------------------------------------------------------
1 | import {useContext} from 'react';
2 | import { ScheduleJobHoverProps } from '../../types';
3 | import { ThemeContext } from '../../ThemeContext';
4 |
5 | export const ScheduleJobHover = ({name, time, x, y}: ScheduleJobHoverProps) => {
6 | const theme = useContext(ThemeContext);
7 |
8 | const getLocalTime = (date: Date): string => {
9 | const hours = date.getHours();
10 | const minutes = date.getMinutes();
11 | const ampm = hours >= 12 ? "pm" : "am";
12 | const formattedHours = (hours % 12) || 12;
13 | const formattedMinutes = minutes < 10 ? `0${minutes}` : minutes;
14 | return `${formattedHours}:${formattedMinutes} ${ampm}`;
15 | }
16 |
17 | return (
18 |
19 |
{name} {getLocalTime(time)}
20 |
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/src/Components/JobCreator/CronJobForm.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useState, useEffect } from 'react';
2 | import '../../Styles/archive.css';
3 |
4 | export const CronJobForm = (props: object) => {
5 | const { addCommand, deleteCommand, commandList, restartPolicy, setRestartPolicy } = props;
6 | const [ concurrencyPolicy, setConcurrencyPolicy ] = useState('Allow');
7 | const [ supsension, setSuspension ] = useState(false);
8 | const [ imagePullPolicy, setImagePullPolicy ] = useState('Always');
9 |
10 | // reference hooks
11 | const commandRef = useRef(null);
12 | const apiVersionRef = useRef(null);
13 | const cronjobNameRef = useRef(null);
14 | const successfulJobHistoryLimit = useRef(null);
15 | const failedJobHistoryLimit = useRef(null);
16 | const imageNameRef = useRef(null);
17 | const imageURLRef = useRef(null);
18 | const scheduleMinute = useRef(null);
19 | const scheduleHour = useRef(null);
20 | const scheduleDay = useRef(null);
21 | const scheduleMonth = useRef(null);
22 | const scheduleWeekday = useRef(null);
23 |
24 | // handles form submission
25 | const handleSubmit = async (e: Event, kind: string) => {
26 | e.preventDefault();
27 |
28 | const form = `
29 | apiVersion: ${apiVersionRef.current.value}
30 | kind: ${kind}
31 | metadata:
32 | name: ${cronjobNameRef.current.value}
33 | spec:
34 | schedule: "${scheduleMinute.current.value} ${scheduleHour.current.value} ${scheduleDay.current.value} ${scheduleMonth.current.value} ${scheduleWeekday.current.value}"
35 | concurrencyPolicy: ${concurrencyPolicy}
36 | successfulJobsHistoryLimit: ${successfulJobHistoryLimit.current.value}
37 | failedJobsHistoryLimit: ${failedJobHistoryLimit.current.value}
38 | suspend: ${supsension}
39 | jobTemplate:
40 | spec:
41 | template:
42 | spec:
43 | containers:
44 | - name: ${imageNameRef.current.value}
45 | image: ${imageURLRef.current.value}
46 | imagePullPolicy: ${imagePullPolicy}
47 | command: [${commandList}]
48 | restartPolicy: ${restartPolicy}
49 | `
50 |
51 | window.electronAPI.submitJob(form);
52 | alert(`submitted ${kind} form: ${form}`)
53 |
54 | }
55 |
56 | // generates array of input commands and adds as a button
57 | const commandArray = [];
58 | for (let i = 0; i < commandList.length; i++) {
59 | commandArray.push()
60 | }
61 |
62 |
63 | return (
64 |
136 | )
137 | }
--------------------------------------------------------------------------------
/src/Components/JobCreator/JobCreator.tsx:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react';
2 | import '../../Styles/createjobs.css';
3 | import { JobTabs } from './JobTabs';
4 | import { ThemeContext } from '../../ThemeContext';
5 |
6 |
7 |
8 | export const JobCreator = () => {
9 | const theme = useContext(ThemeContext);
10 | return (
11 |
12 |
CREATE JOBS HERE
13 |
14 |
15 |
16 | )
17 | }
--------------------------------------------------------------------------------
/src/Components/JobCreator/JobForm.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react';
2 | import '../../Styles/archive.css';
3 |
4 | export const JobForm = (props: object) => {
5 | const { addCommand, deleteCommand, commandList, restartPolicy, setRestartPolicy } = props;
6 | const commandRef = useRef(null);
7 | const apiVersionRef = useRef(null);
8 | const jobNameRef = useRef(null);
9 | const imageNameRef = useRef(null);
10 | const imageURLRef = useRef(null);
11 | const backoffLimitRef = useRef(null);
12 |
13 | // handles form submission
14 | const handleSubmit = async (e: Event, kind: string) => {
15 | e.preventDefault();
16 | const form = `
17 | apiVersion: ${apiVersionRef.current.value}
18 | kind: ${kind}
19 | metadata:
20 | name: ${jobNameRef.current.value}
21 | spec:
22 | template:
23 | spec:
24 | containers:
25 | - name: ${imageNameRef.current.value}
26 | image: ${imageURLRef.current.value}
27 | command: [${commandList}]
28 | restartPolicy: ${restartPolicy}
29 | backoffLimit: ${backoffLimitRef.current.value}
30 | `
31 | window.electronAPI.submitJob(form);
32 | alert(`submitted ${kind} form: ${form}`)
33 |
34 | };
35 |
36 | // generates array of input commands and adds as a button
37 | const commandArray = [];
38 | for (let i = 0; i < commandList.length; i++) {
39 | commandArray.push()
40 | }
41 |
42 | return (
43 |
84 | )
85 | }
--------------------------------------------------------------------------------
/src/Components/JobCreator/JobTabs.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useContext } from 'react';
2 | import { JobForm } from './JobForm';
3 | import { CronJobForm } from './CronJobForm';
4 | import { ThemeContext } from '../../ThemeContext';
5 |
6 | export const JobTabs = () => {
7 | const [ activeTab, setActiveTab ] = useState('tab1');
8 | const [ commandList, setCommandList ] = useState([]);
9 | const [ restartPolicy, setRestartPolicy ] = useState('');
10 | const theme = useContext(ThemeContext);
11 |
12 |
13 | const addCommand = (input: string): any => {
14 | console.log('input is ', input);
15 | if (!commandList.includes(input)) {
16 | const newCommandList: Array = [...commandList];
17 | newCommandList.push(`"${input}"`);
18 | setCommandList(newCommandList);
19 | } else alert(`command "${input}" already exists`)
20 | };
21 |
22 | const deleteCommand = (index: number): void => {
23 | console.log(`deleting ${commandList[index]} from commandList`);
24 | const newCommandList = [...commandList];
25 | newCommandList.splice(index,1);
26 | setCommandList(newCommandList)
27 | }
28 |
29 | const handleTab1 = () => {
30 | setActiveTab('tab1');
31 | };
32 |
33 | const handleTab2 = () => {
34 | setActiveTab('tab2');
35 | }
36 |
37 |
38 | return (
39 |
40 |
41 | - JOB
42 | - CRONJOB
43 |
44 |
45 | {activeTab === 'tab1' ? : }
46 |
47 |
48 | )
49 | }
50 |
51 |
--------------------------------------------------------------------------------
/src/Components/Nav.tsx:
--------------------------------------------------------------------------------
1 | import { useContext } from 'react';
2 | import { Link } from 'react-router-dom';
3 | import { ThemeContext } from '../ThemeContext';
4 | import '../Styles/sidebar.css';
5 |
6 |
7 | export const Nav = () => {
8 | const theme = useContext(ThemeContext);
9 | return (
10 |
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/src/Components/NoPage.tsx:
--------------------------------------------------------------------------------
1 | export const NoPage = () => {
2 | return 404
;
3 | };
4 |
5 |
--------------------------------------------------------------------------------
/src/Models/ArchivedJobsModel.ts:
--------------------------------------------------------------------------------
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: Date,
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 | });
54 |
55 |
56 | module.exports = mongoose.model('ArchivedJobs', archivedJobSchema);
57 |
58 |
59 |
--------------------------------------------------------------------------------
/src/Models/CronjobModel.ts:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const Schema = mongoose.Schema;
3 |
4 | const CronjobSchema = 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_scheduled_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 | });
50 |
51 |
52 | module.exports = mongoose.model('Cronjob', CronjobSchema);
53 |
54 |
55 |
--------------------------------------------------------------------------------
/src/Styles/archive.css:
--------------------------------------------------------------------------------
1 | .archive-container {
2 | width: 100%;
3 | height: 100%;
4 | display: flex;
5 | flex-direction: column;
6 | gap: 2.5rem;
7 | }
8 |
9 | .archive-title {
10 | display: flex;
11 | margin-left: 5px;
12 | gap: 2rem;
13 | font-size: 1.2rem;
14 | margin-top: 1rem;
15 | /* align-self: flex-start; */
16 | align-items: center;
17 | justify-content: space-between;
18 | color: var(--bg-dark-secondary);
19 | width: 100%;
20 | }
21 |
22 | .archive-title-item {
23 | padding: 0.5rem;
24 | border-radius: 5px;
25 | font-size: 1rem;
26 | }
27 | .data-types {
28 | display: flex;
29 | justify-content: space-around;
30 | gap: 1.5rem;
31 | font-size: 1rem;
32 | }
33 |
34 | .archive-job-list {
35 | display: flex;
36 | flex-direction: column;
37 | gap: 15px;
38 | margin-top: 1rem;
39 | padding: 1rem;
40 | height: 80vh;
41 | border-radius: 5px;
42 | overflow: scroll;
43 | }
44 |
45 | /* Hide scrollbar for Chrome, Safari and Opera */
46 | .archive-job-list::-webkit-scrollbar {
47 | display: none;
48 | }
49 |
50 | /* Hide scrollbar for IE, Edge and Firefox */
51 | .archive-job-list {
52 | -ms-overflow-style: none; /* IE and Edge */
53 | scrollbar-width: none; /* Firefox */
54 | }
55 |
56 | .archive-job-basic {
57 | display: flex;
58 | justify-content: space-around;
59 | align-items: center;
60 | height: 50px;
61 | /* background-color: lightyellow; */
62 | box-shadow: 1px 1px 2px #bebebe,
63 | -1px -1px 2px lightgray;
64 | border-radius: 5px;
65 | display: flex;
66 | padding-left: .5rem;
67 | }
68 |
69 | .archive-job-basic:hover {
70 | filter: brightness(1.2);
71 | cursor: pointer;
72 | }
73 |
74 | .archive-job-hover-container {
75 | /* height: 100px;
76 | width: 200px; */
77 | /* color: white; */
78 | /* text-shadow: 1px 1px 1px black; */
79 | /* border: 1px solid white; */
80 | border-radius: 15px;
81 | position: absolute;
82 | /* top: -120%; */
83 | /* left: -90%; */
84 | display: flex;
85 | flex-direction: column;
86 | padding: .5rem;
87 | z-index: 5;
88 | }
89 |
--------------------------------------------------------------------------------
/src/Styles/createjobs.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | padding: 0;
4 | margin: 0;
5 | }
6 |
7 | /* Job Creator Wrapper */
8 | .job_creator {
9 | /* width: 100vw; */
10 | /* height: 100vh; */
11 | display: flex;
12 | flex-direction: column;
13 | align-items: center;
14 | margin-top: 4rem;
15 | /* justify-content: space-evenly; */
16 | overflow: hidden;
17 | }
18 |
19 |
20 | /* Job Tabs */
21 | .job-tab {
22 | width: 80%;
23 | height: 70%;
24 | min-height: 400px;
25 | max-width: 750px;
26 | background: #93cee7;
27 | margin: 3.5rem auto 1.5rem;
28 | padding: 2rem 1rem;
29 | color: #424849;
30 | border-radius: 2rem;
31 | border-top-left-radius: 0;
32 | @media (max-width: 768px) {
33 | padding: 2rem 0;
34 | }
35 | }
36 |
37 | ul.nav-job-tabs {
38 | width: 60%;
39 | margin: 0 auto 2rem;
40 | display: flex;
41 | align-items: center;
42 | justify-content: space-between;
43 | margin-top: -84px;
44 | margin-left: -16.5px;
45 | border: 1px solid #93cee7;
46 | border-top-right-radius: 2rem;
47 | border-top-left-radius: 2rem;
48 | @media (max-width: 768px) {
49 | width: 90%
50 | }
51 | }
52 |
53 | ul.nav-job-tabs li {
54 | width: 50%;
55 | margin-right: -16.5;
56 | padding: 1rem;
57 | list-style: none;
58 | text-align: center;
59 | cursor: pointer;
60 | transition: all 0.7s;
61 | border-bottom-left-radius: 0;
62 | border-top-left-radius: 2rem;
63 | border-top-right-radius: 2rem;
64 | }
65 |
66 | ul.nav-job-tabs li:nth-child(2) {
67 | border-radius: 0;
68 | border-bottom-right-radius: 0;
69 | border-top-left-radius: 2rem;
70 | border-top-right-radius: 2rem;
71 | }
72 |
73 | ul.nav-job-tabs:hover {
74 | background: rgba(233, 184, 236, 0.168);
75 | }
76 |
77 | ul.nav-job-tabs li.active {
78 | background: #93cee7;
79 | }
80 |
81 | .outlet {
82 | background: 93cee7;
83 | margin: 20px;
84 | display: flex;
85 | flex-direction: column;
86 | justify-content: space-between;
87 | }
88 |
89 | .job-form h1, .cronjob-form h1 {
90 | font-size: 2rem;
91 | text-align: center;
92 | }
93 |
94 |
95 | #job_form {
96 | display: flex;
97 | flex-direction: column;
98 | gap: 20px;
99 | font-size: 1.5rem;
100 | margin-left: 5rem;
101 | margin-top: 4rem;
102 | }
103 |
104 | .cronjob_form {
105 | display: flex;
106 | align-items: center;
107 | justify-content: center;
108 | }
109 |
110 | #cronjob_form {
111 | display: flex;
112 | flex-direction: column;
113 | gap: 10px;
114 | font-size: 1.1rem;
115 | /* margin-left: 4rem; */
116 | /* margin-top: 1rem; */
117 | }
118 |
119 | .jobSubmitButton {
120 | width: 120px;
121 | height: 40px;
122 | margin-top: 1rem;
123 | margin-left: 12rem;
124 | }
125 | /*
126 | form fieldset {
127 | display: flex;
128 | flex-direction: column;
129 | justify-content: space-around;
130 | } */
131 |
132 |
133 |
--------------------------------------------------------------------------------
/src/Styles/home.css:
--------------------------------------------------------------------------------
1 | img {
2 | height: 1px;
3 | width: 1px;
4 | }
5 |
6 | .home-container {
7 | width: 100%;
8 | height: 100%;
9 | display: flex;
10 | flex-direction: column;
11 | align-items: center;
12 | gap: 1.5rem;
13 | margin-inline: 1rem;
14 | }
15 |
16 | .home-title {
17 | display: flex;
18 | margin-left: 5px;
19 | gap: 2rem;
20 | font-size: 1.2rem;
21 | margin-top: 1rem;
22 | /* align-self: flex-start; */
23 | align-items: center;
24 | justify-content: space-between;
25 | color: var(--bg-dark-secondary);
26 | width: 100%;
27 | }
28 |
29 | .home-title-item {
30 | padding: 0.5rem;
31 | border-radius: 5px;
32 | font-size: 1rem;
33 | }
34 |
35 | .home-schedule-container {
36 | display: flex;
37 | flex-direction: column;
38 | gap: 10px;
39 | width: 100%;
40 | }
41 |
42 | .home-schedule {
43 | height: 350px;
44 | border: 1px solid rgba(112, 128, 144, 0.996);
45 | border-radius: 5px;
46 | /* box-shadow: 1px 2px 5px lightslategray; */
47 | box-shadow: inset 6px 6px 10px 0 rgba(0, 0, 0, 0.1),
48 | inset -6px -6px 10px 0 rgba(255, 255, 255, 0.1);
49 | display: flex;
50 | align-items: center;
51 | overflow: hidden;
52 | background-color: var(--calendar-color);
53 | position: relative;
54 | }
55 |
56 | .home-schedule-active-day {
57 | font-weight: 500;
58 | margin-left: 5px;
59 | font-size: 1.2rem;
60 | color: var(--text-secondary);
61 | text-shadow: 0.5px 0.5px 1px grey;
62 | }
63 |
64 | .home-schedule-interval {
65 | width: calc(100%/12);
66 | height: 100%;
67 | /* background-color: lightpink; */
68 | display: flex;
69 | justify-content: center;
70 | /* border: 0.5px solid black; */
71 | /* border-left: 1px dotted rgb(169, 169, 169); */
72 | border-left: 1px solid #a9a9a95e;
73 | display: flex;
74 | flex-direction: column;
75 | gap: 5px;
76 | position: relative;
77 | /* background-image: linear-gradient(rgb(137, 137, 137) 33%, rgba(255,255,255,0) 0%);
78 | background-position: right;
79 | background-size: 2px 10px;
80 | background-repeat: repeat-y; */
81 | }
82 |
83 | .home-schedule-interval:nth-of-type(1) {
84 | border: none;
85 | }
86 | .home-schedule-interval:nth-of-type(12) {
87 | background-image: none;
88 | }
89 |
90 | .home-schedule-interval-display-container {
91 | font-weight: 500;
92 | display: flex;
93 | position: relative;
94 | left: -10px;
95 | }
96 |
97 | .home-schedule-interval-display {
98 | width: calc(100%/12);
99 | color: rgb(92, 92, 92);
100 | color: var(--text-secondary);
101 | text-shadow: 0px 0px 0px rgba(0, 0, 0, 0.226);
102 | /* background-color: aliceblue; */
103 | }
104 |
105 | /* .home-schedule-job-container {
106 | display: relative;
107 | } */
108 |
109 | .home-schedule-job {
110 | background-color: lightgray;
111 | /* border: 1px solid #80808070;; */
112 | border: 1px solid rgba(0, 0, 0, 0.282);
113 | border-radius: 5px;
114 | height: 30px;
115 | width: 30%;
116 | position: relative;
117 | box-shadow: 1px 1px 2px rgba(119, 136, 153, 0.467);
118 | display: flex;
119 | align-items: center;
120 | justify-content: center;
121 | z-index: 2;
122 | }
123 |
124 | .home-schedule-job:hover {
125 | filter: brightness(1.2);
126 | cursor: pointer;
127 | }
128 |
129 | .current-time {
130 | position: absolute;
131 | border-right: 1px solid rgba(0, 0, 0, 0.242);
132 | height: 100%;
133 | /* width: 30%; */
134 | background-color: rgba(125, 125, 125, 0.316);
135 | color: #e22f94;
136 | }
137 |
138 | .home-schedule-job-hover-container {
139 | /* height: 100px; */
140 | /* width: 200px; */
141 | position: absolute;
142 | top: -120%;
143 | left: -90%;
144 | display: flex;
145 | flex-direction: column;
146 | padding: .5rem;
147 | z-index: 5;
148 | border-radius: 5px;
149 | }
150 |
151 | .home-job-list {
152 | display: flex;
153 | flex-direction: column;
154 | /* justify-content: center; */
155 | gap: 10px;
156 | /* padding-block: .5rem; */
157 | margin-top: 1rem;
158 | width: 100%;
159 | max-height: 42vh;
160 | }
161 |
162 | .home-job-list-grid {
163 | display: grid;
164 | grid-template-columns: .5fr .7fr .5fr .7fr 1fr;
165 | align-items: center;
166 | padding-inline: 1rem;
167 | }
168 |
169 | .home-job-list-inner-container {
170 | display: flex;
171 | flex-direction: column;
172 | gap: 10px;
173 | /* border: 2px solid #a9a9a9; */
174 | border-radius: 5px;
175 | /* background-color: var(--bg-light-secondary); */
176 | /* background-color: #232c3a; */
177 | padding: 1rem;
178 | padding-inline: 1rem;
179 | max-height: 42vh;
180 | overflow-y: scroll;
181 | width: 100%;
182 | }
183 |
184 | /* Hide scrollbar for Chrome, Safari and Opera */
185 | .home-job-list-inner-container::-webkit-scrollbar {
186 | display: none;
187 | }
188 |
189 | /* Hide scrollbar for IE, Edge and Firefox */
190 | .home-job-list-inner-container {
191 | -ms-overflow-style: none; /* IE and Edge */
192 | scrollbar-width: none; /* Firefox */
193 | }
194 |
195 |
196 | .home-job-list-grid-header {
197 | position: relative;
198 | left: 8px;
199 | top: 8px;
200 | color: var(--text-secondary);
201 | text-shadow: 0px 0px 0px rgba(0, 0, 0, 0.226);
202 | /* height: 50px; */
203 | font-size: 0.9rem;
204 | }
205 |
206 | .grid-header-item {
207 | display: flex;
208 | align-items: center;
209 | gap: 8px;
210 | width: 100px;
211 | }
212 |
213 | .grid-header-item:hover {
214 | cursor: pointer;
215 | }
216 |
217 | .home-job {
218 | height: 50px;
219 | border: 1px none #172c66;
220 | /* box-shadow: 1px 1px 4px #bebebe, */
221 | /* -1px -1px 4px lightgray; */
222 | border-radius: 3px;
223 | font-size: .9rem;
224 | gap: 10px;
225 | transition: .1s;
226 | padding-left: .5rem;
227 | }
228 |
229 | .home-job:hover {
230 | cursor: pointer;
231 | }
232 |
233 | .arrow-up {
234 | width: 0;
235 | height: 0;
236 | border-left: 5px solid transparent;
237 | border-right: 5px solid transparent;
238 |
239 | border-bottom: 5px solid white;
240 | }
241 |
242 | .arrow-down {
243 | width: 0;
244 | height: 0;
245 | border-left: 5px solid transparent;
246 | border-right: 5px solid transparent;
247 |
248 | border-top: 5px solid white;
249 | }
--------------------------------------------------------------------------------
/src/Styles/sidebar.css:
--------------------------------------------------------------------------------
1 | .sidebar {
2 | height: 100vh;
3 | width: 250px;
4 | position: fixed;
5 | z-index: 5;
6 | top: 0;
7 | left: 0;
8 | background-color: var(--sidebar-color);
9 | padding-top: 90px;
10 | transition: 0.5s ease-in-out;
11 | font-size: 25px;
12 | }
13 |
14 | .sidebar_active {
15 | height: 100vh;
16 | width: 0;
17 | position: fixed;
18 | z-index: 5;
19 | top: 0;
20 | left: 0;
21 | background-color: var(--sidebar-color);
22 | padding-top: 90px;
23 | transition: 0.5s ease-in-out;
24 | font-size: 25px;
25 | }
26 |
27 | .sidebar_active .sidebar_links {
28 | display: flex;
29 | flex-direction: column;
30 | justify-content: flex-start;
31 | margin-left: -1000px;
32 | position: fixed;
33 | transition: 0.5s ease-in-out;
34 | }
35 |
36 | .sidebar_links {
37 | display: flex;
38 | flex-direction: column;
39 | justify-content: flex-start;
40 | margin-left: -750px;
41 | position: fixed;
42 | transition: 0.5s ease-in-out;
43 | color: green;
44 | }
45 |
46 | .sidebar_active .sidebar_links li {
47 | padding: 10px;
48 | --c: #c110bb10;
49 | --h: 1.7em ;
50 | line-height: var(--h);
51 | color: #0000;
52 | overflow: hidden;
53 | text-shadow:
54 | 0 calc(-1 * var(--_t, 0em)) var(--c),
55 | 0 calc(var(--h) - var(--_t, 0em)) #fff;
56 | background:
57 | linear-gradient(var(--c) 0 0) no-repeat
58 | calc(200% - var(--_p, 0%)) 100% / 200% var(--_p, .08em);
59 | transition: .3s var(--_s, 0s), background-position .3s calc(.3s - var(--_s, 0s));
60 | }
61 |
62 | .sidebar .sidebar_links li {
63 | padding: 10px;
64 | --c: #c110bb10;
65 | --h: 1.7em ;
66 | line-height: var(--h);
67 | color: #0000;
68 | overflow: hidden;
69 | text-shadow:
70 | 0 calc(-1 * var(--_t, 0em)) var(--c),
71 | 0 calc(var(--h) - var(--_t, 0em)) #fff;
72 | background:
73 | linear-gradient(var(--c) 0 0) no-repeat
74 | calc(200% - var(--_p, 0%)) 100% / 200% var(--_p, .08em);
75 | transition: .3s var(--_s, 0s), background-position .3s calc(.3s - var(--_s, 0s));
76 | }
77 |
78 | .sidebar .sidebar_links li:hover {
79 | --_t: var(--h);
80 | --_p: 100%;
81 | --_s: .3s
82 | }
83 |
84 | /* .sidebar_active .sidebar_links li:hover {
85 | padding: 10px;
86 | color: aqua;
87 | background-color: white;
88 | } */
89 |
90 | .sidebar_active .closebtn {
91 | position: fixed;
92 | margin-left: 15px;
93 | margin-top: -63px;
94 | transition: 0.5s ease-in-out;
95 | }
96 |
97 | .sidebar .closebtn {
98 | position: fixed;
99 | margin-left: 215px;
100 | margin-top: -63px;
101 | transition: 0.5s ease-in-out;
102 | }
103 |
104 | /* .sidebar .sidebar_links li, .sidebar_active .sidebar_links li {
105 | font-size: 25px;
106 | position: relative;
107 | } */
108 |
109 |
110 | /* .sidebar {
111 | height: 100%;
112 | width: 250px;
113 | position: fixed;
114 | z-index: 5;
115 | top: 0;
116 | left: 0;
117 | background-color: rgb(223, 127, 185);
118 | overflow-x: hidden;
119 | padding-top: 60px;
120 | transition: 0.5s;
121 | } */
122 |
123 | /* .sidebar li {
124 | padding: 8px 8px 8px 32px;
125 | text-decoration: none;
126 | font-size: 25px;
127 | color: #818181;
128 | display: block;
129 | transition: 0.3s;
130 | }
131 |
132 | .sidebar li:hover {
133 | color: #f1f1f1;
134 | }
135 |
136 | .sidebar .closebtn {
137 | position: absolute;
138 | top: 0;
139 | right: 25px;
140 | font-size: 36px;
141 | margin-left: 50px;
142 | }
143 |
144 | #app {
145 | transition: margin-left .5s;
146 | padding: 20px;
147 | } */
148 |
149 | @media screen and (max-height: 450px) {
150 | .sidebar { padding-top: 15px; }
151 | .sidebar a { font-size: 18px; }
152 | }
153 |
154 |
155 | /* .sidebar li a {
156 | font-size: 18px;
157 | text-decoration: none;
158 | padding: 10px;
159 | margin-left: 10%;
160 | margin-top: 10px;
161 | display: block;
162 | color: #fff;
163 | } */
164 | /*
165 | .hamburger {
166 | border: none;
167 | outline: 0;
168 | width: 20%;
169 | height: 100vh;
170 | position: absolute;
171 | right: -25px;
172 | background-color: rgb(57, 103, 131);
173 | }
174 |
175 | .hamburger:hover {
176 | cursor: pointer;
177 | }
178 |
179 | .hamburger:after, .hamburger:before, .hamburger div {
180 | background-color: rgb(55, 192, 43);
181 | height: 7px;
182 | margin-right: 7px 0;
183 | border-radius: 3px;
184 | content: "";
185 | display: block;
186 | transition: all 300ms ease-in-out;
187 | }
188 |
189 | .sidebar.active .hamburger:before {
190 | transform: translateY(12px) rotate(-90deg);
191 | }
192 |
193 | .sidebar.active .hamburger::after {
194 | transform: translateY(-12px) rotate(90deg);
195 | }
196 |
197 | .sidebar.active .hamburger div {
198 | transform: scale(100);
199 | } */
200 |
201 | .nav-bar {
202 | position: fixed;
203 | width: 350px;
204 | height: 75px;
205 | top: -60px;
206 | left: 40%;
207 | font-size: 1rem;
208 | border-radius: 5px;
209 | transition: .5s;
210 | display: flex;
211 | align-items: center;
212 | justify-content: center;
213 | gap: 20px;
214 | }
215 |
216 | .nav-bar:hover {
217 | top: -15px;
218 | }
219 |
220 | .nav-bar-icon {
221 | height: 50%;
222 | width: 20%;
223 | /* background-color: saddlebrown; */
224 | }
225 |
--------------------------------------------------------------------------------
/src/ThemeContext.ts:
--------------------------------------------------------------------------------
1 | import { createContext } from "react";
2 |
3 | export const themes = {
4 | light: {
5 | bgPrimary: "rgb(250, 250, 250)",
6 | bgSecondary: "#a5c1e6",
7 | bgListJob: "rgb(250, 250, 250)",
8 | bgListJobBorderHover: "gold",
9 | bgListJobBrightnessHover: "brightness(100%)",
10 | textPrimary: "rgb(50, 50, 50)",
11 | textSecondary: "#eaeaea",
12 | calendar: "#a5c1e6",
13 | calendarBorder: "rgb(100, 100, 100)",
14 | sidebar: "pink",
15 | logo: "#df7fb9",
16 | borderPrimary: "rgb(100, 100, 100)",
17 | borderSecondary: "#e22f94"
18 | },
19 | dark: {
20 | bgPrimary: "#223855",
21 | bgSecondary: "#222934",
22 | bgListJob: "#223855",
23 | bgListJobBorderHover: "rgb(180, 150, 0)",
24 | bgListJobBrightnessHover: "brightness(120%)",
25 | textPrimary: "#eaeaea",
26 | textSecondary: "rgb(50, 50, 50)",
27 | calendar: "#222934",
28 | calendarBorder: "#a9a9a95e",
29 | sidebar: "#df7fb9",
30 | logo: "#df7fb9",
31 | borderPrimary: "#a9a9a9",
32 | borderSecondary: "#eaa6cd"
33 | },
34 | };
35 |
36 | export const ThemeContext = createContext(
37 | themes.dark // default value
38 | );
--------------------------------------------------------------------------------
/src/images/PROTEUS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/proteus/c0e6d79c68e6f9280487762c97ff6d27f1deec2c/src/images/PROTEUS.png
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Cabin:ital,wght@0,400;0,500;0,600;0,700;1,400;1,500;1,600;1,700&family=Kenia&family=Offside&family=Oswald:wght@200;300;400;500;600;700&family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
2 | @import url('https://fonts.googleapis.com/css2?family=Dosis:wght@200;600;700;800&display=swap');
3 | @import url('https://fonts.googleapis.com/css2?family=Days+One&display=swap');
4 |
5 | /* body {
6 | /* font-family: "Poppins", "Avenir", "Helvetica", "Arial", sans-serif; */
7 | /* font-family:system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; */
8 | /* font-family:'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif; */
9 | /* color: darkslategray;
10 | font-size: small; */
11 |
12 | body {
13 | font-family: "Poppins", "Avenir", "Helvetica", "Arial", sans-serif;
14 | color: darkslategray;
15 | font-size: small;
16 | margin: 0rem;
17 | max-width: 38rem;
18 | padding: 1rem;
19 | padding-inline: 2rem;
20 | background-color: var(--bg-light-primary);
21 | }
22 |
23 | :root {
24 | --bg-light-primary: rgb(42, 34, 85);
25 | --bg-light-secondary: #ffffff;
26 | --bg-dark-primary: #98bef1;
27 | --bg-dark-secondary: #a5c1e6;
28 | --text-primary: #eaeaea;
29 | --text-secondary: #eaeaea;
30 | --sidebar-color: #df7fb9;
31 | --calendar-color: #222934;
32 |
33 | --bg-light-primaryrgb: 173, 216, 230;
34 | --bg-dark-secondaryrgb: 222, 93, 130;
35 | --bg-dark-primaryrgb: 112, 128, 144;
36 | --bg-light-secondaryrgb: 255, 255, 224;
37 | --sidebar-color-rgb: 223, 127, 185
38 | }
39 |
40 | main {
41 | width: calc(100vw - 4.5rem);
42 | }
43 |
44 | .proteus-title {
45 | font-family: 'Days One', sans-serif;
46 | font-weight: 800;
47 | font-size: 30px;
48 | color: white;
49 | /* text-shadow: 2px 2px 8px #DE5D82; */
50 | margin-inline: 1rem;
51 | text-shadow: 0 0 0.2em var(--bg-dark-secondary), 0 0 0.2em var(--bg-dark-secondary),
52 | 0 0 0.2em var(--bg-dark-secondary);
53 | }
54 |
55 | a {
56 | text-decoration: none;
57 | font-family: 'Dosis', sans-serif;
58 | font-weight: 600;
59 | color: darkslategray;
60 | margin-left: 800px;
61 | }
62 |
63 | li {
64 | list-style-type: none;
65 | }
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PROTEUS
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { app, BrowserWindow, protocol, Menu, BrowserView, getCurrentWindow, ipcMain, dialog } from 'electron';
2 | import spawn from 'child_process';
3 | import mongoose from 'mongoose';
4 | require('dotenv').config();
5 |
6 | const ArchivedJobs = require('./Models/ArchivedJobsModel');
7 | const CronjobModel = require('./Models/CronjobModel');
8 |
9 | mongoose.connect(process.env.DB_URI, {useNewUrlParser: true, useUnifiedTopology: true});
10 | mongoose.connection.once('open', () => {
11 | console.log('MongoDB database connection established successfully');
12 | });
13 |
14 | // This allows TypeScript to pick up the magic constants that's auto-generated by Forge's Webpack
15 | // plugin that tells the Electron app where to look for the Webpack-bundled app code (depending on
16 | // whether you're running in development or production).
17 | declare const MAIN_WINDOW_WEBPACK_ENTRY: string;
18 | declare const MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY: string;
19 |
20 | // NEED TO CHECK THE TYPE OF RETURNED PROPERTY VALUES
21 | const fetchAllJobs = async () => await ArchivedJobs.find().lean();
22 |
23 | const fetchCronJobs = async () => await CronjobModel.find().lean();
24 |
25 | const submitJob = async (e, text) => {
26 | console.log('submitting job');
27 | console.log('text: ', text);
28 | spawn.execSync(`echo '${text}' > test.yaml`);
29 | spawn.execSync(`kubectl apply -f test.yaml`);
30 |
31 | }
32 | // Handle creating/removing shortcuts on Windows when installing/uninstalling.
33 | if (require('electron-squirrel-startup')) {
34 | app.quit();
35 | }
36 |
37 | const createWindow = (): void => {
38 | // Create the browser window.
39 | const mainWindow = new BrowserWindow({
40 | height: 600,
41 | width: 800,
42 | webPreferences: {
43 | preload: MAIN_WINDOW_PRELOAD_WEBPACK_ENTRY,
44 | },
45 | });
46 |
47 | // and load the index.html of the app.
48 | mainWindow.loadURL(MAIN_WINDOW_WEBPACK_ENTRY);
49 |
50 | };
51 |
52 | // This method will be called when Electron has finished
53 | // initialization and is ready to create browser windows.
54 | // Some APIs can only be used after this event occurs.
55 | app.on('ready', () => {
56 | ipcMain.handle('fetchCronJobs', fetchCronJobs);
57 | ipcMain.handle('submitJob', submitJob);
58 | ipcMain.handle('fetchAllJobs', fetchAllJobs);
59 | loadApp();
60 | createWindow()
61 | });
62 |
63 | // Quit when all windows are closed, except on macOS. There, it's common
64 | // for applications and their menu bar to stay active until the user quits
65 | // explicitly with Cmd + Q.
66 | app.on('window-all-closed', () => {
67 | if (process.platform !== 'darwin') {
68 | app.quit();
69 | }
70 | });
71 |
72 | app.on('activate', () => {
73 | // On OS X it's common to re-create a window in the app when the
74 | // dock icon is clicked and there are no other windows open.
75 | if (BrowserWindow.getAllWindows().length === 0) {
76 | createWindow();
77 | }
78 | });
79 |
80 |
81 |
82 | // In this file you can include the rest of your app's specific main process
83 | // code. You can also put them in separate files and import them here.
84 |
85 | const loadApp = () => {
86 | let template = [
87 | { label: "PROTEUS",
88 | submenu: [
89 | { label: 'About Proteus', click() { require('electron').shell.openExternal(`https://www.proteus-osp.app`); }},
90 | { label: 'Get Proteus image', click() { require('electron').shell.openExternal(`https://hub.docker.com/r/ospproteus/proteus-image`); }},
91 | { type: 'separator' },
92 | { role: 'quit' },
93 | ]
94 | }, {
95 | label: 'File',
96 | submenu: [
97 | { label: 'Check for updates', click() { require('electron').shell.openExternal(`https://github.com/oslabs-beta/proteus`); }},
98 | { type: 'separator' },
99 | { label: 'Close window', click() {app.quit()} }
100 | ]
101 | }, {
102 | label: "Edit",
103 | submenu: [
104 | { role: 'reload' },
105 | { type: 'separator' },
106 | { role: 'toggleDevTools' }
107 | ]
108 | }, {
109 | label: "View",
110 | submenu: [
111 | { role: 'minimize' },
112 | { role: 'togglefullscreen' },
113 | { type: 'separator' },
114 | { role: 'zoomIn' },
115 | { role: 'zoomOut' },
116 | { role: 'resetZoom' }
117 | ]
118 | },
119 | ]
120 | const menu = Menu.buildFromTemplate(template)
121 | Menu.setApplicationMenu(menu)
122 | }
--------------------------------------------------------------------------------
/src/preload.ts:
--------------------------------------------------------------------------------
1 | // See the Electron documentation for details on how to use preload scripts:
2 | // https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts
3 | import { contextBridge, ipcRenderer } from 'electron';
4 |
5 | contextBridge.exposeInMainWorld('electronAPI', {
6 | fetchCronJobs: () => ipcRenderer.invoke('fetchCronJobs'),
7 | submitJob: (text: string) => ipcRenderer.invoke('submitJob', text),
8 | fetchAllJobs: () => ipcRenderer.invoke('fetchAllJobs'),
9 | })
--------------------------------------------------------------------------------
/src/renderer.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file will automatically be loaded by webpack and run in the "renderer" context.
3 | * To learn more about the differences between the "main" and the "renderer" context in
4 | * Electron, visit:
5 | *
6 | * https://electronjs.org/docs/latest/tutorial/process-model
7 | *
8 | * By default, Node.js integration in this file is disabled. When enabling Node.js integration
9 | * in a renderer process, please be aware of potential security implications. You can read
10 | * more about security risks here:
11 | *
12 | * https://electronjs.org/docs/tutorial/security
13 | *
14 | * To enable Node.js integration in this file, open up `main.js` and enable the `nodeIntegration`
15 | * flag:
16 | *
17 | * ```
18 | * // Create the browser window.
19 | * mainWindow = new BrowserWindow({
20 | * width: 800,
21 | * height: 600,
22 | * webPreferences: {
23 | * nodeIntegration: true
24 | * }
25 | * });
26 | * ```
27 | */
28 |
29 | import './index.css';
30 | import './App';
31 |
32 | // console.log('👋 This message is being logged by "renderer.js", included via webpack');
33 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | export type ScheduleIntervalProps = {
2 | startTime: number,
3 | jobs: object[],
4 | boxNumber: number,
5 | renderHover(): React.ReactElement
6 | }
7 |
8 | export type ScheduleJobProps = {
9 | nudge: number,
10 | color: string,
11 | name: string,
12 | time: Date,
13 | renderHover(): React.ReactElement
14 | }
15 |
16 | export type ScheduleJobObject = {
17 | name: string,
18 | time: Date,
19 | color: string,
20 | hovered?: boolean,
21 | shifted?: boolean,
22 | opacity?: number
23 | }
24 |
25 | export type HomeHoverState = {
26 | name?: string,
27 | time?: Date,
28 | x?: number,
29 | y?: number,
30 | active?: boolean
31 | }
32 |
33 | export type ScheduleJobHoverProps = {
34 | name: string,
35 | time: Date,
36 | x: number,
37 | y: number
38 | }
39 |
40 | export type HoursObject = {
41 | startIndex: number,
42 | jobs: [ScheduleJobObject[],ScheduleJobObject[],ScheduleJobObject[],ScheduleJobObject[],ScheduleJobObject[],ScheduleJobObject[],ScheduleJobObject[],ScheduleJobObject[],ScheduleJobObject[],ScheduleJobObject[],ScheduleJobObject[],ScheduleJobObject[]]
43 | }
44 |
45 | export type ScheduleCronJob = {
46 | cronjob_interval: string,
47 | cronjob_name: string,
48 | cronjob_node: string,
49 | kube_cronjob_created: number,
50 | kube_cronjob_next_schedule_time: number,
51 | kube_cronjob_spec_failed_job_history_limit: string,
52 | kube_cronjob_spec_successfyl_job_history_limit: string,
53 | kube_cronjob_spec_suspend: boolean,
54 | kube_cronjob_status_active: boolean,
55 | kube_cronjob_status_last_schedule_time: Date,
56 | kube_cronjob_status_last_successful_time: Date
57 | }
58 |
59 | export type HomeListJobProps = {
60 | name: string,
61 | isHovered: boolean,
62 | createdDate: Date,
63 | interval: string,
64 | node: string,
65 | isActive: boolean,
66 | isSuspended: boolean,
67 | nextScheduledDate: Date,
68 | setHoveredCronjob: React.Dispatch>
69 | }
70 |
71 |
72 | export type JobMetrics = {
73 | kube_job_annotations?: string,
74 | kube_job_complete?: boolean,
75 | kube_job_created?: string,
76 | kube_job_info?: string,
77 | kube_job_labels?: string,
78 | kube_job_owner?: string,
79 | kube_job_name?: string,
80 | kube_job_spec_completions?: string,
81 | kube_job_spec_parallelism?: string,
82 | kube_job_status_active?: string,
83 | kube_job_status_completion_time?: string,
84 | kube_job_status_failed?: string,
85 | kube_job_status_start_time?: string,
86 | kube_job_status_succeeded?: string,
87 | }
88 |
89 | export type PastJobMetrics = {
90 | kube_job_namespace?: string,
91 | kube_job_name?: string,
92 | kube_job_runtime?: string,
93 | kube_job_status?: string,
94 | kube_job_details?: string
95 | }
96 |
97 | export type ArchivedJobMetrics = {
98 | metrics: ArchivedMetricsObj,
99 | renderHover: (name: string, runtime: string | number, node: string, instance: string, cronjob_name: string, x: number, y: number) => void
100 | }
101 |
102 | export type ArchivedMetricsObj = {
103 | instance: string,
104 | kube_job_complete: boolean,
105 | kube_job_created: Date,
106 | kube_job_namespace: string,
107 | kube_job_runtime: number,
108 | kube_job_status_active: boolean,
109 | kube_job_status_completion_time: Date,
110 | kube_job_status_failed: boolean,
111 | kube_job_status_start_time: Date,
112 | kube_job_status_succeeded: boolean,
113 | kube_name: string,
114 | node: string,
115 | cronjob_name: string
116 | }
117 |
118 | export type ArchivedJobs = {
119 | kube_name?: string,
120 | kube_job_namespace?: string,
121 | kube_job_status_start_time?: string,
122 | completion_time?: Date,
123 | kube_job_status_succeeded?: string
124 | }
125 |
126 | export type ArchiveJobHoverProps = {
127 | name: string,
128 | runtime: number | string,
129 | node: string,
130 | instance: string,
131 | cronjob_name: string,
132 | x: number,
133 | y: number
134 | }
135 |
136 | export type DropdownProps = {
137 | onFilterChange: (value: string) => void
138 | }
--------------------------------------------------------------------------------
/test.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: batch/v1
2 | kind: Job
3 | metadata:
4 | name: db-bridge
5 | spec:
6 | template:
7 | spec:
8 | containers:
9 | - name: proteus-image
10 | image: ospproteus/proteus-image
11 | env:
12 | - name: DB_URI
13 | value: "mongodb+srv://proteus:codesmith-proteus@cluster-prometheus-metr.xs8xprx.mongodb.net/?retryWrites=true&w=majority"
14 | - name: SERVICE_IP
15 | value: "10.100.151.71:80"
16 | command: ["npm", "start"]
17 | restartPolicy: OnFailure
18 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES6",
4 | "allowJs": true,
5 | "module": "commonjs",
6 | "skipLibCheck": true,
7 | "esModuleInterop": true,
8 | "noImplicitAny": true,
9 | "sourceMap": true,
10 | "baseUrl": ".",
11 | "outDir": "dist",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "jsx": "react-jsx",
15 | "paths": {
16 | "*": ["node_modules/*"]
17 | }
18 | },
19 | "include": ["src/**/*"]
20 | }
21 |
--------------------------------------------------------------------------------
/webpack.main.config.ts:
--------------------------------------------------------------------------------
1 | import type { Configuration } from 'webpack';
2 |
3 | import { rules } from './webpack.rules';
4 |
5 | export const mainConfig: Configuration = {
6 | /**
7 | * This is the main entry point for your application, it's the first file
8 | * that runs in the main process.
9 | */
10 | entry: './src/index.ts',
11 | // Put your normal webpack config below here
12 | module: {
13 | rules,
14 | },
15 | resolve: {
16 | extensions: ['.js', '.ts', '.jsx', '.tsx', '.css', '.json'],
17 | },
18 | };
19 |
--------------------------------------------------------------------------------
/webpack.plugins.ts:
--------------------------------------------------------------------------------
1 | import type IForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
2 |
3 | // eslint-disable-next-line @typescript-eslint/no-var-requires
4 | const ForkTsCheckerWebpackPlugin: typeof IForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
5 |
6 | export const plugins = [
7 | new ForkTsCheckerWebpackPlugin({
8 | logger: 'webpack-infrastructure',
9 | }),
10 | ];
11 |
--------------------------------------------------------------------------------
/webpack.renderer.config.ts:
--------------------------------------------------------------------------------
1 | import type { Configuration } from 'webpack';
2 |
3 | import { rules } from './webpack.rules';
4 | import { plugins } from './webpack.plugins';
5 |
6 | rules.push({
7 | test: /\.css$/,
8 | use: [{ loader: 'style-loader' }, { loader: 'css-loader' }],
9 | });
10 |
11 | export const rendererConfig: Configuration = {
12 | module: {
13 | rules,
14 | },
15 | plugins,
16 | resolve: {
17 | extensions: ['.js', '.ts', '.jsx', '.tsx', '.css'],
18 | },
19 | };
20 |
--------------------------------------------------------------------------------
/webpack.rules.ts:
--------------------------------------------------------------------------------
1 | import type { ModuleOptions } from 'webpack';
2 | import path from 'path';
3 |
4 | export const rules: Required['rules'] = [
5 | // Add support for native node modules
6 | {
7 | // We're specifying native_modules in the test because the asset relocator loader generates a
8 | // "fake" .node file which is really a cjs file.
9 | test: /native_modules[/\\].+\.node$/,
10 | use: 'node-loader',
11 | },
12 | {
13 | test: /[/\\]node_modules[/\\].+\.(m?js|node)$/,
14 | parser: { amd: false },
15 | use: {
16 | loader: '@vercel/webpack-asset-relocator-loader',
17 | options: {
18 | outputAssetBase: 'native_modules',
19 | },
20 | },
21 | },
22 | {
23 | test: /\.tsx?$/,
24 | exclude: /(node_modules|\.webpack)/,
25 | use: {
26 | loader: 'ts-loader',
27 | options: {
28 | transpileOnly: true,
29 | },
30 | },
31 | },
32 | {
33 | test: /\.(svg|png|jpg|gif)$/,
34 | include: [
35 | path.resolve(__dirname, "src/images")
36 | ],
37 | type: "asset/resource"
38 | }
39 | ];
40 |
--------------------------------------------------------------------------------
/website/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Proteus
8 |
9 |
10 |
11 | Error: Page not found
12 |
13 |
14 |
--------------------------------------------------------------------------------
/website/README.md:
--------------------------------------------------------------------------------
1 | # proteus-landing
2 | landing page for proteus, an open source kubernetes job and cronjob monitoring tool
3 |
4 | run 'npm run build' to build package prior production
5 |
--------------------------------------------------------------------------------
/website/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Proteus
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/website/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "proteus-landing",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "preview": "vite preview"
10 | },
11 | "dependencies": {
12 | "@fortawesome/fontawesome-free-regular": "^5.0.13",
13 | "@fortawesome/fontawesome-free-solid": "^5.0.13",
14 | "@fortawesome/fontawesome-svg-core": "^6.3.0",
15 | "@fortawesome/free-brands-svg-icons": "^6.3.0",
16 | "@fortawesome/free-regular-svg-icons": "^6.3.0",
17 | "@fortawesome/free-solid-svg-icons": "^6.3.0",
18 | "@fortawesome/react-fontawesome": "^0.2.0",
19 | "@heroicons/react": "^2.0.16",
20 | "autoprefixer": "^10.4.13",
21 | "postcss": "^8.4.21",
22 | "react": "^18.2.0",
23 | "react-dom": "^18.2.0",
24 | "react-router-dom": "^6.8.1",
25 | "react-router-hash-link": "^2.4.3",
26 | "tailwindcss": "^3.2.7"
27 | },
28 | "devDependencies": {
29 | "@types/react": "^18.0.27",
30 | "@types/react-dom": "^18.0.10",
31 | "@vitejs/plugin-react": "^3.1.0",
32 | "vite": "^4.1.0"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/website/postcss.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/website/public/vite.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/src/App.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/proteus/c0e6d79c68e6f9280487762c97ff6d27f1deec2c/website/src/App.css
--------------------------------------------------------------------------------
/website/src/App.jsx:
--------------------------------------------------------------------------------
1 | import { Routes, Route } from 'react-router-dom';
2 | import './App.css'
3 | import { Navbar } from './components/Navbar.jsx'
4 | import { Home } from './components/Home.jsx';
5 | import { Features } from './components/Features.jsx';
6 | import { Team } from './components/Team.jsx';
7 | import { Contact } from './components/Contact.jsx';
8 | import { GettingStarted } from './components/GettingStarted.jsx';
9 |
10 | function App() {
11 |
12 |
13 | return (
14 |
15 |
18 |
19 | } />
20 | } />
21 | } />
22 | } />
23 | } />
24 |
25 |
26 | )
27 | }
28 |
29 | export default App
30 |
31 |
32 |
--------------------------------------------------------------------------------
/website/src/assets/Archive.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/proteus/c0e6d79c68e6f9280487762c97ff6d27f1deec2c/website/src/assets/Archive.gif
--------------------------------------------------------------------------------
/website/src/assets/Create.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/proteus/c0e6d79c68e6f9280487762c97ff6d27f1deec2c/website/src/assets/Create.gif
--------------------------------------------------------------------------------
/website/src/assets/Home.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/proteus/c0e6d79c68e6f9280487762c97ff6d27f1deec2c/website/src/assets/Home.gif
--------------------------------------------------------------------------------
/website/src/assets/PROTEUS.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/proteus/c0e6d79c68e6f9280487762c97ff6d27f1deec2c/website/src/assets/PROTEUS.png
--------------------------------------------------------------------------------
/website/src/assets/PROTEUS2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/proteus/c0e6d79c68e6f9280487762c97ff6d27f1deec2c/website/src/assets/PROTEUS2.png
--------------------------------------------------------------------------------
/website/src/assets/PROTEUS3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/proteus/c0e6d79c68e6f9280487762c97ff6d27f1deec2c/website/src/assets/PROTEUS3.png
--------------------------------------------------------------------------------
/website/src/assets/bianca2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/proteus/c0e6d79c68e6f9280487762c97ff6d27f1deec2c/website/src/assets/bianca2.png
--------------------------------------------------------------------------------
/website/src/assets/eddy2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/proteus/c0e6d79c68e6f9280487762c97ff6d27f1deec2c/website/src/assets/eddy2.png
--------------------------------------------------------------------------------
/website/src/assets/mark2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/proteus/c0e6d79c68e6f9280487762c97ff6d27f1deec2c/website/src/assets/mark2.png
--------------------------------------------------------------------------------
/website/src/assets/matt2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/proteus/c0e6d79c68e6f9280487762c97ff6d27f1deec2c/website/src/assets/matt2.png
--------------------------------------------------------------------------------
/website/src/assets/proteus.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/proteus/c0e6d79c68e6f9280487762c97ff6d27f1deec2c/website/src/assets/proteus.ico
--------------------------------------------------------------------------------
/website/src/assets/protozoa1.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/proteus/c0e6d79c68e6f9280487762c97ff6d27f1deec2c/website/src/assets/protozoa1.ico
--------------------------------------------------------------------------------
/website/src/assets/protozoa1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/proteus/c0e6d79c68e6f9280487762c97ff6d27f1deec2c/website/src/assets/protozoa1.png
--------------------------------------------------------------------------------
/website/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/src/components/Contact.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3 | import { faMedium, faLinkedinIn } from '@fortawesome/free-brands-svg-icons';
4 |
5 | export const Contact = () => {
6 | const handleClick = () => {
7 | console.log('handleClick in Contact pending functionality');
8 | window.location.href = 'mailto:proteus.osp@gmail.com';
9 | };
10 |
11 | return (
12 |
13 |
14 |
15 |
Contact Us
16 |
Reach out to us with any questions!
17 |
18 |
19 |
20 |
21 |
25 |
26 |
27 | )
28 | }
29 |
30 |
--------------------------------------------------------------------------------
/website/src/components/Features.jsx:
--------------------------------------------------------------------------------
1 | import { React } from 'react';
2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3 | import { faCode, faCalendarPlus } from '@fortawesome/fontawesome-free-solid';
4 | import { faChartGantt, faTimeline } from '@fortawesome/free-solid-svg-icons';
5 |
6 |
7 | export const Features = () => {
8 |
9 | return (
10 |
11 |
12 |
13 |
Features
14 |
Take your Kubernetes experience to the next level.
15 |
16 |
17 |
18 |
19 |
DevOps simiplified
20 |
21 |
22 |
23 |
24 |
25 |
26 |
Track active Jobs
27 |
28 |
29 |
30 |
31 |
32 |
33 |
View previous Jobs, both successful and failed
34 |
35 |
36 |
37 |
38 |
39 |
40 |
Create new Jobs and Cronjobs
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | )
49 | }
--------------------------------------------------------------------------------
/website/src/components/GettingStarted.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 |
4 | export const GettingStarted = () => {
5 |
6 | return (
7 |
8 |
9 |
10 |
Getting Started
11 |
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.
12 |
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!
13 |
14 |
15 |
16 |
17 |
1.
18 |
Download our desktop application from GitHub
19 |
20 |
21 |
2.
22 |
Configure your mongoDB URI within the codebase
23 |
24 |
25 |
3.
26 |
Access our application's companion image from DockerHub
27 |
28 |
29 |
4.
30 |
Deploy the image as a Job with your mongoDB URI and Prometheus Server Pod IP as environment variables and the restartPolicy set to "OnFailure"
31 |
32 |
33 |
5.
34 |
Scheduled Jobs will appear on the Homepage
35 |
36 |
37 |
6.
38 |
Failed Job deployments will appear in red on the Archive
39 |
40 |
41 |
42 |
43 |
44 |
47 |
48 |

49 |
Visualize all scheduled jobs and their parent cronjobs
50 |
51 |
52 |

53 |
Filter jobs based on success or failure
54 |
55 |
56 |

57 |
Create Jobs and Cronjobs within the application and deploy them on submission
58 |
59 |
60 |
61 |
62 |
63 |
64 | )
65 | }
--------------------------------------------------------------------------------
/website/src/components/Home.jsx:
--------------------------------------------------------------------------------
1 | import { React } from 'react';
2 | import { Features } from '/src/components/Features.jsx';
3 | import { Team } from '/src/components/Team.jsx';
4 | import { Contact } from '/src/components/Contact.jsx';
5 |
6 | export const Home = () => {
7 | return (
8 |
9 |
10 |
11 |

12 |
Track your Kubernetes Jobs and Cronjobs
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | )
27 | };
--------------------------------------------------------------------------------
/website/src/components/Navbar.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Link } from 'react-router-dom';
3 | import { Bars3Icon, XMarkIcon } from '@heroicons/react/24/outline'
4 |
5 |
6 |
7 | export const Navbar = () => {
8 | const [nav, setNav] = useState(false);
9 | const [bgColor, setBGColor] = useState(false);
10 |
11 | const handleClick = () => setNav(!nav);
12 |
13 | const changeNavbar = () => {
14 | if (window.scrollY >= 80) setBGColor(true);
15 | else setBGColor(false);
16 | };
17 |
18 | window.addEventListener('scroll', changeNavbar);
19 |
20 |
21 | return (
22 |
23 |
24 |
25 | {!bgColor ?
:
}
26 |
27 |
28 | - Getting Started
29 | - {e.preventDefault(); window.location.replace('/#team')}} className='text-center hover:font-bold hover:cursor-pointer'>Meet the Team
30 | - { e.preventDefault(); window.location.replace('/#contact')}} className='text-center hover:font-bold hover:cursor-pointer'>Contact Us
31 | - Updates
32 |
33 |
34 |
35 | {!nav ? : }
36 |
37 |
38 |
39 | - Home
40 | - Getting Started
41 | - {e.preventDefault(); window.location.replace('/#team')}} className='border-b-2 border-zinc-300 w-full hover:font-bold hover:cursor-pointer'>Meet the Team
42 | - { e.preventDefault(); window.location.replace('/#contact')}} className='border-b-2 border-zinc-300 w-full hover:font-bold hover:cursor-pointer'>Contact Us
43 | - Updates
44 |
45 |
46 |
47 | )
48 | }
49 |
50 |
--------------------------------------------------------------------------------
/website/src/components/Team.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3 | import { faGithub, faLinkedinIn } from '@fortawesome/free-brands-svg-icons';
4 |
5 |
6 | export const Team = () => {
7 |
8 | return (
9 |
10 |
11 |
12 |
Our Team
13 |
Meet the engineers behind Proteus
14 |
15 |
16 |
17 |
18 |

19 |
20 |
Bianca Hua
21 |
Software Engineer
22 |
26 |
27 |
28 |
29 |

30 |
31 |
Eddy Kaggia
32 |
Software Engineer
33 |
37 |
38 |
39 |
40 |

41 |
42 |
Mark Bryan
43 |
Software Engineer
44 |
48 |
49 |
50 |
51 |

52 |
53 |
Matt Henely
54 |
Software Engineer
55 |
59 |
60 |
61 |
62 |
63 | )
64 | }
65 |
66 |
67 |
68 | {/* 
Henry Halse
Software Engineer

Yarden Edry
Software Engineer

Emad Kalali
Software Engineer

Uma Sowmiyamurthy
Software Engineer

Wendy Kuo
Software Engineer
*/}
--------------------------------------------------------------------------------
/website/src/index.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | @layer base {
6 | li {
7 | @apply p-4
8 | }
9 |
10 | button {
11 | @apply text-green border bg-indigo-600 border-indigo-100
12 | hover:bg-transparent hover:text-indigo-600 rounded-md
13 | }
14 |
15 | }
16 |
17 | #contact-btn {
18 | background-image: linear-gradient(90deg,#4799e6 0,#e969cb 51%, #4799e6);
19 | margin: 10px;
20 | padding: 15px 45px;
21 | text-align: center;
22 | text-transform: uppercase;
23 | transition: background-color 5s ease-in-out;
24 | background-size: 200% auto;
25 | color: #fff;
26 | box-shadow: 0 0 20px #eee;
27 | border-radius: 7px;
28 | letter-spacing: 1.5px;
29 | font-weight: 300;
30 | }
31 |
32 | #contact-btn:hover {
33 | background-image: linear-gradient(120deg, #e969cb 0, #4799e6 51%, #e969cb);
34 | transition: background-color 5s ease-in-out;
35 | }
36 |
37 | #proteus-button {
38 | position: relative;
39 | background: #fff;
40 | color: rgb(0, 0, 0, 0.6);
41 | margin-top: 4rem;
42 | width: 200px;
43 | height: 60px;
44 | border: 2px;
45 | border-radius: 2px;
46 | border-style: solid;
47 | border-color: #e969cb;
48 | font-size: 18px;
49 | font-family: Raleway,sans-serif;
50 | transition: background-color .5s ease-in-out;
51 | overflow: hidden;
52 | }
53 |
54 | #proteus-button:hover {
55 | background-image: linear-gradient(120deg, #e969cb 0, #4799e6 45%, #e969cb);
56 | transition: background-color .5s ease-in-out;
57 | color: white;
58 | }
59 |
60 | #team-circle {
61 | border: solid 3px white inset;
62 | }
63 |
64 | #gs_docs {
65 | text-decoration: underline;
66 | }
67 |
68 | #gs_docs:hover {
69 | color: rgb(238, 106, 220)
70 | }
--------------------------------------------------------------------------------
/website/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom/client'
3 | import { BrowserRouter } from 'react-router-dom'
4 | import App from '/src/App.jsx'
5 | import './index.css'
6 |
7 | ReactDOM.createRoot(document.getElementById('root')).render(
8 |
9 |
10 |
11 |
12 | ,
13 | )
14 |
--------------------------------------------------------------------------------
/website/tailwind.config.cjs:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: [
4 | "./index.html",
5 | "./src/**/*.{js,ts,jsx,tsx}",
6 | ],
7 | theme: {
8 | screens: {
9 | sm: '640px',
10 | md: '768px',
11 | lg: '1024px',
12 | xl: '1280px',
13 | '2xl': '1563px',
14 | },
15 | extend: {
16 | colors: {
17 | 'blue': '#1fb6ff',
18 | 'purple': '#7e5bef',
19 | 'pink': '#EE90EA',
20 | 'orange': '#ff7849',
21 | 'green': '#13ce66',
22 | 'yellow': '#ffc82c',
23 | 'gray-dark': '#273444',
24 | 'gray': '#8492a6',
25 | 'gray-pink': '#9E979E',
26 | 'gray-light': '#d3dce6',
27 | },
28 | fontFamily: {
29 | sans: ['Graphik', 'sans-serif'],
30 | serif: ['Merriweather', 'serif'],
31 | }
32 | }
33 | },
34 | plugins: [],
35 | }
36 |
--------------------------------------------------------------------------------
/website/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------