commit
ed11a1d2cd
|
@ -2,10 +2,22 @@
|
|||
<project version="4">
|
||||
<component name="ChangeListManager">
|
||||
<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/UptimeCard.tsx" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/components/HomePage.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$/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>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
|
@ -15,8 +27,10 @@
|
|||
<component name="FileTemplateManagerImpl">
|
||||
<option name="RECENT_TEMPLATES">
|
||||
<list>
|
||||
<option value="HTTP Request" />
|
||||
<option value="TypeScript File" />
|
||||
<option value="TypeScript JSX File" />
|
||||
<option value="CSS File" />
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
|
@ -33,14 +47,9 @@
|
|||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">
|
||||
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
|
||||
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
|
||||
<property name="WebServerToolWindowFactoryState" value="false" />
|
||||
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
|
||||
<property name="node.js.detected.package.eslint" value="true" />
|
||||
<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="last_opened_file_path" value="$PROJECT_DIR$/src/static/images" />
|
||||
<property name="list.type.of.created.stylesheet" value="CSS" />
|
||||
<property name="nodejs_package_manager_path" value="npm" />
|
||||
<property name="ts.external.directory.path" value="$PROJECT_DIR$/node_modules/typescript/lib" />
|
||||
<property name="vue.rearranger.settings.migration" value="true" />
|
||||
|
@ -48,6 +57,7 @@
|
|||
<component name="RecentsManager">
|
||||
<key name="CopyFile.RECENT_KEYS">
|
||||
<recent name="$PROJECT_DIR$/src/static/images" />
|
||||
<recent name="$PROJECT_DIR$/src/components" />
|
||||
</key>
|
||||
</component>
|
||||
<component name="RunManager">
|
||||
|
@ -69,7 +79,10 @@
|
|||
<workItem from="1648500425230" duration="246000" />
|
||||
<workItem from="1658028513357" duration="2964000" />
|
||||
<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>
|
||||
<servers />
|
||||
</component>
|
||||
|
|
1715
package-lock.json
generated
1715
package-lock.json
generated
File diff suppressed because it is too large
Load diff
13
package.json
13
package.json
|
@ -11,12 +11,17 @@
|
|||
"@types/react": "^17.0.39",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"bootstrap": "^5.1.3",
|
||||
"react": "^17.0.2",
|
||||
"react": "^18.2.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-tooltip": "^4.4.3",
|
||||
"strip-markdown": "^5.0.0",
|
||||
"typescript": "^4.5.5",
|
||||
"urijs": "^1.19.11",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"resolutions": {
|
||||
|
@ -46,5 +51,9 @@
|
|||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react-modal": "^3.13.1",
|
||||
"@types/urijs": "^1.19.19"
|
||||
}
|
||||
}
|
||||
|
|
34
src/App.css
34
src/App.css
|
@ -4,6 +4,40 @@
|
|||
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 {
|
||||
background-color: #98fb98
|
||||
}
|
||||
|
|
90
src/App.tsx
90
src/App.tsx
|
@ -1,97 +1,15 @@
|
|||
import React, {useEffect, useState} from 'react';
|
||||
import NWSLogo from './static/images/NWS_Logo.png';
|
||||
import './App.css';
|
||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||
import {Incident, UptimeResponse} from "./nws-api/types";
|
||||
import {getIncidents, getUptime} from "./nws-api/calls";
|
||||
import ReactTooltip from 'react-tooltip';
|
||||
import UptimeCard from "./components/UptimeCard";
|
||||
import IncidentCard from "./components/IncidentCard";
|
||||
import StatusPage from "./components/StatusPage";
|
||||
import Footer from "./components/Footer";
|
||||
import { BrowserRouter, Route, Outlet, Link } from "react-router-dom";
|
||||
|
||||
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 (
|
||||
<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>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>
|
||||
<div/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
11
src/components/Blog.css
Normal file
11
src/components/Blog.css
Normal 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
41
src/components/Blogs.tsx
Normal 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>
|
||||
);
|
||||
}
|
181
src/components/CreateCruisePage.tsx
Normal file
181
src/components/CreateCruisePage.tsx
Normal 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>
|
||||
);
|
||||
}
|
53
src/components/DashboardPage.tsx
Normal file
53
src/components/DashboardPage.tsx
Normal 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
10
src/components/Footer.tsx
Normal 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>
|
||||
);
|
||||
}
|
66
src/components/HomePage.tsx
Normal file
66
src/components/HomePage.tsx
Normal 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
32
src/components/Login.css
Normal 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
|
||||
}
|
64
src/components/LoginPage.tsx
Normal file
64
src/components/LoginPage.tsx
Normal 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>
|
||||
);
|
||||
}
|
10
src/components/NotFoundPage.tsx
Normal file
10
src/components/NotFoundPage.tsx
Normal 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>
|
||||
);
|
||||
}
|
32
src/components/RegisterPage.css
Normal file
32
src/components/RegisterPage.css
Normal 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
|
||||
}
|
105
src/components/RegisterPage.tsx
Normal file
105
src/components/RegisterPage.tsx
Normal 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>
|
||||
);
|
||||
}
|
73
src/components/StatusPage.tsx
Normal file
73
src/components/StatusPage.tsx
Normal 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>
|
||||
);
|
||||
}
|
28
src/components/UptimeCard.css
Normal file
28
src/components/UptimeCard.css
Normal 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;
|
||||
}
|
|
@ -1,29 +1,52 @@
|
|||
import {UptimeRecord} from "../nws-api/types";
|
||||
import React from "react";
|
||||
import React, {useState} from "react";
|
||||
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(
|
||||
<div className={"nws-card row mb-2"} 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>}
|
||||
{props.uptime.url == null && <h3 className={"col-md-9 col-12"}>{props.uptime.name}</h3>}
|
||||
<div className={"nws-card row mb-2 m-0"} style={{maxWidth: '100%'}}>
|
||||
<h3 className={"col-md-9 col-12 uptime-lnk"} onClick={()=>setModalOpen(true)}>{props.uptime.name}</h3>
|
||||
|
||||
<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`}
|
||||
style={{width: "max-content"}}>
|
||||
>
|
||||
{props.uptime.isUp ? 'Up' : (props.uptime.undergoingMaintenance ? 'Maintenance' : 'Down')}
|
||||
</p>
|
||||
</div>
|
||||
<div className={`d-md-flex col-md-3 col-12 d-none justify-content-end`}>
|
||||
<p className={`fw-bold severity-label
|
||||
${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')}
|
||||
</p>
|
||||
</div>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
|
32
src/components/VerifyPage.css
Normal file
32
src/components/VerifyPage.css
Normal 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
|
||||
}
|
67
src/components/VerifyPage.tsx
Normal file
67
src/components/VerifyPage.tsx
Normal 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>
|
||||
|
||||
);
|
||||
}
|
|
@ -11,3 +11,16 @@ code {
|
|||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
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;
|
||||
}
|
||||
|
|
158
src/index.tsx
158
src/index.tsx
|
@ -1,14 +1,152 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import reportWebVitals from './reportWebVitals';
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import {
|
||||
createBrowserRouter, NavLink,
|
||||
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(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
function Layout (props: {children: any}) {
|
||||
return (
|
||||
<div>
|
||||
<Navbar sticky={"top"} style={{height: "8vh", backgroundColor: "#eee", paddingLeft: "5vw", paddingRight: "5vw", maxWidth:"100%"}} className={"row m-0"}>
|
||||
<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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {Incident, UptimeResponse} from "./types";
|
||||
import {Blog, Incident, Namespace, Service, SessionKey, UptimeResponse} from "./types";
|
||||
|
||||
export async function getUptime(): Promise<UptimeResponse> {
|
||||
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
156
src/nws-api/hooks.ts
Normal 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;
|
||||
}
|
|
@ -3,6 +3,9 @@ export type UptimeRecord = {
|
|||
url: string,
|
||||
uptimeMonth: number,
|
||||
uptimeAllTime: number,
|
||||
uptimeYtd: number,
|
||||
averageResponseTime: number,
|
||||
monitorStart: string,
|
||||
isUp: boolean,
|
||||
undergoingMaintenance: boolean
|
||||
};
|
||||
|
@ -10,6 +13,7 @@ export type UptimeRecord = {
|
|||
export type UptimeResponse = {
|
||||
datacenters: UptimeRecord[],
|
||||
services: UptimeRecord[],
|
||||
competitors: UptimeRecord[],
|
||||
lastUpdated: string
|
||||
};
|
||||
|
||||
|
@ -29,7 +33,43 @@ export type Incident = {
|
|||
};
|
||||
|
||||
enum IncidentSeverity {
|
||||
LOW,
|
||||
MEDIUM,
|
||||
HIGH
|
||||
LOW,
|
||||
MEDIUM,
|
||||
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
|
||||
}
|
||||
|
|
BIN
src/static/images/NWS_Logo_Transparent.png
Normal file
BIN
src/static/images/NWS_Logo_Transparent.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
Loading…
Reference in a new issue