Merge pull request #1 from nickorlow/done

Done
This commit is contained in:
Nicholas Orlowsky 2023-01-18 01:45:47 -06:00 committed by GitHub
commit ed11a1d2cd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 2967 additions and 173 deletions

View file

@ -2,10 +2,22 @@
<project version="4"> <project version="4">
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="03fafda4-e2c1-4602-a731-a2f96e84badd" name="Default Changelist" comment=""> <list default="true" id="03fafda4-e2c1-4602-a731-a2f96e84badd" name="Default Changelist" comment="">
<change afterPath="$PROJECT_DIR$/src/components/IncidentCard.tsx" afterDir="false" /> <change afterPath="$PROJECT_DIR$/src/components/HomePage.tsx" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/components/UptimeCard.tsx" afterDir="false" /> <change afterPath="$PROJECT_DIR$/src/components/UptimeCard.css" afterDir="false" />
<change afterPath="$PROJECT_DIR$/src/static/images/NWS_Logo_Transparent.png" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/App.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/App.tsx" afterDir="false" /> <change beforePath="$PROJECT_DIR$/package-lock.json" beforeDir="false" afterPath="$PROJECT_DIR$/package-lock.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/package.json" beforeDir="false" afterPath="$PROJECT_DIR$/package.json" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/App.css" beforeDir="false" afterPath="$PROJECT_DIR$/src/App.css" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/CreateCruisePage.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/CreateCruisePage.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/DashboardPage.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/DashboardPage.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/Footer.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/Footer.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/StatusPage.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/StatusPage.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/components/UptimeCard.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/components/UptimeCard.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/index.tsx" beforeDir="false" afterPath="$PROJECT_DIR$/src/index.tsx" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/nws-api/calls.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/nws-api/calls.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/nws-api/hooks.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/nws-api/hooks.ts" afterDir="false" />
<change beforePath="$PROJECT_DIR$/src/nws-api/types.ts" beforeDir="false" afterPath="$PROJECT_DIR$/src/nws-api/types.ts" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" /> <option name="HIGHLIGHT_CONFLICTS" value="true" />
@ -15,8 +27,10 @@
<component name="FileTemplateManagerImpl"> <component name="FileTemplateManagerImpl">
<option name="RECENT_TEMPLATES"> <option name="RECENT_TEMPLATES">
<list> <list>
<option value="HTTP Request" />
<option value="TypeScript File" /> <option value="TypeScript File" />
<option value="TypeScript JSX File" /> <option value="TypeScript JSX File" />
<option value="CSS File" />
</list> </list>
</option> </option>
</component> </component>
@ -33,14 +47,9 @@
<option name="showLibraryContents" value="true" /> <option name="showLibraryContents" value="true" />
</component> </component>
<component name="PropertiesComponent"> <component name="PropertiesComponent">
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
<property name="WebServerToolWindowFactoryState" value="false" /> <property name="WebServerToolWindowFactoryState" value="false" />
<property name="last_opened_file_path" value="$PROJECT_DIR$" /> <property name="last_opened_file_path" value="$PROJECT_DIR$/src/static/images" />
<property name="node.js.detected.package.eslint" value="true" /> <property name="list.type.of.created.stylesheet" value="CSS" />
<property name="node.js.path.for.package.eslint" value="project" />
<property name="node.js.selected.package.eslint" value="(autodetect)" />
<property name="nodejs_interpreter_path" value="/usr/local/bin/node" />
<property name="nodejs_package_manager_path" value="npm" /> <property name="nodejs_package_manager_path" value="npm" />
<property name="ts.external.directory.path" value="$PROJECT_DIR$/node_modules/typescript/lib" /> <property name="ts.external.directory.path" value="$PROJECT_DIR$/node_modules/typescript/lib" />
<property name="vue.rearranger.settings.migration" value="true" /> <property name="vue.rearranger.settings.migration" value="true" />
@ -48,6 +57,7 @@
<component name="RecentsManager"> <component name="RecentsManager">
<key name="CopyFile.RECENT_KEYS"> <key name="CopyFile.RECENT_KEYS">
<recent name="$PROJECT_DIR$/src/static/images" /> <recent name="$PROJECT_DIR$/src/static/images" />
<recent name="$PROJECT_DIR$/src/components" />
</key> </key>
</component> </component>
<component name="RunManager"> <component name="RunManager">
@ -69,7 +79,10 @@
<workItem from="1648500425230" duration="246000" /> <workItem from="1648500425230" duration="246000" />
<workItem from="1658028513357" duration="2964000" /> <workItem from="1658028513357" duration="2964000" />
<workItem from="1666469240565" duration="7361000" /> <workItem from="1666469240565" duration="7361000" />
<workItem from="1666543043382" duration="439000" /> <workItem from="1666543043382" duration="3699000" />
<workItem from="1668047509596" duration="7310000" />
<workItem from="1673378530233" duration="327000" />
<workItem from="1673538703809" duration="6147000" />
</task> </task>
<servers /> <servers />
</component> </component>

1715
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -11,12 +11,17 @@
"@types/react": "^17.0.39", "@types/react": "^17.0.39",
"@types/react-dom": "^17.0.11", "@types/react-dom": "^17.0.11",
"bootstrap": "^5.1.3", "bootstrap": "^5.1.3",
"react": "^17.0.2", "react": "^18.2.0",
"react-bootstrap": "^2.4.0", "react-bootstrap": "^2.4.0",
"react-dom": "^17.0.2", "react-dom": "^18.2.0",
"react-markdown": "^8.0.3",
"react-modal": "^3.16.1",
"react-router-dom": "^6.4.2",
"react-scripts": "5.0.0", "react-scripts": "5.0.0",
"react-tooltip": "^4.4.3", "react-tooltip": "^4.4.3",
"strip-markdown": "^5.0.0",
"typescript": "^4.5.5", "typescript": "^4.5.5",
"urijs": "^1.19.11",
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4"
}, },
"resolutions": { "resolutions": {
@ -46,5 +51,9 @@
"last 1 firefox version", "last 1 firefox version",
"last 1 safari version" "last 1 safari version"
] ]
},
"devDependencies": {
"@types/react-modal": "^3.13.1",
"@types/urijs": "^1.19.19"
} }
} }

View file

@ -4,6 +4,40 @@
flex-direction: column; flex-direction: column;
} }
input {
border-radius: 10px;
border-color: #000;
border-width: 1px;
border-style: solid;
padding: 3px;
}
button {
border-radius: 10px !important;
border-width: 0px;
background-color: cornflowerblue;
color: white;
margin-top: 10px;
margin-bottom: 10px;
padding: 3px
}
p {
margin: 0 !important;
}
.error-banner {
background-color: #f08080;
border-radius: 10px;
padding: 3px;
height: max-content;
}
.low-severity { .low-severity {
background-color: #98fb98 background-color: #98fb98
} }

View file

@ -1,97 +1,15 @@
import React, {useEffect, useState} from 'react'; import React, {useEffect, useState} from 'react';
import NWSLogo from './static/images/NWS_Logo.png';
import './App.css'; import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css'; import 'bootstrap/dist/css/bootstrap.min.css';
import {Incident, UptimeResponse} from "./nws-api/types"; import StatusPage from "./components/StatusPage";
import {getIncidents, getUptime} from "./nws-api/calls"; import Footer from "./components/Footer";
import ReactTooltip from 'react-tooltip'; import { BrowserRouter, Route, Outlet, Link } from "react-router-dom";
import UptimeCard from "./components/UptimeCard";
import IncidentCard from "./components/IncidentCard";
function App() { function App() {
const [uptime, setUptime] = useState<UptimeResponse>({datacenters: [], services:[], lastUpdated: ""});
const [incidents, setIncidents] = useState<Incident[]>([]);
const fetchUptime = async () => {
let resp: UptimeResponse = await getUptime();
setUptime(resp);
}
const fetchIncidents = async () => {
let resp: Incident[] = await getIncidents();
setIncidents(resp);
}
useEffect(() => {
fetchUptime();
fetchIncidents();
}, []);
// @ts-ignore
return ( return (
<div className="App"> <div/>
<div className={"w-100 row"}>
<div className={"col-md-6 d-flex justify-content-center flex-column align-items-center"}>
<img src={NWSLogo} alt="nws-logo" style={{width: "70%"}}/>
</div>
<div className={"col-md-6 text-center d-flex justify-content-center flex-column align-items-center"}>
<h1>Nick Web Services</h1>
<p style={{ maxWidth: 500 }} className={"col-md-6 text-center"}>
Nick Web Services is a hosting service based out of
Austin, Texas. It is committed
to achieving maximum uptime with better performance and a lower cost than any of the major cloud
services.
</p>
</div>
</div>
<div style={{width: '75vw'}}>
<hr/>
</div>
<div className={"text-left row"} style={{width: '75vw'}}>
<h2>NWS System Status</h2>
<p>Last updated at {new Date(uptime.lastUpdated).toLocaleString()}</p>
<div className={"col-md-6 col-12"}>
<h3>Service Status</h3>
{uptime.services.map((e) => {
return (
<UptimeCard uptime={e}/>
);
})}
</div>
<div className={"col-md-6 col-12"}>
<h3>Datacenter Status</h3>
{uptime.datacenters.map((e) => {
return (
<UptimeCard uptime={e}/>
);
})}
</div>
</div>
<div style={{width: '75vw'}}>
<hr/>
</div>
<div>
<h3>Service Alerts</h3>
{incidents.map((e) => {
return (
<IncidentCard incident={e}/>
);
})}
{incidents.length == 0 && <div className={`row text-center`} style={{width: '75vw'}}>
<h5 className={"col-12"}>No service alerts.</h5>
</div>}
</div>
<footer style={{margin: 25}}>
NWS is owned and operated by <a href={"http://nickorlow.com"}>Nicholas Orlowsky</a>.
</footer>
</div>
); );
} }

11
src/components/Blog.css Normal file
View file

@ -0,0 +1,11 @@
.blog-card {
transition: 1s;
width: 80%;
background-color: #eee;
border-radius: 20px;
overflow: clip;
}
.blog-card:hover {
transform: translateY(-10px);
}

41
src/components/Blogs.tsx Normal file
View file

@ -0,0 +1,41 @@
import {useEffect, useState} from "react";
import {Blog, Incident, UptimeResponse} from "../nws-api/types";
import {getBlogs, getIncidents, getUptime} from "../nws-api/calls";
import "./Blog.css";
import ReactMarkdown from 'react-markdown';
import strip from 'strip-markdown';
export default function Blogs(){
const [blogs, setBlogs] = useState<Blog[]>([]);
const fetchBlogs = async () => {
let resp: Blog[] = await getBlogs();
setBlogs(resp);
}
useEffect(() => {
fetchBlogs();
}, []);
return(
<div>
<h1>Blogs</h1>
<div className={"d-flex justify-content-center"}>
{blogs.map((e)=>{
return(
<div className={"blog-card row"} onClick={()=>{window.location.href=`/blog?id=${e.id}`}}>
<img src={e.imageUrl} className={"col-md-4 m-0 p-0"}/>
<div className={"p-2 col-md-8"}>
<h2>{e.title}</h2>
<p>By: {e.author}</p>
<p style={{maxHeight: 100, overflow: "clip"}}><ReactMarkdown remarkPlugins={[strip]}>{e.content}</ReactMarkdown>...</p>
<p><b>Click to read more</b></p>
</div>
</div>
);
})}
</div>
</div>
);
}

View file

@ -0,0 +1,181 @@
import {useState} from "react";
import URI from "urijs";
import {Namespace} from "../nws-api/types";
import {useNWSAccount, useNWSAuthKey} from "../nws-api/hooks";
export default function CreateCruisePage() {
const [page, setPage] = useState('info');
const [strat, setStrat] = useState<'raw-html' | 'react-js'>('raw-html');
const [owner, setOwner] = useState('');
const [repo, setRepo] = useState('');
const [name, setName] = useState('');
const [gitUriInput, setGUI] = useState('');
const [hostUriInput, setHUI] = useState('');
const authKey = useNWSAuthKey();
const acct = useNWSAccount();
function deploy() {
fetch("https://api-nws.nickorlow.com/Services/" + acct!.id + "/service",
{
method: 'POST',
headers: {
"Authorization": authKey
},
body: JSON.stringify({
"serviceName": name,
"containerUrl": `ghcr.io/${owner}/${repo}`,
"namespaceId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"serviceUrl": hostUriInput,
})
}).then((response)=> {
if(response.status === 200) {
}
}).catch((ex) =>{
alert(ex)
});
}
return (
<div className={"App"}>
<h1>Create Container Deployment</h1>
<div style={{width: "75vw"}}>
{
page === 'info' &&
<div>
<h3>Some information before we get started:</h3>
<ul>
<li>NWS is free to use</li>
<li>Currently, your DNS provider must support DNS flattening if you intend to point your root domain (e.g. nickorlow.com) to NWS. Subdomains should work fine though. (Cloudflare, Route 53, and Pagely). (Moving to Cloudflare is pretty easy)</li>
<li>Through the Web UI, you may only add one domain name. If you need to add more, <a href={"mailto:nws-support@nickorlow.com"}>contact me</a></li>
<li>NWS does not guarantee any uptime</li>
<li>NWS is run by a college student with little free time, support may reflect this</li>
<li>This platform is very early in development. It may require you to have some technical
knowledge.
</li>
<li>NWS may cease operations in the event of a widespread viral infection transmitted via
bites or contact with bodily fluids that causes human corpses to reanimate and seek to
consume living human flesh, blood, brain or nerve tissue and is likely to result in the
fall of organized civilization.
</li>
</ul>
<button onClick={()=>setPage('framework-hostname')}>I understand, continue</button>
</div>
}
{
page === 'framework-hostname' &&
<div>
<h5>What is this deployment's name?</h5>
<i>May only be lowercase letters and dashes, max 20 chars</i>
<br/>
<input value={name} onChange={(e)=>{setName(e.currentTarget.value)}}/>
<br/>
<h5>How did you create your website?</h5>
<p>Don't see your technology/framework? Email me: <a href={"mailto:nws-support@nickorlow.com"}>nws-support@nickorlow.com</a></p>
<select value={strat}>
<option id={"raw-html"} onClick={()=>setStrat('raw-html')}>Raw HTML</option>
<option id={"react-js"} onClick={()=>setStrat('react-js')}>React JS</option>
</select>
<br/>
<h5>What is the url of the github repo where your code is hosted? (e.g. https://github.com/nickorlow/personal-site)</h5>
<p>Other git hosting providers are not currently supported through the Web UI</p>
<p>The repo must be public to create it through the Web UI</p>
<input value={gitUriInput} onInput={(e)=>{setGUI(e.currentTarget.value)}}/>
<br/>
<h5>What domain name will you use with your website? (e.g. nickorlow.com)</h5>
<input value={hostUriInput} onChange={(e)=>{setHUI(e.currentTarget.value)}}/>
<br/>
<button onClick={()=>{
try {
let git_url = new URL(gitUriInput);
if (git_url.host !== 'github.com') {
alert('Only github is supported!')
return;
} else {
console.log(git_url.pathname.split('/'))
setOwner(git_url.pathname.split('/')[1])
setRepo(git_url.pathname.split('/')[2])
}
} catch (e) {
alert('invalid github url')
return;
}
try {
let url = new URL("https://"+hostUriInput);
} catch (e) {
alert('invalid host url')
return;
}
if(!/^[a-z-]+$/.test(name) || name.length > 20 || name.length == 0) {
alert('may only be lowercase and dashes and under 20 chars')
return;
}
setHUI("https://"+hostUriInput);
setPage('scriptgen')
}}>Continue</button>
<button onClick={()=>{setPage('info')}}>Back</button>
</div>
}
{
page === 'scriptgen' &&
<div>
<h4>Copy & Paste the below into your terminal to add NWS deployment scripts to your webapp</h4>
<code style={{backgroundColor: "black", padding: 5, borderRadius: 10}}> curl -s https://raw.githubusercontent.com/nickorlow/nws-ghactions-templates/main/add-nws.sh | bash -s {strat} {owner} {repo}</code>
<br/><span>Ensure the script finishes running before continuing</span>
<br/>
<button onClick={()=>{
deploy();
setPage('dns');
}}>Continue</button>
<button onClick={()=>setPage('framework-hostname')}>Back</button>
</div>
}
{
page === 'dns' &&
<div>
<h4>Add the following DNS entry to {new URI(hostUriInput).hostname()}</h4>
{
new URI(hostUriInput).subdomain().length == 0 &&
<div>
<p>If your DNS provider is:</p>
<ul>
<li>Cloudflare</li>
<li>Route 53</li>
<li>Pagely</li>
</ul>
<p>Type: CNAME</p>
<p>Name: @ ({hostUriInput})</p>
<p>Value: entry.nws.nickorlow.com</p>
</div>
}
{
new URI(hostUriInput).subdomain().length > 0 &&
<div>
<p>Type: CNAME</p>
<p>Name: {new URI(hostUriInput).subdomain()} ({new URI(hostUriInput).hostname()})</p>
<p>Value: entry.nws.nickorlow.com</p>
</div>
}
<br/>
<button onClick={()=>setPage('done')}>Finish Setup</button>
</div>
}
{
page === 'done' &&
<div>
<h3>Welcome to NWS</h3> <br/>
<button onClick={()=>{window.location.href="/dashboard"}}>Go to Dashboard</button> <br/>
<button onClick={()=>{window.location.href=hostUriInput}}>See my Site</button>
</div>
}
</div>
</div>
);
}

View file

@ -0,0 +1,53 @@
import {Account, Namespace, Service} from "../nws-api/types";
import {
useGetAccountNamespaces,
useGetAccountServices,
useGetServicesInNamespace,
useLoggedInRedirect,
useNWSAccount
} from "../nws-api/hooks";
import {useState} from "react";
export default function DashboardPage() {
useLoggedInRedirect();
let account: Account | undefined = useNWSAccount();
let {setNs, services, ns} = useGetServicesInNamespace();
let namespaces: Namespace[] = useGetAccountNamespaces();
return(
<div style={{minHeight: "100vh", padding: "50px"}}>
<div className={"row"}>
<h1 className={"col-md-10 col-12"}>Welcome to NWS, {account?.name}!</h1>
<select className={"col-12 col-md-2"} defaultValue={"Select Namespace..."}>
<option value="" disabled selected>Select Namespace...</option>
{
namespaces.map((e)=>{
return <option onClick={(a)=>{setNs(e)}}>{e.name}</option>
})
}
<option value="" disabled>---</option>
<option value="create-ns">Create Namespace</option>
</select>
</div>
<hr/>
<div className={"d-flex justify-content-between"}>
<h2>Container Deployment Services</h2>
<button onClick={(e) => {window.location.href = "/cruise/new?namespaceId="+ns!.id}}>Create Cruise Service</button>
</div>
<div className={"row"}>
{services.map((e)=>{
return (
<div className={"col-4"} style={{ padding: 5}}>
<div style={{backgroundColor: "#eee", borderRadius: 20, padding: 5}}>
<h3>{e.serviceName}</h3>
<p><b>Application Id</b></p>
<p>{e.serviceId}</p>
</div>
</div>);
})}
</div>
</div>
);
}

10
src/components/Footer.tsx Normal file
View file

@ -0,0 +1,10 @@
import React from "react";
export default function Footer() {
return (
<footer className={"mt-2 p-3"} style={{backgroundColor: "#eee"}}>
<p>NWS is owned and operated by <a href={"http://nickorlow.com"}>Nicholas Orlowsky</a>.</p>
<p>Copyright © Nicholas Orlowsky {new Date().getFullYear()}</p>
</footer>
);
}

View file

@ -0,0 +1,66 @@
import NWSLogo from "../static/images/NWS_Logo.png";
import UptimeCard from "./UptimeCard";
import IncidentCard from "./IncidentCard";
import Footer from "./Footer";
import React, {useEffect, useState} from "react";
import {Incident, UptimeResponse} from "../nws-api/types";
import {getIncidents, getUptime} from "../nws-api/calls";
import "../App.css";
export default function HomePage() {
const [uptime, setUptime] = useState<UptimeResponse>({datacenters: [], services: [], competitors: [], lastUpdated: ""});
const [incidents, setIncidents] = useState<Incident[]>([]);
const fetchUptime = async () => {
let resp: UptimeResponse = await getUptime();
setUptime(resp);
}
const fetchIncidents = async () => {
let resp: Incident[] = await getIncidents();
setIncidents(resp);
}
useEffect(() => {
fetchUptime();
fetchIncidents();
}, []);
return(
<div className="App">
<div className={"w-100 row"}>
<div className={"col-md-6 d-flex justify-content-center flex-column align-items-center"}>
<img src={NWSLogo} alt="nws-logo" style={{width: "70%"}}/>
</div>
<div className={"col-md-6 text-center d-flex justify-content-center flex-column align-items-center"}>
<h1>Nick Web Services</h1>
<p style={{maxWidth: 500}} className={"col-md-6 text-center"}>
Nick Web Services is a hosting service based out of
Austin, Texas. It is committed
to achieving maximum uptime with better performance and a lower cost than any of the major cloud
services.
</p>
</div>
</div>
<div style={{width: '75vw'}}>
<hr/>
</div>
<div className={"text-left row"} style={{width: '75vw'}}>
<h2>Compare us to our competitors</h2>
<p>Last updated at {new Date(uptime.lastUpdated).toLocaleString()}</p>
<div className={"col-12 row w-100 m-0"}>
{uptime.competitors.sort((a,b)=>{return b.uptimeMonth === a.uptimeMonth ? (a.name === "NWS" ? -1000 : b.name.localeCompare(a.name)) : b.uptimeMonth - a.uptimeMonth}).map((e) => {
return (
<UptimeCard uptime={e} isService={false}/>
);
})}
</div>
</div>
</div>
);
}

32
src/components/Login.css Normal file
View file

@ -0,0 +1,32 @@
.login-box {
border-style: solid;
border-width: 1px;
border-color: #aaa;
border-radius: 10px;
align-self: center;
justify-self: center;
width: 500px;
padding: 20px;
display: flex;
flex-direction: column;
justify-content: center;
}
.login-label {
margin: 0;
}
.login-button {
border-radius: 10px;
border-width: 0px;
background-color: cornflowerblue;
color: white;
margin-top: 10px;
margin-bottom: 10px;
padding: 3px
}

View file

@ -0,0 +1,64 @@
import "./Login.css";
import {useEffect, useState} from "react";
import {Account, ApiError, SessionKey} from "../nws-api/types";
import {useNonLoggedInRedirect} from "../nws-api/hooks";
export default function LoginPage() {
useNonLoggedInRedirect();
const [errorMessage, setErrorMessage] = useState<string>("");
const [email, setEmail] = useState<string>("");
const [password, setPassword] = useState<string>("");
function loginUser() {
if(email == "" || password == "") {
setErrorMessage("Please enter an email and password");
return;
}
let acc: Account = {
email: email,
password: password
};
fetch("https://api-nws.nickorlow.com/Account/session",{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(acc)
}).then((result) => {
if(result.status == 200) {
result.json().then((o: SessionKey)=>{
localStorage.setItem("session_key", JSON.stringify(o));
window.location.href = '/dashboard';
});
} else {
setErrorMessage("Server Error (This is NWS' fault)");
}
}).catch((e) =>{
setErrorMessage("Server Error (This is NWS' fault)");
});
}
return(
<div style={{minHeight: "100vh", display: "grid", width: "100%"}}>
<div className={"login-box"}>
<h3>Login to NWS Dashboard</h3>
{ errorMessage != "" &&
<div className={"error-banner"}>
<p style={{color: "black"}}>{errorMessage}</p>
</div>
}
<p className={"login-label"}>E-Mail Address</p>
<input onChange={(e)=>{setEmail(e.target.value)}} className={"login-input"}/>
<p className={"login-label"}>Password</p>
<input onChange={(e)=>{setPassword(e.target.value)}}className={"login-input"} type={"password"}/>
<button className={"login-button"} onClick={loginUser}>Login</button>
<p>No account? <a href={"/register"}>Register Here!</a></p>
</div>
</div>
);
}

View file

@ -0,0 +1,10 @@
export default function NotFoundPage() {
return(
<div style={{width: "100vw", height: "100vh", display: "flex", justifyContent: "center", alignContent: "center", alignItems: "center"}}>
<div>
<h1>Not Found :(</h1>
<a href={"/"}>Home</a>
</div>
</div>
);
}

View file

@ -0,0 +1,32 @@
.reg-box {
border-style: solid;
border-width: 1px;
border-color: #aaa;
border-radius: 10px;
align-self: center;
justify-self: center;
width: 500px;
padding: 20px;
display: flex;
flex-direction: column;
justify-content: center;
}
.reg-label {
margin: 0;
}
.reg-button {
border-radius: 10px;
border-width: 0px;
background-color: cornflowerblue;
color: white;
margin-top: 10px;
margin-bottom: 10px;
padding: 3px
}

View file

@ -0,0 +1,105 @@
import "./RegisterPage.css";
import {useState} from "react";
import {Account, ApiError} from "../nws-api/types";
import {useNonLoggedInRedirect} from "../nws-api/hooks";
export default function RegisterPage() {
const [errorMessage, setErrorMessage] = useState<String>("");
const [name, setName] = useState<string>("");
const [email, setEmail] = useState<string>("");
const [password, setPassword] = useState<string>("");
const [cpassword, setCpassword] = useState<string>("");
const [inviteCode, setInviteCode] = useState<string>("");
const [didRegister, setDidRegister] = useState<Boolean>(false);
async function createAccount() {
if(name == "" || email == "" || password == "" || cpassword == "" || inviteCode == "") {
setErrorMessage("You must fill out all information before registering.")
return;
}
if(cpassword != password) {
setErrorMessage("Passwords don't match!")
return;
}
if(!email
.toLowerCase()
.match(
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
)) {
setErrorMessage("You have entered an invalid E-Mail address.")
return;
}
let newAcc: Account = {
email: email,
name: name,
password: password
};
fetch("https://api-nws.nickorlow.com/Account",{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Invite-Code': inviteCode
},
body: JSON.stringify(newAcc)
}).then((result) => {
if(result.status == 200) {
setDidRegister(true);
} else {
result.json().then((o: ApiError) => {
setErrorMessage(o.ErrorMessage);
});
}
}).catch((e) =>{
setErrorMessage("Server Error (This is NWS' fault)")
});
}
useNonLoggedInRedirect();
return(
<div style={{minHeight: "100vh", display: "grid", width: "100%"}}>
<div className={"reg-box"} style={{display: didRegister ? "none" : "flex"}}>
<h3>Create an NWS Account</h3>
{ errorMessage != "" &&
<div className={"error-banner"}>
<p style={{color: "black"}}>{errorMessage}</p>
</div>
}
<p className={"reg-label"}>Name</p>
<input onChange={(e)=>setName(e.target.value)} className={"reg-input"}/>
<p className={"reg-label"}>E-Mail Address</p>
<input onChange={(e)=>setEmail(e.target.value)} className={"reg-input"}/>
<p className={"reg-label"}>Password</p>
<input onChange={(e)=>setPassword(e.target.value)} className={"reg-input"} type={"password"}/>
<p className={"reg-label"}>Confirm</p>
<input onChange={(e)=>setCpassword(e.target.value)} className={"reg-input"} type={"password"}/>
<div style={{width: "100%", marginTop: 10, marginBottom: 10, padding: 10, backgroundColor: "#eee", borderRadius: 10}}>
<p className={"reg-label"}>Invite Code</p>
<input onChange={(e)=>setInviteCode(e.target.value)} style={{width: "100%"}}/>
<small>Currently, NWS is invite only. Email me to get an invite code.</small>
</div>
<button onClick={createAccount} className={"reg-button"}>Create Account</button>
<p>Already have an account? <a href={"/login"}>Login Here!</a></p>
</div>
<div className={"reg-box"} style={{display: didRegister ? "flex" : "none"}}>
<h3>Verify your E-Mail address.</h3>
<p>Please verify your E-Mail by clicking the link we sent to you at: <b>{email}</b></p>
</div>
</div>
);
}

View file

@ -0,0 +1,73 @@
import NWSLogo from "../static/images/NWS_Logo.png";
import UptimeCard from "./UptimeCard";
import IncidentCard from "./IncidentCard";
import Footer from "./Footer";
import React, {useEffect, useState} from "react";
import {Incident, UptimeResponse} from "../nws-api/types";
import {getIncidents, getUptime} from "../nws-api/calls";
import "../App.css";
export default function StatusPage() {
const [uptime, setUptime] = useState<UptimeResponse>({datacenters: [], services: [], competitors: [], lastUpdated: ""});
const [incidents, setIncidents] = useState<Incident[]>([]);
const fetchUptime = async () => {
let resp: UptimeResponse = await getUptime();
setUptime(resp);
}
const fetchIncidents = async () => {
let resp: Incident[] = await getIncidents();
setIncidents(resp);
}
useEffect(() => {
fetchUptime();
fetchIncidents();
}, []);
return(
<div className="App" style={{padding: 20}}>
<div className={"text-left row"} style={{width: '75vw'}}>
<h1>NWS System Status</h1>
<p>Last updated at {new Date(uptime.lastUpdated).toLocaleString()}</p>
<div className={"col-md-6 col-12"}>
<h3>Service Status</h3>
{uptime.services.map((e) => {
return (
<UptimeCard uptime={e} isService={true}/>
);
})}
</div>
<div className={"col-md-6 col-12"}>
<h3>Datacenter Status</h3>
{uptime.datacenters.map((e) => {
return (
<UptimeCard uptime={e} isService={false}/>
);
})}
</div>
</div>
<div style={{width: '75vw'}}>
<hr/>
</div>
<div>
<h3>Service Alerts</h3>
{incidents.map((e) => {
return (
<IncidentCard incident={e}/>
);
})}
{incidents.length == 0 &&
<div className={`row text-center`} style={{width: '75vw'}}>
<h5 className={"col-12"}>No service alerts.</h5>
</div>
}
</div>
</div>
);
}

View file

@ -0,0 +1,28 @@
.uptime-lnk {
font-weight: bolder;
color: #2f2f2f;
text-decoration: none;
transition: .5s;
cursor: pointer;
}
.uptime-lnk:hover {
color: #e08b0d;
}
.uptime-modal {
border-radius: 20px;
border-color: transparent;
background-color: #eee;
padding: 20px;
position: absolute;
left: 50%;
top: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
width: 500px;
max-width: 100vw;
}

View file

@ -1,29 +1,52 @@
import {UptimeRecord} from "../nws-api/types"; import {UptimeRecord} from "../nws-api/types";
import React from "react"; import React, {useState} from "react";
import '../App.css'; import '../App.css';
import "./UptimeCard.css"
import Modal from "react-modal";
export default function UptimeCard(props: {uptime: UptimeRecord}) { export default function UptimeCard(props: {uptime: UptimeRecord, isService: boolean}) {
const [isModalOpen, setModalOpen] = useState(false);
return( return(
<div className={"nws-card row mb-2"} style={{maxWidth: '100%'}}> <div className={"nws-card row mb-2 m-0"} style={{maxWidth: '100%'}}>
{props.uptime.url != null && <h3 className={"col-md-9 col-12"}><a href={props.uptime.url} style={{textDecoration: "none"}}>{props.uptime.name}</a></h3>} <h3 className={"col-md-9 col-12 uptime-lnk"} onClick={()=>setModalOpen(true)}>{props.uptime.name}</h3>
{props.uptime.url == null && <h3 className={"col-md-9 col-12"}>{props.uptime.name}</h3>}
<div className={`col-md-3 col-12 d-flex d-md-none justify-content-start`}> <div className={`col-md-3 col-12 d-flex d-md-none justify-content-start`}>
<p className={`fw-bold severity-label <p className={`fw-bold severity-label w-100
${props.uptime.isUp ? 'low' : (props.uptime.undergoingMaintenance ? 'med' : 'high')}-severity`} ${props.uptime.isUp ? 'low' : (props.uptime.undergoingMaintenance ? 'med' : 'high')}-severity`}
style={{width: "max-content"}}> >
{props.uptime.isUp ? 'Up' : (props.uptime.undergoingMaintenance ? 'Maintenance' : 'Down')} {props.uptime.isUp ? 'Up' : (props.uptime.undergoingMaintenance ? 'Maintenance' : 'Down')}
</p> </p>
</div> </div>
<div className={`d-md-flex col-md-3 col-12 d-none justify-content-end`}> <div className={`d-md-flex col-md-3 col-12 d-none justify-content-end`}>
<p className={`fw-bold severity-label <p className={`fw-bold severity-label
${props.uptime.isUp ? 'low' : (props.uptime.undergoingMaintenance ? 'med' : 'high')}-severity`} ${props.uptime.isUp ? 'low' : (props.uptime.undergoingMaintenance ? 'med' : 'high')}-severity`}
style={{width: "max-content"}}> style={{width: "max-content", height: 'max-content'}}>
{props.uptime.isUp ? 'Up' : (props.uptime.undergoingMaintenance ? 'Maintenance' : 'Down')} {props.uptime.isUp ? 'Up' : (props.uptime.undergoingMaintenance ? 'Maintenance' : 'Down')}
</p> </p>
</div> </div>
<p className={"col-md-12 col-12"}><b>Last Month Uptime:</b> {props.uptime.uptimeMonth}%</p> <p className={"col-md-12 col-12"}><b>Last Month Uptime:</b> {props.uptime.uptimeMonth}%</p>
<p className={"col-md-6 col-12"}><b>All Time Uptime:</b> {props.uptime.uptimeAllTime}%</p> <p className={"col-md-6 col-12"}><b>{new Date().getFullYear()} Uptime:</b> {props.uptime.uptimeYtd}%</p>
<p className={"col-md-6 col-12"}><b>Avg Response Time:</b> {props.uptime.averageResponseTime}ms</p>
<Modal className={"uptime-modal"} isOpen={isModalOpen}>
<div className={"mb-3"}>
<h1 className={"mb-0"}>{props.uptime.name}</h1>
{props.uptime.url && <p>(<a href={props.uptime.url}>{props.uptime.url}</a>)</p>}
</div>
<div className={"mb-3"}>
<p>Monitoring since {props.uptime.monitorStart}</p>
<p><b>{new Date().getFullYear()} Uptime (YTD):</b> {props.uptime.uptimeYtd}%</p>
<p><b>Last Month Uptime:</b> {props.uptime.uptimeMonth}%</p>
<p><b>All-Time Uptime:</b> {props.uptime.uptimeAllTime}%</p>
<p><b>Average Response Time:</b> {props.uptime.averageResponseTime}ms</p>
</div>
{
props.isService &&
<div className={"mb-3"}>
<i>Note that the uptime and performance of services hosted on NWS may be affected by factors not controlled by NWS such as bad bad optimization or buggy software.</i>
</div>
}
<button className={"w-100"} onClick={()=>setModalOpen(false)}>Close</button>
</Modal>
</div> </div>
); );
} }

View file

@ -0,0 +1,32 @@
.verify-box {
border-style: solid;
border-width: 1px;
border-color: #aaa;
border-radius: 10px;
align-self: center;
justify-self: center;
width: 500px;
padding: 20px;
display: flex;
flex-direction: column;
justify-content: center;
}
.verify-label {
margin: 0;
}
.verify-button {
border-radius: 10px;
border-width: 0px;
background-color: cornflowerblue;
color: white;
margin-top: 10px;
margin-bottom: 10px;
padding: 3px
}

View file

@ -0,0 +1,67 @@
import "./RegisterPage.css";
import {useEffect, useState} from "react";
import {Account, SessionKey} from "../nws-api/types";
import {useSearchParams} from "react-router-dom";
import {Session} from "inspector";
export default function VerifyPage() {
const [pageState, setPageState] = useState<string>("");
const [searchParams, setSearchParams] = useSearchParams();
useEffect(()=>{
let verificationKey: string | null = searchParams.get("key");
if(verificationKey == null) {
setPageState("invalid_code");
return;
} else {
fetch("https://api-nws.nickorlow.com/Account/verify?verificationKey=" + verificationKey, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json'
}
}).then((result) => {
if (result.status == 200) {
result.json().then((o: SessionKey)=>{
localStorage.setItem("session_key", JSON.stringify(o));
window.location.href = '/dashboard';
});
}
if (result.status == 500) {
setPageState("server_error");
} else {
result.json().then((o) => {
if (o.ErrorMessage == "Invalid verification key.") {
setPageState("invalid_code");
} else {
setPageState("expired_code");
}
});
}
}).catch((e) => {
setPageState("invalid_code");
});
}
}, [])
return(
<div style={{minHeight: "100vh", display: "grid", width: "100%"}}>
<div className={"reg-box"} style={{display: pageState == "invalid_code" ? "flex" : "none"}}>
<h3>Uh Oh!</h3>
<p>Looks like the verification code you provided didn't work!</p>
<p className={"mt-2"}>Try to click on the link in the E-Mail sent to you instead of copying it.</p>
</div>
<div className={"reg-box"} style={{display: pageState == "expired_code" ? "flex" : "none"}}>
<h3>Expired Link</h3>
<p>It looks like the link you used to verify your account has expired.</p>
<p className={"mt-2"}>We've sent a new link to your email that is valid for 30 minutes.</p>
</div>
</div>
);
}

View file

@ -11,3 +11,16 @@ code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace; monospace;
} }
.nav-lnk {
font-weight: bold;
color: black;
text-decoration: none;
padding-left: 30px;
font-size: 1.1rem;
transition: .5s;
}
.nav-lnk:hover {
color: #F7BA00;
}

View file

@ -1,14 +1,152 @@
import React from 'react'; import * as React from "react";
import ReactDOM from 'react-dom'; import * as ReactDOM from "react-dom";
import './index.css'; import {
import App from './App'; createBrowserRouter, NavLink,
import reportWebVitals from './reportWebVitals'; RouterProvider,
} from "react-router-dom";
import StatusPage from "./components/StatusPage";
import reportWebVitals from "./reportWebVitals";
import "./index.css";
import 'bootstrap/dist/css/bootstrap.min.css';
import UptimeCard from "./components/UptimeCard";
import Footer from "./components/Footer";
import {Nav, Navbar, NavbarBrand, NavDropdown} from "react-bootstrap";
import NWSLogo from "./static/images/NWS_Logo_Transparent.png";
import Blogs from "./components/Blogs";
import NotFoundPage from "./components/NotFoundPage";
import LoginPage from "./components/LoginPage";
import RegisterPage from "./components/RegisterPage";
import VerifyPage from "./components/VerifyPage";
import DashboardPage from "./components/DashboardPage";
import CreateCruisePage from "./components/CreateCruisePage";
import HomePage from "./components/HomePage";
ReactDOM.render( function Layout (props: {children: any}) {
<React.StrictMode> return (
<App /> <div>
</React.StrictMode>, <Navbar sticky={"top"} style={{height: "8vh", backgroundColor: "#eee", paddingLeft: "5vw", paddingRight: "5vw", maxWidth:"100%"}} className={"row m-0"}>
document.getElementById('root') <div className={"col-10"}>
<NavbarBrand>
<img src={NWSLogo} alt="nws-logo" style={{width: 120}}/>
</NavbarBrand>
<NavLink className={"nav-lnk"} to={"/"}>
Home
</NavLink>
<NavLink className={"nav-lnk"} to={"/status"}>
Status
</NavLink>
</div>
{/*<NavLink className={"nav-lnk"} to={"/blogs"}>*/}
{/* Blog*/}
{/*</NavLink>*/}
<div className={"col-2 d-none d-md-block"}>
{ localStorage.getItem("session_key") === null &&
(
<NavLink className={"nav-lnk"} to={"/login"}>
Login
</NavLink>
)
}
{ localStorage.getItem("session_key") === null ||
(
<NavDropdown title={"Account"} className={"nav-lnk"}>
<NavLink className={"nav-lnk"} to={"/dashboard"}>
Dashboard
</NavLink>
<hr/>
<NavLink className={"nav-lnk"} to={"/login"} onClick={()=>{localStorage.removeItem("session_key")}}>
Logout
</NavLink>
</NavDropdown>
)
}
</div>
</Navbar>
<div style={{minHeight: "92vh"}}>
{props.children}
</div>
<Footer/>
</div>
);
}
const router = createBrowserRouter([
{
path: "/",
element:
<Layout>
<HomePage/>
</Layout>
},
{
path: "status",
element:
<Layout>
<StatusPage/>
</Layout>
},
{
path: "blog",
element:
<Layout>
<Blogs/>
</Layout>
},
{
path: "blogs",
element:
<Layout>
<Blogs/>
</Layout>
},
{
path: "login",
element:
<Layout>
<LoginPage/>
</Layout>
},
{
path: "verify",
element:
<Layout>
<VerifyPage/>
</Layout>
},
{
path: "dashboard",
element:
<Layout>
<DashboardPage/>
</Layout>
},
{
path: "register",
element:
<Layout>
<RegisterPage/>
</Layout>
},
{
path: "cruise/new",
element:
<Layout>
<CreateCruisePage/>
</Layout>
},
{
path: "*",
element:
<Layout>
<NotFoundPage/>
</Layout>
},
]);
// @ts-ignore
ReactDOM.createRoot(document.getElementById("root")).render(
<RouterProvider router={router} />
); );
// If you want to start measuring performance in your app, pass a function // If you want to start measuring performance in your app, pass a function

View file

@ -1,4 +1,4 @@
import {Incident, UptimeResponse} from "./types"; import {Blog, Incident, Namespace, Service, SessionKey, UptimeResponse} from "./types";
export async function getUptime(): Promise<UptimeResponse> { export async function getUptime(): Promise<UptimeResponse> {
let response: Response = await fetch('https://api-nws.nickorlow.com/uptime'); let response: Response = await fetch('https://api-nws.nickorlow.com/uptime');
@ -17,3 +17,35 @@ export async function getIncidents(): Promise<Incident[]> {
} }
} }
export async function getBlogs(): Promise<Blog[]> {
let response: Response = await fetch('https://api-nws.nickorlow.com/blogs');
let blogs: Blog[] = await response.json();
return blogs;
}
export async function getSessionKey(accountId: string, password: string): Promise<SessionKey> {
let response: Response = await fetch('https://api-nws.nickorlow.com/Account/session',
{
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
'id': accountId,
'password': password
})
});
let sessionKey: SessionKey = await response.json();
return sessionKey;
}
export async function getNamespaces(accountId: string, skey: SessionKey): Promise<Namespace[]> {
let response: Response = await fetch('https://api-nws.nickorlow.com/'+accountId+'/namespaces', {
headers: {
Authorization: skey.id
}
});
let namespaces: Namespace[] = await response.json();
return namespaces;
}

156
src/nws-api/hooks.ts Normal file
View file

@ -0,0 +1,156 @@
import {useEffect, useState} from "react";
import {Account, Namespace, Service, SessionKey} from "./types";
export function useNonLoggedInRedirect() {
useEffect(()=>{
let rawSession: string | null = localStorage.getItem("session_key");
if(rawSession != null) {
let session: SessionKey = JSON.parse(rawSession);
if(session.expiry < new Date()) {
localStorage.removeItem("session_key");
} else {
window.location.href = "/dashboard";
}
}
}, []);
return true;
}
export function useLoggedInRedirect() {
useEffect(()=>{
let rawSession: string | null = localStorage.getItem("session_key");
if(rawSession != null) {
let session: SessionKey = JSON.parse(rawSession);
if(session.expiry > new Date()) {
window.location.href = "/login";
}
} else {
window.location.href = "/login";
}
}, []);
return true;
}
export function useGetAccountServices() {
const [services, setService] = useState<Service[]>([]);
useEffect(() => {
let rawSession: string | null = localStorage.getItem("session_key");
if(rawSession != null) {
let session: SessionKey = JSON.parse(rawSession);
fetch("https://api-nws.nickorlow.com/Account/services?accountId=" + session.accountId,
{
headers: {
"Authorization": btoa(session.accountId + ":" + session.id)
}
}).then((response)=>{
response.json().then((svcs: Service[]) => {
console.log(svcs)
setService(svcs);
});
});
}
}, []);
return services;
}
export function useGetAccountNamespaces() {
const [namespaces, setNamespaces] = useState<Namespace[]>([]);
useEffect(() => {
let rawSession: string | null = localStorage.getItem("session_key");
if(rawSession != null) {
let session: SessionKey = JSON.parse(rawSession);
fetch("https://api-nws.nickorlow.com/Account/" + session.accountId + "/namespaces",
{
headers: {
"Authorization": btoa(session.accountId + ":" + session.id)
}
}).then((response)=>{
response.json().then((svcs: Namespace[]) => {
console.log(svcs)
setNamespaces(svcs);
});
});
}
}, []);
return namespaces;
}
export function useNWSAuthKey() {
const [key, setKey] = useState('');
useEffect(() => {
let rawSession: string | null = localStorage.getItem("session_key");
if(rawSession != null) {
let session: SessionKey = JSON.parse(rawSession);
setKey(btoa(session.accountId + ":" + session.id))
}
}, []);
return key;
}
export function useGetServicesInNamespace() {
const [services, setServices] = useState<Service[]>([]);
const [ns, setNs] = useState<Namespace | null>(null);
useEffect(() => {
console.log(ns !== null ? ns.id : "null")
if(ns === null) return;
let rawSession: string | null = localStorage.getItem("session_key");
if(rawSession != null) {
let session: SessionKey = JSON.parse(rawSession);
fetch("https://api-nws.nickorlow.com/Account/" + session.accountId + "/namespaces/" + ns.id + "/services",
{
headers: {
"Authorization": btoa(session.accountId + ":" + session.id)
}
}).then((response)=>{
response.json().then((svcs: Service[]) => {
console.log(svcs)
setServices(svcs);
});
});
}
}, [ns]);
return {setNs, services, ns};
}
export function useNWSAccount() {
const [accountInfo, setAccountInfo] = useState<Account>();
useEffect(()=>{
let rawSession: string | null = localStorage.getItem("session_key");
if(rawSession != null) {
let session: SessionKey = JSON.parse(rawSession);
fetch("https://api-nws.nickorlow.com/Account/"+session.accountId, {
headers: {
"Authorization": btoa(session.accountId+":"+session.id)
}
}).then((e)=>{
if(e.status == 200) {
e.json().then((o: Account) => {
setAccountInfo(o)
})
} else {
localStorage.removeItem("session_key");
window.location.href = "/login";
}
});
} else {
localStorage.removeItem("session_key");
window.location.href = "/login";
}
}, []);
return accountInfo;
}

View file

@ -3,6 +3,9 @@ export type UptimeRecord = {
url: string, url: string,
uptimeMonth: number, uptimeMonth: number,
uptimeAllTime: number, uptimeAllTime: number,
uptimeYtd: number,
averageResponseTime: number,
monitorStart: string,
isUp: boolean, isUp: boolean,
undergoingMaintenance: boolean undergoingMaintenance: boolean
}; };
@ -10,6 +13,7 @@ export type UptimeRecord = {
export type UptimeResponse = { export type UptimeResponse = {
datacenters: UptimeRecord[], datacenters: UptimeRecord[],
services: UptimeRecord[], services: UptimeRecord[],
competitors: UptimeRecord[],
lastUpdated: string lastUpdated: string
}; };
@ -29,7 +33,43 @@ export type Incident = {
}; };
enum IncidentSeverity { enum IncidentSeverity {
LOW, LOW,
MEDIUM, MEDIUM,
HIGH HIGH
}; };
// Below is primarily for user-facing things
export type Account = {
id?: string,
email: string,
name?: string,
password?: string,
status?: string
};
export type Service = {
serviceId: string,
serviceName: string,
namespace: string,
containerUrl: string,
ownerId: string
}
export type ApiError = {
StatusCode: number,
ErrorMessage: string
};
export type SessionKey = {
id: string,
expiry: Date,
accountId: string,
ip: string
};
export type Namespace = {
id: string,
accountId: string,
name: string
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB