mirror of
https://github.com/moghtech/komodo.git
synced 2026-09-09 08:01:31 +00:00
restat in cli and fix ws
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
@@ -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{" "}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -107,3 +107,4 @@ async function createNetwork() {
|
||||
const command = `docker network create ${DOCKER_NETWORK}`;
|
||||
return await execute(command);
|
||||
}
|
||||
|
||||
|
||||
@@ -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}`
|
||||
: "";
|
||||
}
|
||||
@@ -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),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
@@ -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";
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user