mirror of
https://github.com/thatmattlove/hyperglass.git
synced 2026-01-17 08:48:05 +00:00
Closes #175: Remove usage of hyperglass.env.json
This commit is contained in:
parent
b49b6ea58e
commit
8aeb8036ff
17 changed files with 224 additions and 195 deletions
4
hyperglass/ui/.eslintignore
Normal file
4
hyperglass/ui/.eslintignore
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
node_modules
|
||||
dist
|
||||
.next/
|
||||
favicon-formats.ts
|
||||
1
hyperglass/ui/.gitignore
vendored
1
hyperglass/ui/.gitignore
vendored
|
|
@ -1,4 +1,5 @@
|
|||
.DS_Store
|
||||
.env*
|
||||
*.tsbuildinfo
|
||||
# dev/test files
|
||||
TODO.txt
|
||||
|
|
|
|||
|
|
@ -5,4 +5,5 @@ yarn.lock
|
|||
package-lock.json
|
||||
.eslintrc
|
||||
tsconfig.json
|
||||
.next/
|
||||
.next/
|
||||
favicon-formats.ts
|
||||
|
|
|
|||
11
hyperglass/ui/components/favicon.tsx
Normal file
11
hyperglass/ui/components/favicon.tsx
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import type { Favicon as FaviconProps } from '~/types';
|
||||
|
||||
/**
|
||||
* Render a `<link/>` element to reference a server-side favicon.
|
||||
*/
|
||||
export const Favicon = (props: FaviconProps): JSX.Element => {
|
||||
const { image_format, dimensions, prefix, rel } = props;
|
||||
const [w, h] = dimensions;
|
||||
const src = `/images/favicons/${prefix}-${w}x${h}.${image_format}`;
|
||||
return <link rel={rel ?? ''} href={src} type={`image/${image_format}`} />;
|
||||
};
|
||||
|
|
@ -2,6 +2,7 @@ export * from './card';
|
|||
export * from './codeBlock';
|
||||
export * from './countdown';
|
||||
export * from './debugger';
|
||||
export * from './favicon';
|
||||
export * from './footer';
|
||||
export * from './form';
|
||||
export * from './greeting';
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { useTheme } from '@chakra-ui/react';
|
|||
import { useConfig } from '~/context';
|
||||
import { googleFontUrl } from '~/util';
|
||||
|
||||
export const Meta: React.FC = () => {
|
||||
export const Meta = (): JSX.Element => {
|
||||
const config = useConfig();
|
||||
const { fonts } = useTheme();
|
||||
const [location, setLocation] = useState('/');
|
||||
|
|
@ -12,22 +12,6 @@ export const Meta: React.FC = () => {
|
|||
const {
|
||||
siteTitle: title = 'hyperglass',
|
||||
siteDescription: description = 'Network Looking Glass',
|
||||
siteKeywords: keywords = [
|
||||
'hyperglass',
|
||||
'looking glass',
|
||||
'lg',
|
||||
'peer',
|
||||
'peering',
|
||||
'ipv4',
|
||||
'ipv6',
|
||||
'transit',
|
||||
'community',
|
||||
'communities',
|
||||
'bgp',
|
||||
'routing',
|
||||
'network',
|
||||
'isp',
|
||||
],
|
||||
} = useConfig();
|
||||
|
||||
const siteName = `${title} - ${description}`;
|
||||
|
|
@ -42,8 +26,7 @@ export const Meta: React.FC = () => {
|
|||
|
||||
return (
|
||||
<Head>
|
||||
<title>{title}</title>
|
||||
<meta name="language" content="en" />
|
||||
<title key="title">{title}</title>
|
||||
<meta name="url" content={location} />
|
||||
<meta name="og:title" content={title} />
|
||||
<meta name="og:url" content={location} />
|
||||
|
|
@ -52,8 +35,7 @@ export const Meta: React.FC = () => {
|
|||
<meta name="description" content={description} />
|
||||
<meta property="og:image:alt" content={siteName} />
|
||||
<meta name="og:description" content={description} />
|
||||
<meta name="keywords" content={keywords.join(', ')} />
|
||||
<meta name="hg-version" content={config.version} />
|
||||
<meta name="hyperglass-version" content={config.version} />
|
||||
</Head>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
1
hyperglass/ui/favicon-formats.ts
Normal file
1
hyperglass/ui/favicon-formats.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
import type { Favicon } from '~/types';export default [{"dimensions": [64, 64], "image_format": "ico", "prefix": "favicon", "rel": null}, {"dimensions": [16, 16], "image_format": "png", "prefix": "favicon", "rel": "icon"}, {"dimensions": [32, 32], "image_format": "png", "prefix": "favicon", "rel": "icon"}, {"dimensions": [64, 64], "image_format": "png", "prefix": "favicon", "rel": "icon"}, {"dimensions": [96, 96], "image_format": "png", "prefix": "favicon", "rel": "icon"}, {"dimensions": [180, 180], "image_format": "png", "prefix": "favicon", "rel": "icon"}, {"dimensions": [57, 57], "image_format": "png", "prefix": "apple-touch-icon", "rel": "apple-touch-icon"}, {"dimensions": [60, 60], "image_format": "png", "prefix": "apple-touch-icon", "rel": "apple-touch-icon"}, {"dimensions": [72, 72], "image_format": "png", "prefix": "apple-touch-icon", "rel": "apple-touch-icon"}, {"dimensions": [76, 76], "image_format": "png", "prefix": "apple-touch-icon", "rel": "apple-touch-icon"}, {"dimensions": [114, 114], "image_format": "png", "prefix": "apple-touch-icon", "rel": "apple-touch-icon"}, {"dimensions": [120, 120], "image_format": "png", "prefix": "apple-touch-icon", "rel": "apple-touch-icon"}, {"dimensions": [144, 144], "image_format": "png", "prefix": "apple-touch-icon", "rel": "apple-touch-icon"}, {"dimensions": [152, 152], "image_format": "png", "prefix": "apple-touch-icon", "rel": "apple-touch-icon"}, {"dimensions": [167, 167], "image_format": "png", "prefix": "apple-touch-icon", "rel": "apple-touch-icon"}, {"dimensions": [180, 180], "image_format": "png", "prefix": "apple-touch-icon", "rel": "apple-touch-icon"}, {"dimensions": [70, 70], "image_format": "png", "prefix": "mstile", "rel": null}, {"dimensions": [270, 270], "image_format": "png", "prefix": "mstile", "rel": null}, {"dimensions": [310, 310], "image_format": "png", "prefix": "mstile", "rel": null}, {"dimensions": [310, 150], "image_format": "png", "prefix": "mstile", "rel": null}, {"dimensions": [196, 196], "image_format": "png", "prefix": "favicon", "rel": "shortcut icon"}] as Favicon[];
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
const envData = require('/tmp/hyperglass.env.json');
|
||||
|
||||
/**
|
||||
* @type {import('next').NextConfig}
|
||||
*/
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
poweredByHeader: false,
|
||||
env: { ...envData },
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,21 +2,9 @@
|
|||
const express = require('express');
|
||||
const proxyMiddleware = require('http-proxy-middleware');
|
||||
const next = require('next');
|
||||
const config = require('/tmp/hyperglass.env.json');
|
||||
|
||||
const {
|
||||
env: { NODE_ENV },
|
||||
hyperglass: { url },
|
||||
} = config;
|
||||
|
||||
const devProxy = {
|
||||
'/api/query/': { target: url + 'api/query/', pathRewrite: { '^/api/query/': '' } },
|
||||
'/ui/props/': { target: url + 'ui/props/', pathRewrite: { '^/ui/props/': '' } },
|
||||
'/images': { target: url + 'images', pathRewrite: { '^/images': '' } },
|
||||
};
|
||||
|
||||
const port = parseInt(process.env.PORT, 10) || 3000;
|
||||
const dev = NODE_ENV !== 'production';
|
||||
const dev = process.env.NODE_ENV !== 'production';
|
||||
const app = next({
|
||||
dir: '.', // base directory where everything is, could move to src later
|
||||
dev,
|
||||
|
|
@ -30,8 +18,20 @@ app
|
|||
.then(() => {
|
||||
server = express();
|
||||
|
||||
const devProxy = {
|
||||
'/api/query/': {
|
||||
target: process.env.HYPERGLASS_URL + 'api/query/',
|
||||
pathRewrite: { '^/api/query/': '' },
|
||||
},
|
||||
'/ui/props/': {
|
||||
target: process.env.HYPERGLASS_URL + 'ui/props/',
|
||||
pathRewrite: { '^/ui/props/': '' },
|
||||
},
|
||||
'/images': { target: process.env.HYPERGLASS_URL + 'images', pathRewrite: { '^/images': '' } },
|
||||
};
|
||||
|
||||
// Set up the proxy.
|
||||
if (dev && devProxy) {
|
||||
if (dev) {
|
||||
Object.keys(devProxy).forEach(context => {
|
||||
server.use(proxyMiddleware(context, devProxy[context]));
|
||||
});
|
||||
|
|
@ -44,7 +44,7 @@ app
|
|||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
console.log(`> Ready on port ${port} [${NODE_ENV}]`);
|
||||
console.log(`> Ready on port ${port} [${process.env.NODE_ENV}]`);
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import Head from 'next/head';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
|
||||
import type { AppProps } from 'next/app';
|
||||
|
|
@ -9,24 +8,9 @@ const App = (props: AppProps): JSX.Element => {
|
|||
const { Component, pageProps } = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>hyperglass</title>
|
||||
<meta httpEquiv="Content-Type" content="text/html" />
|
||||
<meta charSet="UTF-8" />
|
||||
<meta name="og:type" content="website" />
|
||||
<meta name="og:image" content="/images/opengraph.jpg" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0"
|
||||
/>
|
||||
</Head>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Component {...pageProps} />
|
||||
</QueryClientProvider>
|
||||
</>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Component {...pageProps} />
|
||||
</QueryClientProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import Document, { Html, Head, Main, NextScript } from 'next/document';
|
||||
import { Favicon } from '~/components';
|
||||
import favicons from '../favicon-formats';
|
||||
|
||||
import type { DocumentContext, DocumentInitialProps } from 'next/document';
|
||||
|
||||
|
|
@ -12,10 +14,23 @@ class MyDocument extends Document {
|
|||
return (
|
||||
<Html lang="en">
|
||||
<Head>
|
||||
<meta name="language" content="en" />
|
||||
<meta httpEquiv="Content-Type" content="text/html" />
|
||||
<meta charSet="UTF-8" />
|
||||
<meta name="og:type" content="website" />
|
||||
<meta name="og:image" content="/images/opengraph.jpg" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0"
|
||||
/>
|
||||
<link rel="dns-prefetch" href="//fonts.gstatic.com" />
|
||||
<link rel="dns-prefetch" href="//fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://www.google-analytics.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="true" />
|
||||
{favicons.map((favicon, idx) => (
|
||||
<Favicon key={idx} {...favicon} />
|
||||
))}
|
||||
</Head>
|
||||
<body>
|
||||
<Main />
|
||||
|
|
|
|||
|
|
@ -1,34 +1,20 @@
|
|||
import Head from 'next/head';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { Meta, Loading, If, LoadError } from '~/components';
|
||||
import { HyperglassProvider } from '~/context';
|
||||
import { useHyperglassConfig } from '~/hooks';
|
||||
import { getFavicons } from '~/util';
|
||||
|
||||
import type { GetStaticProps } from 'next';
|
||||
import type { FaviconComponent } from '~/types';
|
||||
import type { NextPage } from 'next';
|
||||
|
||||
const Layout = dynamic<Dict>(() => import('~/components').then(i => i.Layout), {
|
||||
loading: Loading,
|
||||
});
|
||||
|
||||
interface TIndex {
|
||||
favicons: FaviconComponent[];
|
||||
}
|
||||
|
||||
const Index = (props: TIndex): JSX.Element => {
|
||||
const { favicons } = props;
|
||||
const Index: NextPage = () => {
|
||||
const { data, error, isLoading, ready, refetch, showError, isLoadingInitial } =
|
||||
useHyperglassConfig();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
{favicons.map((icon, idx) => {
|
||||
const { rel, href, type } = icon;
|
||||
return <link rel={rel} href={href} type={type} key={idx} />;
|
||||
})}
|
||||
</Head>
|
||||
<If c={isLoadingInitial}>
|
||||
<Loading />
|
||||
</If>
|
||||
|
|
@ -45,11 +31,4 @@ const Index = (props: TIndex): JSX.Element => {
|
|||
);
|
||||
};
|
||||
|
||||
export const getStaticProps: GetStaticProps<TIndex> = async () => {
|
||||
const favicons = await getFavicons();
|
||||
return {
|
||||
props: { favicons },
|
||||
};
|
||||
};
|
||||
|
||||
export default Index;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
import type { Theme } from './theme';
|
||||
import type { CamelCasedPropertiesDeep, CamelCasedProperties } from 'type-fest';
|
||||
|
||||
// export type QueryFields = 'query_type' | 'query_target' | 'query_location' | 'query_vrf';
|
||||
|
||||
type Side = 'left' | 'right';
|
||||
|
||||
export type ParsedDataField = [string, keyof Route, 'left' | 'right' | 'center' | null];
|
||||
|
|
@ -189,12 +187,6 @@ export interface Favicon {
|
|||
prefix: string;
|
||||
}
|
||||
|
||||
export interface FaviconComponent {
|
||||
rel: string;
|
||||
href: string;
|
||||
type: string;
|
||||
}
|
||||
|
||||
export type Config = CamelCasedPropertiesDeep<_ConfigDeep> & CamelCasedProperties<_ConfigShallow>;
|
||||
export type ThemeConfig = CamelCasedProperties<_ThemeConfig>;
|
||||
export type Content = CamelCasedProperties<_Content>;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { isObject } from '~/types';
|
||||
|
||||
import type { Config, FaviconComponent } from '~/types';
|
||||
import type { Config } from '~/types';
|
||||
|
||||
export class ConfigLoadError extends Error {
|
||||
public url: string = '/ui/props/';
|
||||
|
|
@ -56,14 +56,3 @@ export async function getHyperglassConfig(): Promise<Config> {
|
|||
}
|
||||
throw new ConfigLoadError('Unknown Error');
|
||||
}
|
||||
|
||||
export async function getFavicons(): Promise<FaviconComponent[]> {
|
||||
const { favicons: faviconConfig } = process.env.hyperglass;
|
||||
return faviconConfig.map(icon => {
|
||||
const { image_format, dimensions, prefix } = icon;
|
||||
const [w, h] = dimensions;
|
||||
const rel = icon.rel ?? '';
|
||||
const src = `/images/favicons/${prefix}-${w}x${h}.${image_format}`;
|
||||
return { rel, href: src, type: `image/${image_format}` };
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
# Standard Library
|
||||
import shutil
|
||||
import typing as t
|
||||
from queue import Queue
|
||||
from typing import List, Tuple, Union, Iterable, Optional, Generator
|
||||
from pathlib import Path
|
||||
from threading import Thread
|
||||
|
||||
|
|
@ -11,14 +11,8 @@ from threading import Thread
|
|||
from hyperglass.log import log
|
||||
|
||||
|
||||
async def move_files(src: Path, dst: Path, files: Iterable[Path]) -> Tuple[str]: # noqa: C901
|
||||
"""Move iterable of files from source to destination.
|
||||
|
||||
Arguments:
|
||||
src {Path} -- Current directory of files
|
||||
dst {Path} -- Target destination directory
|
||||
files {Iterable} -- Iterable of files
|
||||
"""
|
||||
async def move_files(src: Path, dst: Path, files: t.Iterable[Path]) -> t.Tuple[str]: # noqa: C901
|
||||
"""Move iterable of files from source to destination."""
|
||||
|
||||
def error(*args, **kwargs):
|
||||
msg = ", ".join(args)
|
||||
|
|
@ -39,7 +33,7 @@ async def move_files(src: Path, dst: Path, files: Iterable[Path]) -> Tuple[str]:
|
|||
except TypeError:
|
||||
raise error("{p} is not a valid path", p=dst)
|
||||
|
||||
if not isinstance(files, (List, Tuple, Generator)):
|
||||
if not isinstance(files, (t.List, t.Tuple, t.Generator)):
|
||||
raise error(
|
||||
"{fa} must be an iterable (list, tuple, or generator). Received {f}",
|
||||
fa="Files argument",
|
||||
|
|
@ -95,7 +89,7 @@ class FileCopy(Thread):
|
|||
pass
|
||||
|
||||
|
||||
def copyfiles(src_files: Iterable[Path], dst_files: Iterable[Path]):
|
||||
def copyfiles(src_files: t.Iterable[Path], dst_files: t.Iterable[Path]):
|
||||
"""Copy iterable of files from source to destination with threading."""
|
||||
queue = Queue()
|
||||
threads = ()
|
||||
|
|
@ -131,7 +125,7 @@ def copyfiles(src_files: Iterable[Path], dst_files: Iterable[Path]):
|
|||
return True
|
||||
|
||||
|
||||
def check_path(path: Union[Path, str], mode: str = "r", create: bool = False) -> Optional[Path]:
|
||||
def check_path(path: t.Union[Path, str], mode: str = "r", create: bool = False) -> t.Optional[Path]:
|
||||
"""Verify if a path exists and is accessible."""
|
||||
|
||||
result = None
|
||||
|
|
@ -157,3 +151,30 @@ def check_path(path: Union[Path, str], mode: str = "r", create: bool = False) ->
|
|||
pass
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def dotenv_to_dict(dotenv: t.Union[Path, str]) -> t.Dict[str, str]:
|
||||
"""Convert a .env file to a Python dict."""
|
||||
if not isinstance(dotenv, (Path, str)):
|
||||
raise TypeError("Argument 'file' must be a Path object or string")
|
||||
result = {}
|
||||
data = ""
|
||||
if isinstance(dotenv, Path):
|
||||
if not dotenv.exists():
|
||||
raise FileNotFoundError("{!r} does not exist", str(dotenv))
|
||||
with dotenv.open("r") as f:
|
||||
data = f.read()
|
||||
else:
|
||||
data = dotenv
|
||||
|
||||
for line in (line for line in (line.strip() for line in data.splitlines()) if line):
|
||||
parts = line.split("=")
|
||||
if len(parts) != 2:
|
||||
raise TypeError(
|
||||
f"Line {line!r} is improperly formatted. "
|
||||
"Expected a key/value pair such as 'key=value'"
|
||||
)
|
||||
key, value = line.split("=")
|
||||
result[key.strip()] = value.strip()
|
||||
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from pathlib import Path
|
|||
from hyperglass.log import log
|
||||
|
||||
# Local
|
||||
from .files import copyfiles, check_path
|
||||
from .files import copyfiles, check_path, dotenv_to_dict
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
# Project
|
||||
|
|
@ -239,6 +239,26 @@ def migrate_images(app_path: Path, params: "UIParameters"):
|
|||
return copyfiles(src_files, dst_files)
|
||||
|
||||
|
||||
def write_favicon_formats(formats: t.Tuple[t.Dict[str, t.Any]]) -> None:
|
||||
"""Create a TypeScript file in the `ui` directory containing favicon formats.
|
||||
|
||||
This file should stay the same, unless the favicons library updates
|
||||
supported formats.
|
||||
"""
|
||||
# Standard Library
|
||||
from collections import OrderedDict
|
||||
|
||||
file = Path(__file__).parent.parent / "ui" / "favicon-formats.ts"
|
||||
|
||||
# Sort each favicon definition to ensure the result stays the same
|
||||
# time the UI build runs.
|
||||
ordered = json.dumps([OrderedDict(sorted(fmt.items())) for fmt in formats])
|
||||
data = "import type {{ Favicon }} from '~/types';export default {} as Favicon[];".format(
|
||||
ordered
|
||||
)
|
||||
file.write_text(data)
|
||||
|
||||
|
||||
async def build_frontend( # noqa: C901
|
||||
dev_mode: bool,
|
||||
dev_url: str,
|
||||
|
|
@ -249,19 +269,7 @@ async def build_frontend( # noqa: C901
|
|||
timeout: int = 180,
|
||||
full: bool = False,
|
||||
) -> bool:
|
||||
"""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.
|
||||
"""
|
||||
"""Perform full frontend UI build process."""
|
||||
# Standard Library
|
||||
import hashlib
|
||||
|
||||
|
|
@ -273,24 +281,18 @@ async def build_frontend( # noqa: C901
|
|||
|
||||
# Create temporary file. json file extension is added for easy
|
||||
# webpack JSON parsing.
|
||||
env_file = Path("/tmp/hyperglass.env.json") # noqa: S108
|
||||
dot_env_file = Path(__file__).parent.parent / "ui" / ".env"
|
||||
env_config = {}
|
||||
|
||||
package_json = await read_package_json()
|
||||
|
||||
env_vars = {
|
||||
"hyperglass": {"version": __version__},
|
||||
"env": {},
|
||||
}
|
||||
|
||||
# Set NextJS production/development mode and base URL based on
|
||||
# developer_mode setting.
|
||||
if dev_mode:
|
||||
env_vars["env"].update({"NODE_ENV": "development"})
|
||||
env_vars["hyperglass"].update({"url": dev_url})
|
||||
env_config.update({"HYPERGLASS_URL": dev_url, "NODE_ENV": "development"})
|
||||
|
||||
else:
|
||||
env_vars["env"].update({"NODE_ENV": "production"})
|
||||
env_vars["hyperglass"].update({"url": prod_url})
|
||||
env_config.update({"HYPERGLASS_URL": prod_url, "NODE_ENV": "production"})
|
||||
|
||||
# Check if hyperglass/ui/node_modules has been initialized. If not,
|
||||
# initialize it.
|
||||
|
|
@ -310,71 +312,70 @@ async def build_frontend( # noqa: C901
|
|||
images_dir = app_path / "static" / "images"
|
||||
favicon_dir = images_dir / "favicons"
|
||||
|
||||
try:
|
||||
if not favicon_dir.exists():
|
||||
favicon_dir.mkdir()
|
||||
async with Favicons(
|
||||
source=params.web.logo.favicon,
|
||||
output_directory=favicon_dir,
|
||||
base_url="/images/favicons/",
|
||||
) as favicons:
|
||||
await favicons.generate()
|
||||
log.debug("Generated {} favicons", favicons.completed)
|
||||
env_vars["hyperglass"].update({"favicons": favicons.formats()})
|
||||
build_data = {
|
||||
"params": params.export_dict(),
|
||||
"version": __version__,
|
||||
"package_json": package_json,
|
||||
}
|
||||
build_json = json.dumps(build_data, default=str)
|
||||
# Create SHA256 hash from all parameters passed to UI, use as
|
||||
# build identifier.
|
||||
build_id = hashlib.sha256(build_json.encode()).hexdigest()
|
||||
if not favicon_dir.exists():
|
||||
favicon_dir.mkdir()
|
||||
|
||||
# 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:
|
||||
with env_file.open("r") as ef:
|
||||
ef_id = json.load(ef).get("buildId", "empty")
|
||||
log.debug("Previous Build ID: {}", ef_id)
|
||||
async with Favicons(
|
||||
source=params.web.logo.favicon,
|
||||
output_directory=favicon_dir,
|
||||
base_url="/images/favicons/",
|
||||
) as favicons:
|
||||
await favicons.generate()
|
||||
log.debug("Generated {} favicons", favicons.completed)
|
||||
write_favicon_formats(favicons.formats())
|
||||
|
||||
if ef_id == build_id:
|
||||
log.debug("UI parameters unchanged since last build, skipping UI build...")
|
||||
return True
|
||||
build_data = {
|
||||
"params": params.export_dict(),
|
||||
"version": __version__,
|
||||
"package_json": package_json,
|
||||
}
|
||||
|
||||
env_vars["buildId"] = build_id
|
||||
build_json = json.dumps(build_data, default=str)
|
||||
|
||||
env_file.write_text(json.dumps(env_vars, default=str))
|
||||
log.debug("Wrote UI environment file '{}'", str(env_file))
|
||||
# Create SHA256 hash from all parameters passed to UI, use as
|
||||
# build identifier.
|
||||
build_id = hashlib.sha256(build_json.encode()).hexdigest()
|
||||
|
||||
# Initiate Next.JS export process.
|
||||
if any((not dev_mode, force, full)):
|
||||
log.info("Starting UI build")
|
||||
initialize_result = await node_initial(timeout, dev_mode)
|
||||
build_result = await build_ui(app_path=app_path)
|
||||
# Read hard-coded environment file from last build. If build ID
|
||||
# matches this build's ID, don't run a new build.
|
||||
if dot_env_file.exists() and not force:
|
||||
env_data = dotenv_to_dict(dot_env_file)
|
||||
env_build_id = env_data.get("HYPERGLASS_BUILD_ID", "None")
|
||||
log.debug("Previous Build ID: {!r}", env_build_id)
|
||||
|
||||
if initialize_result:
|
||||
log.debug(initialize_result)
|
||||
elif initialize_result == "":
|
||||
log.debug("Re-initialized node_modules")
|
||||
if env_build_id == build_id:
|
||||
log.debug("UI parameters unchanged since last build, skipping UI build...")
|
||||
return True
|
||||
|
||||
if build_result:
|
||||
log.success("Completed UI build")
|
||||
elif dev_mode and not force:
|
||||
log.debug("Running in developer mode, did not build new UI files")
|
||||
env_config.update({"HYPERGLASS_BUILD_ID": build_id})
|
||||
|
||||
migrate_images(app_path, params)
|
||||
dot_env_file.write_text("\n".join(f"{k}={v}" for k, v in env_config.items()))
|
||||
log.debug("Wrote UI environment file {!r}", str(dot_env_file))
|
||||
|
||||
generate_opengraph(
|
||||
params.web.opengraph.image,
|
||||
1200,
|
||||
630,
|
||||
images_dir,
|
||||
params.web.theme.colors.black,
|
||||
)
|
||||
# Initiate Next.JS export process.
|
||||
if any((not dev_mode, force, full)):
|
||||
log.info("Starting UI build")
|
||||
initialize_result = await node_initial(timeout, dev_mode)
|
||||
build_result = await build_ui(app_path=app_path)
|
||||
|
||||
except Exception as err:
|
||||
log.error(err)
|
||||
raise RuntimeError(str(err)) from None
|
||||
if initialize_result:
|
||||
log.debug(initialize_result)
|
||||
elif initialize_result == "":
|
||||
log.debug("Re-initialized node_modules")
|
||||
|
||||
if build_result:
|
||||
log.success("Completed UI build")
|
||||
elif dev_mode and not force:
|
||||
log.debug("Running in developer mode, did not build new UI files")
|
||||
|
||||
migrate_images(app_path, params)
|
||||
|
||||
generate_opengraph(
|
||||
params.web.opengraph.image,
|
||||
1200,
|
||||
630,
|
||||
images_dir,
|
||||
params.web.theme.colors.black,
|
||||
)
|
||||
|
||||
return True
|
||||
|
|
|
|||
50
hyperglass/util/tests/test_files.py
Normal file
50
hyperglass/util/tests/test_files.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
"""Test file-related utilities."""
|
||||
|
||||
# Standard Library
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
# Third Party
|
||||
import pytest
|
||||
|
||||
# Local
|
||||
from ..files import dotenv_to_dict
|
||||
|
||||
ENV_TEST = """KEY1=VALUE1
|
||||
KEY2=VALUE2
|
||||
KEY3=VALUE3
|
||||
"""
|
||||
|
||||
|
||||
def test_dotenv_to_dict_string():
|
||||
result = dotenv_to_dict(ENV_TEST)
|
||||
assert result.get("KEY1") == "VALUE1"
|
||||
assert result.get("KEY2") == "VALUE2"
|
||||
assert result.get("KEY3") == "VALUE3"
|
||||
|
||||
|
||||
def test_dotenv_to_dict_file():
|
||||
_, filename = tempfile.mkstemp()
|
||||
file = Path(filename)
|
||||
with file.open("w+") as f:
|
||||
f.write(ENV_TEST)
|
||||
result = dotenv_to_dict(file)
|
||||
assert result.get("KEY1") == "VALUE1"
|
||||
assert result.get("KEY2") == "VALUE2"
|
||||
assert result.get("KEY3") == "VALUE3"
|
||||
file.unlink()
|
||||
|
||||
|
||||
def test_dotenv_to_dict_raises_type_error():
|
||||
with pytest.raises(TypeError):
|
||||
dotenv_to_dict(True)
|
||||
|
||||
|
||||
def test_dotenv_to_dict_raises_filenotfounderror():
|
||||
with pytest.raises(FileNotFoundError):
|
||||
dotenv_to_dict(Path("/tmp/not-a-thing")) # noqa: S108
|
||||
|
||||
|
||||
def test_dotenv_invalid_format():
|
||||
with pytest.raises(TypeError):
|
||||
dotenv_to_dict("this should raise an error")
|
||||
Loading…
Add table
Reference in a new issue