Merge branch 'done' of https://github.com/nickorlow/nws-site into done
This commit is contained in:
commit
ddb20ee17c
|
@ -2,10 +2,21 @@
|
|||
<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/CreateCruisePage.tsx" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/components/DashboardPage.tsx" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/components/Login.css" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/components/LoginPage.tsx" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/components/NotFoundPage.tsx" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/components/RegisterPage.css" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/components/RegisterPage.tsx" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/components/VerifyPage.css" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/components/VerifyPage.tsx" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/nws-api/hooks.ts" 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$/src/App.css" beforeDir="false" afterPath="$PROJECT_DIR$/src/App.css" 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/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,6 +26,8 @@
|
|||
<component name="FileTemplateManagerImpl">
|
||||
<option name="RECENT_TEMPLATES">
|
||||
<list>
|
||||
<option value="HTTP Request" />
|
||||
<option value="CSS File" />
|
||||
<option value="TypeScript File" />
|
||||
<option value="TypeScript JSX File" />
|
||||
</list>
|
||||
|
@ -32,21 +45,19 @@
|
|||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<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="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" />
|
||||
</component>
|
||||
<component name="PropertiesComponent"><![CDATA[{
|
||||
"keyToString": {
|
||||
"WebServerToolWindowFactoryState": "false",
|
||||
"last_opened_file_path": "/home/nickorlow/programmming/personal/nws-site/src/components",
|
||||
"list.type.of.created.stylesheet": "CSS",
|
||||
"nodejs_package_manager_path": "npm",
|
||||
"ts.external.directory.path": "/home/nickorlow/programmming/personal/nws-site/node_modules/typescript/lib",
|
||||
"vue.rearranger.settings.migration": "true"
|
||||
}
|
||||
}]]></component>
|
||||
<component name="RecentsManager">
|
||||
<key name="CopyFile.RECENT_KEYS">
|
||||
<recent name="$PROJECT_DIR$/src/components" />
|
||||
<recent name="$PROJECT_DIR$/src/static/images" />
|
||||
</key>
|
||||
</component>
|
||||
|
@ -69,7 +80,8 @@
|
|||
<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" />
|
||||
</task>
|
||||
<servers />
|
||||
</component>
|
||||
|
|
1630
package-lock.json
generated
1630
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -11,11 +11,14 @@
|
|||
"@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-router-dom": "^6.4.2",
|
||||
"react-scripts": "5.0.0",
|
||||
"react-tooltip": "^4.4.3",
|
||||
"strip-markdown": "^5.0.0",
|
||||
"typescript": "^4.5.5",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
|
|
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;
|
||||
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>
|
||||
);
|
||||
}
|
8
src/components/CreateCruisePage.tsx
Normal file
8
src/components/CreateCruisePage.tsx
Normal file
|
@ -0,0 +1,8 @@
|
|||
export default function CreateCruisePage() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Create Cruise</h1>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
35
src/components/DashboardPage.tsx
Normal file
35
src/components/DashboardPage.tsx
Normal file
|
@ -0,0 +1,35 @@
|
|||
import {Account, Service} from "../nws-api/types";
|
||||
import {useGetAccountServices, useLoggedInRedirect, useNWSAccount} from "../nws-api/hooks";
|
||||
|
||||
|
||||
export default function DashboardPage() {
|
||||
useLoggedInRedirect();
|
||||
let account: Account | undefined = useNWSAccount();
|
||||
let services: Service[] = useGetAccountServices();
|
||||
|
||||
return(
|
||||
<div style={{minHeight: "100vh", padding: "50px"}}>
|
||||
<h1>Welcome to NWS, {account?.name}!</h1>
|
||||
<hr/>
|
||||
<div className={"d-flex justify-content-between"}>
|
||||
<h2>Your NWS Cruise™ Services</h2>
|
||||
<button onClick={(e) => {window.location.href = "/cruise/new"}}>Create Cruise Service</button>
|
||||
</div>
|
||||
{/*<h2>Your NWS Write™ Blogs</h2>*/}
|
||||
<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>
|
||||
<p><b>Deployment Key</b></p>
|
||||
<a href={"#regen"}>Regenerate Deploy Key</a>
|
||||
</div>
|
||||
</div>);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
9
src/components/Footer.tsx
Normal file
9
src/components/Footer.tsx
Normal file
|
@ -0,0 +1,9 @@
|
|||
import React from "react";
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer style={{margin: 25}}>
|
||||
NWS is owned and operated by <a href={"http://nickorlow.com"}>Nicholas Orlowsky</a>.
|
||||
</footer>
|
||||
);
|
||||
}
|
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>
|
||||
);
|
||||
}
|
93
src/components/StatusPage.tsx
Normal file
93
src/components/StatusPage.tsx
Normal file
|
@ -0,0 +1,93 @@
|
|||
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: [], 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>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>
|
||||
</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;
|
||||
}
|
||||
|
|
155
src/index.tsx
155
src/index.tsx
|
@ -1,14 +1,149 @@
|
|||
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.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";
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
function Layout (props: {children: any}) {
|
||||
return (
|
||||
<div>
|
||||
<Navbar sticky={"top"} style={{backgroundColor: "#eee", paddingLeft: 100, paddingRight: 100}} className={"row"}>
|
||||
<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"}>
|
||||
{ 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>
|
||||
{props.children}
|
||||
<Footer/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const router = createBrowserRouter([
|
||||
{
|
||||
path: "/",
|
||||
element:
|
||||
<Layout>
|
||||
<StatusPage/>
|
||||
</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, 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,27 @@ 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;
|
||||
}
|
||||
|
||||
|
||||
|
|
90
src/nws-api/hooks.ts
Normal file
90
src/nws-api/hooks.ts
Normal file
|
@ -0,0 +1,90 @@
|
|||
import {useEffect, useState} from "react";
|
||||
import {Account, 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 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;
|
||||
}
|
|
@ -33,3 +33,35 @@ enum IncidentSeverity {
|
|||
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
|
||||
};
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue