try to show error page on fatal errors
This commit is contained in:
parent
3ac1ba4f23
commit
14645980af
|
|
@ -56,7 +56,7 @@ export async function loadCommandModule(cmd: string) {
|
||||||
let sysCmdWatcher
|
let sysCmdWatcher
|
||||||
let usrCmdWatcher
|
let usrCmdWatcher
|
||||||
function startWatchers() {
|
function startWatchers() {
|
||||||
expectDir(NOSE_BIN)
|
if (!expectDir(NOSE_BIN)) return
|
||||||
|
|
||||||
sysCmdWatcher = watch(NOSE_SYS_BIN, async (event, filename) =>
|
sysCmdWatcher = watch(NOSE_SYS_BIN, async (event, filename) =>
|
||||||
sendAll({ type: "commands", data: await commands() })
|
sendAll({ type: "commands", data: await commands() })
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ export function publishAppDNS(app: string) {
|
||||||
|
|
||||||
let wwwWatcher
|
let wwwWatcher
|
||||||
function startWatcher() {
|
function startWatcher() {
|
||||||
expectDir(NOSE_WWW)
|
if (!expectDir(NOSE_WWW)) return
|
||||||
|
|
||||||
wwwWatcher = watch(NOSE_WWW, (event, filename) => {
|
wwwWatcher = watch(NOSE_WWW, (event, filename) => {
|
||||||
const www = apps()
|
const www = apps()
|
||||||
|
|
|
||||||
10
src/fatal.ts
Normal file
10
src/fatal.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
////
|
||||||
|
// We want to show a blue screen of death if NOSE has a fatal error.
|
||||||
|
|
||||||
|
export let fatal: string | undefined
|
||||||
|
|
||||||
|
export function setFatal(error: string) {
|
||||||
|
console.error(error)
|
||||||
|
if (!fatal)
|
||||||
|
fatal = error
|
||||||
|
}
|
||||||
60
src/html/error.tsx
Normal file
60
src/html/error.tsx
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
import type { FC } from "hono/jsx"
|
||||||
|
import { css, js } from "../helpers"
|
||||||
|
|
||||||
|
export const Error: FC = async ({ error }) => (
|
||||||
|
<>
|
||||||
|
{css`
|
||||||
|
:root {
|
||||||
|
--letterbox: #CC8800;
|
||||||
|
--text: #FFBF40;
|
||||||
|
--bg: #201600;
|
||||||
|
}
|
||||||
|
* { color: var(--text); text-align: center; }
|
||||||
|
html { background: var(--letterbox); }
|
||||||
|
#content { background-color: var(--bg); }
|
||||||
|
|
||||||
|
h1 { max-width: 38%; margin: 0 auto; margin-top: 100px; }
|
||||||
|
h2 { max-width: 80%; margin: 0 auto; margin-top: 10px; }
|
||||||
|
p { max-width: 90%; margin: 0 auto; margin-top: 150px; }
|
||||||
|
|
||||||
|
.restart { max-width: 35%; }
|
||||||
|
.restart button {
|
||||||
|
font-size: 30px;
|
||||||
|
background: var(--letterbox);
|
||||||
|
color: var(--red);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 { animation: glow 3s ease-in-out infinite alternate; }
|
||||||
|
|
||||||
|
@keyframes glow {
|
||||||
|
0% {
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
text-shadow:
|
||||||
|
0 0 2px #FFAA33,
|
||||||
|
0 0 4px #FF8800,
|
||||||
|
0 0 6px #FF6600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
`}
|
||||||
|
|
||||||
|
{js`
|
||||||
|
window.addEventListener("click", async e => {
|
||||||
|
if (!e.target || !e.target.matches("button")) return
|
||||||
|
e.target.textContent = "[RESTARTING...]"
|
||||||
|
await fetch("/cmd/restart")
|
||||||
|
setTimeout(() => window.location.reload(), 3000)
|
||||||
|
})
|
||||||
|
`}
|
||||||
|
|
||||||
|
<h1>Fatal Error</h1>
|
||||||
|
<h2>NOSE failed to start properly.</h2>
|
||||||
|
<br />
|
||||||
|
<p>{error}</p>
|
||||||
|
<p class="restart">
|
||||||
|
<button>[RESTART]</button>
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
@ -3,12 +3,13 @@
|
||||||
|
|
||||||
import { $ } from "bun"
|
import { $ } from "bun"
|
||||||
import { NOSE_DIR } from "./config"
|
import { NOSE_DIR } from "./config"
|
||||||
import { isDir } from "./utils"
|
import { expectDir } from "./utils"
|
||||||
|
|
||||||
// Make NOSE_DIR if it doesn't exist
|
// Make NOSE_DIR if it doesn't exist
|
||||||
export async function initNoseDir() {
|
export async function initNoseDir() {
|
||||||
if (isDir(NOSE_DIR)) return
|
if (expectDir(NOSE_DIR)) return
|
||||||
|
|
||||||
await $`cp -r ./nose ${NOSE_DIR}`
|
await $`cp -r ./nose ${NOSE_DIR}`
|
||||||
|
expectDir(NOSE_DIR)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,17 +8,20 @@ import color from "kleur"
|
||||||
|
|
||||||
import type { Message } from "./shared/types"
|
import type { Message } from "./shared/types"
|
||||||
import { NOSE_ICON, NOSE_BIN, NOSE_WWW, NOSE_DATA, NOSE_DIR } from "./config"
|
import { NOSE_ICON, NOSE_BIN, NOSE_WWW, NOSE_DATA, NOSE_DIR } from "./config"
|
||||||
import { transpile, isFile, tilde } from "./utils"
|
import { transpile, isFile, tilde, isDir } from "./utils"
|
||||||
import { serveApp } from "./webapp"
|
import { serveApp } from "./webapp"
|
||||||
import { initDNS } from "./dns"
|
|
||||||
import { commands, commandPath, loadCommandModule } from "./commands"
|
import { commands, commandPath, loadCommandModule } from "./commands"
|
||||||
import { runCommandFn } from "./shell"
|
import { runCommandFn } from "./shell"
|
||||||
import { send, addWebsocket, removeWebsocket, closeWebsockets } from "./websocket"
|
import { send, addWebsocket, removeWebsocket, closeWebsockets } from "./websocket"
|
||||||
|
import { initSneakers, disconnectSneakers } from "./sneaker"
|
||||||
|
import { dispatchMessage } from "./dispatch"
|
||||||
|
import { fatal } from "./fatal"
|
||||||
|
|
||||||
import { Layout } from "./html/layout"
|
import { Layout } from "./html/layout"
|
||||||
import { Terminal } from "./html/terminal"
|
import { Terminal } from "./html/terminal"
|
||||||
import { dispatchMessage } from "./dispatch"
|
import { Error } from "./html/error"
|
||||||
import { initSneakers, disconnectSneakers } from "./sneaker"
|
|
||||||
|
import { initDNS } from "./dns"
|
||||||
import { initNoseDir } from "./nosedir"
|
import { initNoseDir } from "./nosedir"
|
||||||
import { initCommands } from "./commands"
|
import { initCommands } from "./commands"
|
||||||
|
|
||||||
|
|
@ -43,6 +46,16 @@ app.use("*", async (c, next) => {
|
||||||
console.log(fn(`${c.res.status} ${c.req.method} ${c.req.url} (${end - start}ms)`))
|
console.log(fn(`${c.res.status} ${c.req.method} ${c.req.url} (${end - start}ms)`))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.use("*", async (c, next) => {
|
||||||
|
const error = fatal ? fatal : !isDir(NOSE_DIR) ? `NOSE_DIR doesn't exist: ${NOSE_DIR}` : undefined
|
||||||
|
|
||||||
|
if (!error || (["/cmd/restart", "/cmd/reboot"].includes(c.req.path))) {
|
||||||
|
await next()
|
||||||
|
} else {
|
||||||
|
return c.html(<Layout><Error error={error} /></Layout>, 500)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
app.on("GET", ["/js/:path{.+}", "/shared/:path{.+}"], async c => {
|
app.on("GET", ["/js/:path{.+}", "/shared/:path{.+}"], async c => {
|
||||||
let path = "./src/" + c.req.path.replace("..", ".")
|
let path = "./src/" + c.req.path.replace("..", ".")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
// Shell utilities and helper functions.
|
// Shell utilities and helper functions.
|
||||||
|
|
||||||
import { statSync } from "fs"
|
import { statSync } from "fs"
|
||||||
import { basename } from "path"
|
import { setFatal } from "./fatal"
|
||||||
import { stat } from "fs/promises"
|
import { stat } from "fs/promises"
|
||||||
|
|
||||||
// Convert /Users/$USER or /home/$USER to ~ for simplicity
|
// Convert /Users/$USER or /home/$USER to ~ for simplicity
|
||||||
|
|
@ -17,11 +17,13 @@ export function untilde(path: string): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// End the process with an instructive error if a directory doesn't exist.
|
// End the process with an instructive error if a directory doesn't exist.
|
||||||
export function expectDir(path: string) {
|
export function expectDir(path: string): boolean {
|
||||||
if (!isDir(path)) {
|
if (!isDir(path)) {
|
||||||
console.error(`No ${basename(path)} directory detected.`)
|
setFatal(`Missing critical directory: ${path}`)
|
||||||
process.exit(1)
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is the given `path` a file?
|
// Is the given `path` a file?
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user