restat in cli and fix ws

This commit is contained in:
mbecker20
2022-03-21 02:46:24 -07:00
parent 875d1e6976
commit f2f626f367
20 changed files with 400 additions and 32 deletions
+5
View File
@@ -12,6 +12,7 @@ import Registry from "./components/deployment-config/Registry";
import CoreOrPeriphery from "./components/core-or-periphery/CoreOrPeriphery";
import { bound } from "./util/helpers/general";
import Setup from "./components/Setup";
import Restart from "./components/Restart";
type Page = {
title: string;
@@ -61,6 +62,10 @@ init().then(({ flags, dockerInstalled }) => {
title: "docker intro",
view: <Docker />,
},
{
title: "restart",
view: <Restart />,
},
!flags.core && !flags.periphery
? {
title: "core or periphery",
+164
View File
@@ -0,0 +1,164 @@
import React, { Fragment, useEffect } from "react";
import LabelledSelector from "./util/LabelledSelector";
import { useMainSequence } from "../cli";
import { useStore } from "../util/hooks";
import { Box, Newline, Text } from "ink";
import { Input } from "./util/Input";
import EnterToContinue from "./util/EnterToContinue";
import { CommandLogError } from "@monitor/types";
import { restart, RestartError } from "../util/helpers/restart";
type State = {
stage:
| "query"
| "name"
| "mongo"
| "confirm"
| "installing"
| "finished"
| "error";
name: string;
mongoUrl?: string;
result?: CommandLogError;
error?: RestartError;
};
const Restart = () => {
const { next, prev } = useMainSequence();
const [config, setConfig, setMany] = useStore<State>({
stage: "query",
name: "monitor-core",
});
const { stage, name, mongoUrl, result, error } = config;
useEffect(() => {
if (stage === "installing") {
restart({ name, mongoUrl: mongoUrl! }, (err) =>
setMany(["stage", "error"], ["error", err])
).then((success) => {
if (success) {
setMany(["stage", "finished"], ["result", success]);
}
});
} else if (stage === "finished" || stage === "error") {
process.exit();
}
}, [stage]);
if (stage === "query") {
return (
<LabelledSelector
label="What are you trying to do?"
items={["deploy monitor core", "restart monitor core"]}
onSelect={(option) => {
switch (option) {
case "deploy monitor core":
next();
break;
case "restart monitor core":
setConfig("stage", "name");
break;
}
}}
onEsc={prev}
vertical
/>
);
} else {
return (
<Box flexDirection="column">
<Text color="green">
name:{" "}
<Text color="white">
{stage === "name" ? (
<Input
initialValue={name}
onSubmit={(name) => setMany(["stage", "mongo"], ["name", name])}
onEsc={() => setConfig("stage", "query")}
/>
) : (
name
)}
</Text>
</Text>
{stage === "mongo" && (
<Text color="green">
mongo url:{" "}
<Text color="white">
<Input
initialValue={mongoUrl || "mongodb://127.0.0.1:27017/monitor"}
onSubmit={(mongoUrl) =>
setMany(["stage", "confirm"], ["mongoUrl", mongoUrl])
}
onEsc={() => setConfig("stage", "name")}
/>
</Text>
</Text>
)}
{mongoUrl && stage !== "mongo" && (
<Text color="green">
mongo url: <Text color="white">{mongoUrl}</Text>
</Text>
)}
<Newline />
{stage === "confirm" && (
<EnterToContinue
onEnter={() => {
setConfig("stage", "installing");
}}
onEsc={() => setConfig("stage", "mongo")}
pressEnterTo="restart monitor"
/>
)}
{(stage === "installing" || stage === "error") && (
<Fragment>
<Text>restarting...</Text>
</Fragment>
)}
{result && (
<Fragment>
<Text color="green">finished restarting</Text>
<Newline />
<Box flexDirection="column" marginLeft={2}>
<Text color="green">
command: <Text color="white">{result.command}</Text>
</Text>
{result.log.stderr ? (
<Text color="red">
stderr: <Text color="white">{result.log.stderr}</Text>
</Text>
) : undefined}
{result.log.stdout ? (
<Text color="blue">
stdout: <Text color="white">{result.log.stdout}</Text>
</Text>
) : undefined}
</Box>
<Newline />
</Fragment>
)}
{error && (
<Fragment>
<Newline />
<Text color="red">error restarting</Text>
<Newline />
<Text>{error.message}</Text>
<Text>{error.error}</Text>
<Newline />
</Fragment>
)}
</Box>
);
}
};
export default Restart;
+3 -2
View File
@@ -1,9 +1,10 @@
import React from "react";
import { Text } from "ink";
import { useEnter } from "../../util/hooks";
import { useEnter, useEsc } from "../../util/hooks";
const EnterToContinue = ({ onEnter, pressEnterTo }: { onEnter: () => void; pressEnterTo?: string }) => {
const EnterToContinue = ({ onEnter, pressEnterTo, onEsc }: { onEnter: () => void; pressEnterTo?: string; onEsc?: () => void; }) => {
useEnter(onEnter);
useEsc(() => onEsc && onEsc());
return (
<Text>
press{" "}
+3 -1
View File
@@ -6,6 +6,7 @@ const LabelledSelector = ({
label,
items,
onSelect,
onEsc,
vertical,
labelColor = "white",
}: {
@@ -14,6 +15,7 @@ const LabelledSelector = ({
items: string[];
onSelect?: (item: string, index: number) => void;
vertical?: boolean;
onEsc?: () => void;
}) => {
return (
<Box flexDirection={vertical ? "column" : "row"}>
@@ -23,7 +25,7 @@ const LabelledSelector = ({
label
)}
{vertical && <Newline />}
<Selector items={items} onSelect={onSelect} />
<Selector items={items} onSelect={onSelect} onEsc={onEsc} />
</Box>
);
};
+3
View File
@@ -4,6 +4,7 @@ import { Box, Text, useInput } from "ink";
const Selector = (p: {
items: string[];
onSelect?: (item: string, i: number) => void;
onEsc?: () => void;
}) => {
const [highlighted, setHighlighted] = useState(0);
useInput((_, key) => {
@@ -13,6 +14,8 @@ const Selector = (p: {
setHighlighted(Math.min(highlighted + 1, p.items.length - 1));
} else if (key.return) {
if (p.onSelect) p.onSelect(p.items[highlighted]!, highlighted);
} else if (key.escape) {
if (p.onEsc) p.onEsc();
}
});
return (
+4 -1
View File
@@ -8,7 +8,8 @@ const YesNo = ({
onSelect,
vertical,
labelColor,
noYes
noYes,
onEsc
}: {
label: ReactNode;
onYes?: () => void;
@@ -17,6 +18,7 @@ const YesNo = ({
vertical?: boolean;
labelColor?: "green" | "white";
noYes?: boolean;
onEsc?: () => void;
}) => {
return (
<LabelledSelector
@@ -32,6 +34,7 @@ const YesNo = ({
}}
vertical={vertical}
labelColor={labelColor}
onEsc={onEsc}
/>
);
};
+1
View File
@@ -107,3 +107,4 @@ async function createNetwork() {
const command = `docker network create ${DOCKER_NETWORK}`;
return await execute(command);
}
+82 -1
View File
@@ -1,4 +1,4 @@
import { CommandLogError } from "@monitor/types";
import { CommandLogError, Conversion, DockerRunArgs, EnvironmentVar, Volume } from "@monitor/types";
import { execute } from "./execute";
export type InstallLog = {
@@ -102,3 +102,84 @@ export async function isDockerInstalled() {
const res = await execute("docker ps");
return !res.isError;
}
export async function deleteContainer(containerName: string) {
return await execute(
`docker stop ${containerName} && docker container rm ${containerName}`
);
}
/* Docker Run for Deployments */
export async function dockerRun(
{
image,
latest,
ports,
environment,
network,
volumes,
restart,
postImage,
containerName,
containerUser,
}: DockerRunArgs
) {
const command =
`docker run -d` +
name(containerName) +
containerUserString(containerUser) +
portsString(ports) +
volsString(volumes) +
envString(environment) +
restartString(restart) +
networkString(network) +
` ${image}${latest ? ":latest" : ""}${postImage ? " " + postImage : ""}`;
return await execute(command);
}
function name(containerName?: string) {
return containerName ? ` --name ${containerName}` : "";
}
function portsString(ports?: Conversion[]) {
return ports && ports.length > 0
? ports
.map(({ local, container }) => ` -p ${local}:${container}`)
.reduce((prev, curr) => prev + curr)
: "";
}
function volsString(volumes?: Volume[]) {
return volumes && volumes.length > 0
? volumes
.map(({ local, container }) => {
return ` -v ${local}:${container}`;
})
.reduce((prev, curr) => prev + curr)
: "";
}
function restartString(restart?: string) {
return restart
? ` --restart=${restart}${restart === "on-failure" ? ":10" : ""}`
: "";
}
function envString(environment?: EnvironmentVar[]) {
return environment && environment.length > 0
? environment
.map(({ variable, value }) => ` -e "${variable}=${value}"`)
.reduce((prev, curr) => prev + curr)
: "";
}
function networkString(network?: string) {
return network ? ` --network ${network}` : "";
}
function containerUserString(containerUser?: string) {
return containerUser && containerUser.length > 0
? ` -u ${containerUser}`
: "";
}
+37
View File
@@ -0,0 +1,37 @@
import { getCoreDeployment } from "../mongoose/deployment";
import { deleteContainer, dockerRun } from "./docker";
export type RestartError = {
message: string;
error: string;
}
export async function restart(
args: { name: string; mongoUrl: string },
onError: (err: RestartError) => void
) {
try {
const deployment = await getCoreDeployment(args);
if (deployment) {
try {
await deleteContainer(deployment.containerName!);
return await dockerRun(deployment);
} catch (error) {
onError({
message: "failed to restart container",
error: JSON.stringify(error),
});
}
} else {
onError({
message: "could not find deployment at name",
error: "",
});
}
} catch (error) {
onError({
message: "failed to connect to mongo at url",
error: JSON.stringify(error),
});
}
}
+12 -6
View File
@@ -1,4 +1,5 @@
import { Deployment } from "@monitor/types";
import mongoose from "mongoose";
import { model, Schema } from "mongoose";
export default function deploymentModel() {
@@ -7,11 +8,6 @@ export default function deploymentModel() {
container: String,
});
const Volume = new Schema({
variable: String,
value: String,
});
const EnvironmentVar = new Schema({
variable: String,
value: String,
@@ -27,7 +23,7 @@ export default function deploymentModel() {
image: String, // used if deploying an external image (from docker hub)
latest: Boolean, // if custom image, use this to add :latest
ports: [Conversion],
volumes: [Volume],
volumes: [Conversion],
environment: [EnvironmentVar],
network: String,
restart: String,
@@ -42,3 +38,13 @@ export default function deploymentModel() {
return model("Deployment", schema)
}
export async function getCoreDeployment({ name, mongoUrl }: { name: string; mongoUrl: string }) {
await mongoose.connect(mongoUrl);
const deployments = deploymentModel();
return (await deployments.findOne({ name }).lean().exec()) as
| Deployment
| undefined;
}
+11 -4
View File
@@ -27,18 +27,25 @@ const ws = fp((app: FastifyInstance, _: {}, done: () => void) => {
}
);
app.get("/ws", { websocket: true }, async (connection) => {
app.get("/ws", { websocket: true }, async (connection) => {
connection.socket.on("message", async (msg) => {
const jwt = JSON.parse(msg.toString()).token;
if (app.jwt.verify(jwt)) {
if (jwt && app.jwt.verify(jwt)) {
const payload = decode(jwt) as { id: string };
const userID = payload.id;
const user = await app.users.findById(userID);
if (user) {
const unsub = messages.subscribe((msg) => connection.socket.send(msg));
const unsub = messages.subscribe((msg) =>
connection.socket.send(msg)
);
connection.socket.removeAllListeners("message");
connection.socket.on("message", (msg) =>
handleMessage(app, connection.socket, JSON.parse(msg.toString()), user)
handleMessage(
app,
connection.socket,
JSON.parse(msg.toString()),
user
)
);
connection.socket.on("close", unsub);
connection.socket.send(
+11 -6
View File
@@ -4,19 +4,24 @@ import Flex from "./util/layout/Flex";
import Grid from "./util/layout/Grid";
import { User } from "@monitor/types";
import { pushNotification } from "..";
import { useUser } from "../state/UserProvider";
import { useAppState } from "../state/StateProvider";
const UserInfo: Component<{ user: User; logout: () => void }> = (p) => {
const UserInfo: Component<{}> = (p) => {
const { user, logout } = useUser();
const { ws } = useAppState();
return (
<Grid style={{ "font-size": "2rem" }}>
<div>provider: {getAuthProvider(p.user)}</div>
<div>provider: {getAuthProvider(user() as User)}</div>
<Flex alignItems="center">
<div>username: {p.user.username}</div>
<Show when={p.user.avatar}>
<img src={p.user.avatar} style={{ width: "2rem", height: "2rem" }} />
<div>username: {user().username}</div>
<Show when={user().avatar}>
<img src={user().avatar} style={{ width: "2rem", height: "2rem" }} />
</Show>
</Flex>
<button style={{ width: "100%" }} onClick={() => {
p.logout();
logout();
ws.close();
pushNotification("ok", "logged out");
}}>
logout
@@ -0,0 +1,3 @@
.Deployment {
}
@@ -0,0 +1,14 @@
import { Component } from "solid-js";
import { useAppState } from "../../state/StateProvider";
import s from "./Deployment.module.css";
const Deployment: Component<{}> = (p) => {
const { servers, deployments } = useAppState();
return (
<div class={s.Deployment} >
</div>
);
}
export default Deployment;
@@ -0,0 +1,13 @@
import { Component } from "solid-js";
import { useToggle } from "../../../util/hooks";
const Config: Component<{}> = (p) => {
const [editing, toggleEditing] = useToggle();
return (
<div>
</div>
);
}
export default Config;
@@ -0,0 +1,11 @@
import { Component } from "solid-js";
const ErrorLog: Component<{}> = (p) => {
return (
<div>
</div>
);
}
export default ErrorLog;
@@ -0,0 +1,11 @@
import { Component } from "solid-js";
const Log: Component<{}> = (p) => {
return (
<div>
</div>
);
}
export default Log;
+1 -1
View File
@@ -2,7 +2,7 @@
import { render } from "solid-js/web";
import "./index.css";
import App from "./components/App/App";
import App from "./components/app/App";
import Client from "./util/client";
import makeNotifications from "./components/util/notification/Notifications";
import { UserProvider } from "./state/UserProvider";
+4 -2
View File
@@ -3,7 +3,8 @@ import { Component, createContext, createResource, Resource, Setter, useContext
import { client } from "..";
export type UserState = {
user: Resource<false | User | undefined>;
userResource: Resource<false | User | undefined>;
user: () => User;
setUser: Setter<false | User | undefined>;
logout: () => void;
username: () => string | undefined;
@@ -25,7 +26,8 @@ export const UserProvider: Component = (p) => {
}
};
const context: UserState = {
user,
userResource: user,
user: () => user() as User,
setUser: mutate,
logout,
username,
+7 -8
View File
@@ -17,8 +17,8 @@ export async function prune() {
export async function allContainerStatus(dockerode: Dockerode) {
const statusAr = await dockerode.listContainers({ all: true });
const statusNames = statusAr.map((stat) =>
stat.Names[0]?.slice(1, stat.Names[0]?.length) || stat.Id
const statusNames = statusAr.map(
(stat) => stat.Names[0]?.slice(1, stat.Names[0]?.length) || stat.Id
); // they all start with '/'
return objFrom2Arrays(
statusNames,
@@ -114,13 +114,13 @@ export async function dockerRun(
envString(environment) +
restartString(restart) +
networkString(network) +
` ${image}${latest && ":latest"}${postImage && " " + postImage}`;
` ${image}${latest ? ":latest" : ""}${postImage ? " " + postImage : ""}`;
return await execute(command);
}
function name(containerName?: string) {
return containerName && ` --name ${containerName}`;
return containerName ? ` --name ${containerName}` : "";
}
function portsString(ports?: Conversion[]) {
@@ -153,10 +153,9 @@ function repoVolume(
repoMount?: { repoFolder: string; containerMount: string }
) {
// repo root should be SYSROOT + "repos/"
return (
repoMount &&
` -v ${repoMount.repoFolder + containerName}:${repoMount.containerMount}`
);
return repoMount
? ` -v ${repoMount.repoFolder + containerName}:${repoMount.containerMount}`
: "";
}
function restartString(restart?: string) {