mirror of
https://github.com/thatmattlove/hyperglass.git
synced 2026-01-17 08:48:05 +00:00
add app-level getInitialProps for full SSR; add UI build process to web app
This commit is contained in:
parent
9ca90cc2ae
commit
37f3194bbc
5 changed files with 122 additions and 30 deletions
|
|
@ -54,11 +54,10 @@ async def build_ui():
|
|||
"""
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
import ujson as json
|
||||
|
||||
ui_dir = Path(__file__).parent.parent / "ui"
|
||||
|
||||
yarn_command = "yarn --silent --emoji false --json --no-progress build"
|
||||
yarn_command = "yarn --silent --emoji false --no-progress build"
|
||||
try:
|
||||
proc = await asyncio.create_subprocess_shell(
|
||||
cmd=yarn_command,
|
||||
|
|
@ -68,20 +67,17 @@ async def build_ui():
|
|||
)
|
||||
|
||||
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=60)
|
||||
output_out = json.loads(stdout.decode("utf-8").split("\n")[0])
|
||||
messages = stdout.decode("utf-8").strip()
|
||||
errors = stderr.decode("utf-8").strip()
|
||||
|
||||
if proc.returncode != 0:
|
||||
output_error = json.loads(stderr.decode("utf-8").strip("\n"))
|
||||
raise RuntimeError(
|
||||
f'Error building web assets with script {output_out["data"]}:'
|
||||
f'{output_error["data"]}'
|
||||
)
|
||||
raise RuntimeError(f"\nMessages:\n{messages}\nErrors:\n{errors}")
|
||||
|
||||
await proc.wait()
|
||||
except Exception as e:
|
||||
raise RuntimeError(str(e))
|
||||
|
||||
return output_out["data"]
|
||||
return messages
|
||||
|
||||
|
||||
async def write_env(variables):
|
||||
|
|
@ -158,3 +154,100 @@ async def clear_redis_cache(db, config):
|
|||
except Exception as e:
|
||||
raise RuntimeError(f"Error clearing cache: {str(e)}") from None
|
||||
return True
|
||||
|
||||
|
||||
async def build_frontend(dev_mode, dev_url, prod_url, params, force=False):
|
||||
"""Perform full frontend UI build process.
|
||||
|
||||
Securely creates temporary file, writes frontend configuration
|
||||
parameters to file as JSON. Then writes the name of the temporary
|
||||
file to /tmp/hyperglass.env.json as {"configFile": <file_name> }.
|
||||
|
||||
Webpack reads /tmp/hyperglass.env.json, loads the temporary file,
|
||||
and sets its contents to Node environment variables during the build
|
||||
process.
|
||||
|
||||
After the build is successful, the temporary file is automatically
|
||||
closed during garbage collection.
|
||||
|
||||
Arguments:
|
||||
dev_mode {bool} -- Development Mode
|
||||
dev_url {str} -- Development Mode URL
|
||||
prod_url {str} -- Production Mode URL
|
||||
params {dict} -- Frontend Config paramters
|
||||
|
||||
Raises:
|
||||
RuntimeError: Raised if errors occur during build process.
|
||||
|
||||
Returns:
|
||||
{bool} -- True if successful
|
||||
"""
|
||||
import hashlib
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from aiofile import AIOFile
|
||||
import ujson as json
|
||||
|
||||
env_file = Path("/tmp/hyperglass.env.json") # noqa: S108
|
||||
|
||||
env_vars = {"_HYPERGLASS_CONFIG_": params}
|
||||
# Set NextJS production/development mode and base URL based on
|
||||
# developer_mode setting.
|
||||
if dev_mode:
|
||||
env_vars.update({"NODE_ENV": "development", "_HYPERGLASS_URL_": dev_url})
|
||||
else:
|
||||
env_vars.update({"NODE_ENV": "production", "_HYPERGLASS_URL_": prod_url})
|
||||
|
||||
try:
|
||||
env_json = json.dumps(env_vars)
|
||||
|
||||
# Create SHA256 hash from all parameters passed to UI, use as
|
||||
# build identifier.
|
||||
build_id = hashlib.sha256(env_json.encode()).hexdigest()
|
||||
|
||||
# Read hard-coded environment file from last build. If build ID
|
||||
# matches this build's ID, don't run a new build.
|
||||
if env_file.exists() and not force:
|
||||
async with AIOFile(env_file, "r") as ef:
|
||||
ef_json = await ef.read()
|
||||
ef_id = json.loads(ef_json).get("buildId", "empty")
|
||||
|
||||
if ef_id == build_id:
|
||||
log.debug(
|
||||
"No changes to UI parameters since last build, skipping..."
|
||||
)
|
||||
return True
|
||||
|
||||
# Create temporary file. .json file extension is added for easy
|
||||
# webpack JSON parsing.
|
||||
temp_file = tempfile.NamedTemporaryFile(
|
||||
mode="w+", prefix="hyperglass_", suffix=".json", delete=not dev_mode
|
||||
)
|
||||
log.debug(
|
||||
f"Created temporary UI config file: '{temp_file.name}' for build {build_id}"
|
||||
)
|
||||
|
||||
async with AIOFile(temp_file.name, "w+") as temp:
|
||||
await temp.write(env_json)
|
||||
await temp.fsync()
|
||||
|
||||
# Write "permanent" file (hard-coded named) for Node to read.
|
||||
async with AIOFile(env_file, "w+") as ef:
|
||||
await ef.write(
|
||||
json.dumps({"configFile": temp_file.name, "buildId": build_id})
|
||||
)
|
||||
await ef.fsync()
|
||||
|
||||
# While temporary file is still open, initiate UI build process.
|
||||
if not dev_mode or force:
|
||||
build_result = await build_ui()
|
||||
|
||||
if build_result:
|
||||
log.debug("Completed UI build")
|
||||
elif dev_mode and not force:
|
||||
log.debug("Running in developer mode, did not run `yarn build`")
|
||||
|
||||
except Exception as e:
|
||||
raise RuntimeError(str(e))
|
||||
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
const aliases = require("./.alias");
|
||||
const envVars = require("/tmp/hyperglass.env.json");
|
||||
const { configFile } = envVars;
|
||||
const config = require(String(configFile));
|
||||
|
||||
module.exports = {
|
||||
webpack(config) {
|
||||
|
|
@ -9,5 +12,10 @@ module.exports = {
|
|||
};
|
||||
return config;
|
||||
},
|
||||
poweredByHeader: false
|
||||
poweredByHeader: false,
|
||||
env: {
|
||||
_NODE_ENV_: config.NODE_ENV,
|
||||
_HYPERGLASS_URL_: config._HYPERGLASS_URL_,
|
||||
_HYPERGLASS_CONFIG_: config._HYPERGLASS_CONFIG_
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@
|
|||
const express = require("express");
|
||||
const next = require("next");
|
||||
const envVars = require("/tmp/hyperglass.env.json");
|
||||
const env = envVars.NODE_ENV;
|
||||
const envUrl = envVars._HYPERGLASS_URL_;
|
||||
const { configFile } = envVars;
|
||||
const config = require(String(configFile));
|
||||
|
||||
const { NODE_ENV: env, _HYPERGLASS_URL_: envUrl } = config;
|
||||
|
||||
const devProxy = {
|
||||
"/api/config": { target: envUrl + "config", pathRewrite: { "^/api/config": "" } },
|
||||
"/api/query": { target: envUrl + "query", pathRewrite: { "^/api/query": "" } },
|
||||
"/api/query/": { target: envUrl + "query/", pathRewrite: { "^/api/query/": "" } },
|
||||
"/images": { target: envUrl + "images", pathRewrite: { "^/images": "" } }
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,13 @@
|
|||
import React from "react";
|
||||
import useAxios from "axios-hooks";
|
||||
import { HyperglassProvider } from "~/components/HyperglassProvider";
|
||||
import PreConfig from "~/components/PreConfig";
|
||||
|
||||
const config = process.env._HYPERGLASS_CONFIG_;
|
||||
|
||||
const Hyperglass = ({ Component, pageProps }) => {
|
||||
const [{ data, loading, error }, refetch] = useAxios({
|
||||
url: "/api/config",
|
||||
method: "get"
|
||||
});
|
||||
return (
|
||||
<>
|
||||
{!data ? (
|
||||
<PreConfig loading={loading} error={error} refresh={refetch} />
|
||||
) : (
|
||||
<HyperglassProvider config={data}>
|
||||
<Component {...pageProps} />
|
||||
</HyperglassProvider>
|
||||
)}
|
||||
</>
|
||||
<HyperglassProvider config={config}>
|
||||
<Component {...pageProps} />
|
||||
</HyperglassProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ const ErrorPage = ({ msg, statusCode }) => {
|
|||
|
||||
ErrorPage.getInitialProps = ({ res, err }) => {
|
||||
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
|
||||
const msg = err ? err.message : res.req.url;
|
||||
const msg = err ? err.message : res.req?.url || "Error";
|
||||
return { msg, statusCode };
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue