This commit is contained in:
Nicholas Orlowsky 2023-01-07 11:29:58 -06:00
commit ddb20ee17c
No known key found for this signature in database
GPG key ID: 3845F78A73B14100
23 changed files with 2498 additions and 170 deletions

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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"
},

View file

@ -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
}

View file

@ -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
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,8 @@
export default function CreateCruisePage() {
return (
<div>
<h1>Create Cruise</h1>
</div>
);
}

View 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>
);
}

View 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
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,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>
);
}

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',
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,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

View file

@ -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
View 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;
}

View file

@ -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
};