Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dashboard: fix and improve build #986

Merged
merged 11 commits into from
Aug 6, 2024
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: {
aaronchongth marked this conversation as resolved.
Show resolved Hide resolved
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
Loading