Skip to content

Commit

Permalink
dashboard: fix and improve build (#986)
Browse files Browse the repository at this point in the history
* fix deps not built before building dashboard

Signed-off-by: Teo Koon Peng <teokoonpeng@gmail.com>

* cleanup old config; make adminTab and customTab optional

Signed-off-by: Teo Koon Peng <teokoonpeng@gmail.com>

* allow authenticator to be tree shaked; rudimetry way to have runtime args

Signed-off-by: Teo Koon Peng <teokoonpeng@gmail.com>

* allow custom tabs and admin tags to be tree shaken

Signed-off-by: Teo Koon Peng <teokoonpeng@gmail.com>

* actually tree shake out admin tab

Signed-off-by: Teo Koon Peng <teokoonpeng@gmail.com>

* put all build time config in separate object

Signed-off-by: Teo Koon Peng <teokoonpeng@gmail.com>

* fallback to test config

Signed-off-by: Teo Koon Peng <teokoonpeng@gmail.com>

* add base url to build config

Signed-off-by: Teo Koon Peng <teokoonpeng@gmail.com>

* add __init__.py so older setuptools sees ros_pydantic

Signed-off-by: Teo Koon Peng <teokoonpeng@gmail.com>

* use header logo in login page

Signed-off-by: Teo Koon Peng <teokoonpeng@gmail.com>

---------

Signed-off-by: Teo Koon Peng <teokoonpeng@gmail.com>
  • Loading branch information
koonpeng authored Aug 6, 2024
1 parent b4c2b06 commit 10cc4bd
Show file tree
Hide file tree
Showing 13 changed files with 204 additions and 79 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
"prettier --write"
],
"**/*.py": [
".venv/bin/isort",
".venv/bin/black"
".venv/bin/pipenv run isort",
".venv/bin/pipenv run black"
]
},
"overrides": {
Expand Down
Empty file.
56 changes: 56 additions & 0 deletions packages/dashboard/app-config.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
export interface RobotResource {
icon?: string;
scale?: number;
}

export interface FleetResource {
default: RobotResource;
}

export interface LogoResource {
header: string;
}

export interface Resources {
fleets: Record<string, FleetResource>;
logos: LogoResource;
}

export interface TaskResource {
taskDefinitionId: string;
displayName?: string;
}

export interface StubAuthConfig {
provider: 'stub';
config: null;
}

export interface KeycloakAuthConfig {
provider: 'keycloak';
config: {
url: string;
realm: string;
clientId: string;
};
}

export interface AppConfigInput {
rmfServerUrl: string;
trajectoryServerUrl: string;
auth: KeycloakAuthConfig | StubAuthConfig;
helpLink: string;
reportIssue: string;
pickupZones: string[];
defaultZoom: number;
defaultRobotZoom: number;
attributionPrefix: string;
defaultMapLevel: string;
allowedTasks: TaskResource[];
resources: Record<string, Resources> & Record<'default', Resources>;
customTabs?: boolean;
adminTab?: boolean;
// FIXME(koonpeng): this is used for very specific tasks, should be removed when mission
// system is implemented.
cartIds: string[];
}
14 changes: 8 additions & 6 deletions packages/dashboard/app-config.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
{
"rmfServerUrl": "http://localhost:8000",
"trajectoryServerUrl": "ws://localhost:8006",
"auth": {
"provider": "stub"
},
"authConfig": {},
"helpLink": "https://osrf.github.io/ros2multirobotbook/rmf-core.html",
"reportIssue": "https://github.com/open-rmf/rmf-web/issues",
"pickupZones": [],
Expand Down Expand Up @@ -33,7 +31,11 @@
}
}
},
"customTabs": false,
"adminTab": false,
"cartIds": []
"cartIds": [],
"buildConfig": {
"baseUrl": "/",
"authProvider": "stub",
"customTabs": false,
"adminTab": false
}
}
2 changes: 1 addition & 1 deletion packages/dashboard/index.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
Expand Down
2 changes: 1 addition & 1 deletion packages/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"type": "module",
"private": true,
"scripts": {
"build": "vite build",
"build": "pnpm run --filter {.}^... build && vite build",
"build-storybook": "storybook build",
"lint": "tsc --build && eslint --max-warnings 0 src",
"start": "concurrently npm:start:rmf-server npm:start:react",
Expand Down
73 changes: 40 additions & 33 deletions packages/dashboard/src/app-config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import { getDefaultTaskDefinition, TaskDefinition } from 'react-components';

import appConfigJson from '../app-config.json';
import testConfig from '../app-config.json';
import { Authenticator, KeycloakAuthenticator, StubAuthenticator } from './auth';
import { BasePath } from './util/url';

Expand All @@ -28,27 +28,18 @@ export interface TaskResource {
displayName?: string;
}

export interface StubAuthConfig {
provider: 'stub';
}
export interface StubAuthConfig {}

export interface KeycloakAuthConfig {
provider: 'keycloak';
config: {
url: string;
realm: string;
clientId: string;
};
}

export interface AuthConfig {
provider: string;
url: string;
realm: string;
clientId: string;
}

export interface AppConfig {
export interface RuntimeConfig {
rmfServerUrl: string;
trajectoryServerUrl: string;
auth: KeycloakAuthConfig | StubAuthConfig;
authConfig: KeycloakAuthConfig | StubAuthConfig;
helpLink: string;
reportIssue: string;
pickupZones: string[];
Expand All @@ -58,32 +49,48 @@ export interface AppConfig {
defaultMapLevel: string;
allowedTasks: TaskResource[];
resources: Record<string, Resources> & Record<'default', Resources>;
customTabs: boolean;
adminTab: boolean;
// FIXME(koonpeng): this is used for very specific tasks, should be removed when mission
// system is implemented.
cartIds: string[];
}

// we specifically don't export app config to force consumers to use the context.
const appConfig: AppConfig = appConfigJson as AppConfig;
// these will be injected as defines and potentially be tree shaken out
export interface BuildConfig {
baseUrl: string;
authProvider: 'keycloak' | 'stub';
customTabs?: boolean;
adminTab?: boolean;
}

export interface AppConfig extends RuntimeConfig {
buildConfig: BuildConfig;
}

declare const APP_CONFIG: AppConfig;

const appConfig: AppConfig = (() => {
if (import.meta.env.PROD) {
return APP_CONFIG;
} else {
// globals cannot be injected in tests so we need a fallback, this should be
// removed by terser in prod builds.
return testConfig as AppConfig;
}
})();

export const AppConfigContext = React.createContext(appConfig);

const authenticator: Authenticator = (() => {
switch (appConfig.auth.provider) {
case 'keycloak':
if (!import.meta.env.VITE_KEYCLOAK_CONFIG) {
throw new Error('missing VITE_KEYCLOAK_CONFIG');
}
return new KeycloakAuthenticator(
appConfig.auth.config,
`${window.location.origin}${BasePath}/silent-check-sso.html`,
);
case 'stub':
return new StubAuthenticator();
default:
throw new Error('unknown auth provider');
// must use if statement instead of switch for vite tree shaking to work
if (APP_CONFIG_AUTH_PROVIDER === 'keycloak') {
return new KeycloakAuthenticator(
APP_CONFIG.authConfig as KeycloakAuthConfig,
`${window.location.origin}${BasePath}/silent-check-sso.html`,
);
} else if (APP_CONFIG_AUTH_PROVIDER === 'stub') {
return new StubAuthenticator();
} else {
throw new Error('unknown auth provider');
}
})();

Expand Down
56 changes: 31 additions & 25 deletions packages/dashboard/src/components/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,32 +96,38 @@ export default function App(): JSX.Element | null {
}
/>

<Route
path={CustomRoute1}
element={
<PrivateRoute unauthorizedComponent={loginRedirect} user={user}>
<ManagedWorkspace key="custom1" workspaceId="custom1" />
</PrivateRoute>
}
/>
{APP_CONFIG_ENABLE_CUSTOM_TABS && (
<Route
path={CustomRoute1}
element={
<PrivateRoute unauthorizedComponent={loginRedirect} user={user}>
<ManagedWorkspace key="custom1" workspaceId="custom1" />
</PrivateRoute>
}
/>
)}

<Route
path={CustomRoute2}
element={
<PrivateRoute unauthorizedComponent={loginRedirect} user={user}>
<ManagedWorkspace key="custom2" workspaceId="custom2" />
</PrivateRoute>
}
/>
{APP_CONFIG_ENABLE_CUSTOM_TABS && (
<Route
path={CustomRoute2}
element={
<PrivateRoute unauthorizedComponent={loginRedirect} user={user}>
<ManagedWorkspace key="custom2" workspaceId="custom2" />
</PrivateRoute>
}
/>
)}

<Route
path={AdminRoute}
element={
<PrivateRoute unauthorizedComponent={loginRedirect} user={user}>
<AdminRouter />
</PrivateRoute>
}
/>
{APP_CONFIG_ENABLE_ADMIN_TAB && (
<Route
path={AdminRoute}
element={
<PrivateRoute unauthorizedComponent={loginRedirect} user={user}>
<AdminRouter />
</PrivateRoute>
}
/>
)}
</Routes>
</AppBase>
</RmfApp>
Expand All @@ -132,7 +138,7 @@ export default function App(): JSX.Element | null {
element={
<LoginPage
title={'Dashboard'}
logo="assets/defaultLogo.png"
logo={resources.logos.header}
onLoginClick={() =>
authenticator.login(`${window.location.origin}${DashboardRoute}`)
}
Expand Down
4 changes: 2 additions & 2 deletions packages/dashboard/src/components/appbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ export const AppBar = React.memo(({ extraToolbarItems }: AppBarProps): React.Rea
aria-label="Tasks"
onTabClick={() => navigate(TasksRoute)}
/>
{appConfig.customTabs && (
{APP_CONFIG_ENABLE_CUSTOM_TABS && (
<>
<StyledAppBarTab
label="Custom 1"
Expand All @@ -360,7 +360,7 @@ export const AppBar = React.memo(({ extraToolbarItems }: AppBarProps): React.Rea
/>
</>
)}
{appConfig.adminTab && profile?.user.is_admin && (
{APP_CONFIG_ENABLE_ADMIN_TAB && profile?.user.is_admin && (
<StyledAppBarTab
label="Admin"
value="admin"
Expand Down
5 changes: 5 additions & 0 deletions packages/dashboard/src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
/// <reference types="vite/client" />

declare const APP_CONFIG_BASE_URL: string;
declare const APP_CONFIG_AUTH_PROVIDER: string;
declare const APP_CONFIG_ENABLE_CUSTOM_TABS: boolean;
declare const APP_CONFIG_ENABLE_ADMIN_TAB: boolean;
48 changes: 46 additions & 2 deletions packages/dashboard/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,55 @@
/// <reference types="vitest" />

import react from '@vitejs/plugin-react-swc';
import { defineConfig } from 'vite';
import { defineConfig, Plugin } from 'vite';

import appConfig from './app-config.json';

/**
* The goal of this plugin is to inject global variables to `index.html`, allowing
* a crude way to configure some variables after the bundle is built via `sed`.
*
* An example use case is building a dockerfile, because the domain in a dev or staging
* environment is typically different from prod, we would normally end up needing to build
* different images (which is really detrimental for staging). In such scenario, you can
* set the `rmfServerUrl` to a placeholder like `__RMF_SERVER_URL_PLACEHOLDER__` in
* `app-config.json`, then do a search and replace at the container entrypoint.
*
* The reason for doing this outside the app code is to avoid these variables from
* being modified by the bundler, and to reduce the chance that the search and replace modify
* unintended code.
*/
const injectGlobals: Plugin = {
name: 'injectRuntimeArgs',
transformIndexHtml: {
order: 'pre', // must be injected before the app
handler: () => {
return [
{
tag: 'script',
injectTo: 'head',
children: `const APP_CONFIG=${JSON.stringify(appConfig)}`,
},
];
},
},
};

function booleanToString(b: boolean | null | undefined) {
return b ? 'true' : 'false';
}

const buildConfig = appConfig.buildConfig;

// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
base: buildConfig.baseUrl,
define: {
APP_CONFIG_AUTH_PROVIDER: `'${buildConfig.authProvider}'`,
APP_CONFIG_ENABLE_CUSTOM_TABS: `${booleanToString(buildConfig.customTabs)}`,
APP_CONFIG_ENABLE_ADMIN_TAB: `${booleanToString(buildConfig.adminTab)}`,
},
plugins: [injectGlobals, react()],
test: {
environment: 'jsdom',
globals: true,
Expand Down
7 changes: 6 additions & 1 deletion packages/ros-translator/ros_translator/pydantic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,9 @@ def generate_messages(roslib: RosLibrary, pkg: str, outdir: str):


def generate_init(namespace: Namespace, outdir: str):
with open(joinp(outdir, namespace.full_name, "__init__.py"), mode="w") as f:
with open(
joinp(outdir, namespace.full_name, "__init__.py"), mode="w", encoding="utf8"
) as f:
for m in namespace.messages.values():
name = m.structure.namespaced_type.name
f.write(f"from .{name} import {name}\n")
Expand All @@ -140,6 +142,9 @@ def generate_modules(pkgs: Sequence[str], outdir: str):
generate_messages(roslib, pkg_index.pkg_name, outdir)
generate_init(pkg_index.root_ns, outdir)

with open(joinp(outdir, "__init__.py"), mode="w", encoding="utf8"):
pass


def generate(pkgs: Sequence[str], outdir: str):
print("Generating pydantic interfaces")
Expand Down
Loading

0 comments on commit 10cc4bd

Please sign in to comment.