Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions dashboard/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist/
node_modules/
27 changes: 11 additions & 16 deletions dashboard/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,36 +1,31 @@
# Step 1: Builds and tests
FROM node:14.21.3-bullseye AS build
FROM node:16.20.2-bullseye AS build

ARG kubeflowversion
ARG commit

ENV BUILD_VERSION=$kubeflowversion
ENV BUILD_COMMIT=$commit
ENV CHROME_BIN=/usr/bin/chromium
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true

RUN apt update -qq && apt install -qq -y gnulib

COPY . /centraldashboard
WORKDIR /centraldashboard

RUN BUILDARCH="$(dpkg --print-architecture)" && npm rebuild && \
if [ "$BUILDARCH" = "arm64" ] || \
[ "$BUILDARCH" = "armhf" ]; then \
export CFLAGS=-Wno-error && \
export CXXFLAGS=-Wno-error; \
fi && \
npm install && \
npm run build && \
npm prune --production
RUN npm ci \
&& npm run build \
&& npm prune --production

# Step 2: Packages assets for serving
FROM node:14.21.3-alpine3.17 AS serve
FROM node:16.20.2-alpine AS serve

RUN apk add --no-cache tini

USER node

ENV NODE_ENV=production
WORKDIR /app
COPY --from=build /centraldashboard .
WORKDIR /usr/src/app
COPY --from=build --chown=node:node /centraldashboard .

EXPOSE 8082
ENTRYPOINT ["npm", "start"]
ENTRYPOINT ["/sbin/tini", "--" , "npm", "start"]
35 changes: 25 additions & 10 deletions dashboard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,31 @@ It provides a jump-off point to all other tool Web UIs.

### Getting Started

Make sure you have installed node 12 or 14.

1. Run `cd dashboard`
2. Run `make build-local` to install npm dependencies
3. Run `npm run dev` to start the development server
- This runs [webpack](https://webpack.js.org/) over the front-end code in the [public](./public) folder.
It also starts the [webpack-dev-server](https://webpack.js.org/configuration/dev-server/) at http://localhost:8081.
- It also starts the Express API server at http://localhost:8082.
Requests from the front-end starting with `/api` are proxied to the Express server.
All other requests are handled by the front-end server which mirrors the production configuration.
Make sure you have installed node 16!

1. We STRONGLY recommend using [nvm](https://github.com/nvm-sh/nvm):
- Uninstall any Homebrew versions with `brew uninstall node` (or `node@XX`)
- Install `nvm` with `curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash`
- Install node `16` with `nvm install 16`
- Use node `16` with `nvm use 16`
- Set node `16` as the default with `nvm alias default 16`
2. Run `cd dashboard`
3. Run `npm install` to install npm dependencies
4. Run `./run_local.sh` to start the development server, this will:
- Run the following kubectl port-forwards:
- `localhost:8081` -> `kfam-api.deploykf-dashboard.svc`
- `localhost:8085` -> `jupyter-web-app-service.kubeflow.svc`
- `localhost:8087` -> `ml-pipeline-ui.kubeflow.svc`
- Run [webpack](https://webpack.js.org/) over the front-end code in the [public](./public) folder
- Run [webpack-dev-server](https://webpack.js.org/configuration/dev-server/) at http://localhost:8081
- Run the Express API server at http://localhost:8082
- Proxy requests from the front-end starting with `/api` to the Express server.
- All other requests are handled by the front-end server which mirrors the production configuration.
- __TIP:__ if you see `bind: address already in use` errors, you might try `pkill -f "kubectl port-forward"` to stop all existing port-forwards
5. Open your browser to `http://localhost:8080` to see the dashboard:
- You will need to inject your requrests with a `kubeflow-userid` header
- You can do this in Chrome by using the [Header Editor](https://chromewebstore.google.com/detail/eningockdidmgiojffjmkdblpjocbhgh) extension
- For example, set the `kubeflow-userid` header to `user1@example.com`

### Server Components

Expand Down
6 changes: 4 additions & 2 deletions dashboard/app/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,10 @@ export class Api {
}

let interval = Interval.Last15m;
if (Interval[req.query.interval] !== undefined) {
interval = Number(Interval[req.query.interval]);
const intervalQuery = req.query.interval as string;
const intervalQueryKey = intervalQuery as keyof typeof Interval;
if (Interval[intervalQueryKey] !== undefined) {
interval = Interval[intervalQueryKey];
}
switch (req.params.type) {
case 'node':
Expand Down
10 changes: 5 additions & 5 deletions dashboard/app/k8s_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ const APP_API_NAME = 'applications';
/** Wrap Kubernetes API calls in a simpler interface for use in routes. */
export class KubernetesService {
private namespace = process.env.POD_NAMESPACE || 'kubeflow';
private coreAPI: k8s.Core_v1Api;
private customObjectsAPI: k8s.Custom_objectsApi;
private coreAPI: k8s.CoreV1Api;
private customObjectsAPI: k8s.CustomObjectsApi;
private dashboardConfigMap = DASHBOARD_CONFIGMAP;

constructor(private kubeConfig: k8s.KubeConfig) {
Expand All @@ -63,9 +63,9 @@ export class KubernetesService {
if (context && context.namespace) {
this.namespace = context.namespace;
}
this.coreAPI = this.kubeConfig.makeApiClient(k8s.Core_v1Api);
this.coreAPI = this.kubeConfig.makeApiClient(k8s.CoreV1Api);
this.customObjectsAPI =
this.kubeConfig.makeApiClient(k8s.Custom_objectsApi);
this.kubeConfig.makeApiClient(k8s.CustomObjectsApi);
}

/** Retrieves the list of namespaces from the Cluster. */
Expand All @@ -91,7 +91,7 @@ export class KubernetesService {
}

/** Retrieves the list of events for the given Namespace from the Cluster. */
async getEventsForNamespace(namespace: string): Promise<k8s.V1Event[]> {
async getEventsForNamespace(namespace: string): Promise<k8s.CoreV1Event[]> {
try {
const {body} = await this.coreAPI.listNamespacedEvent(namespace);
return body.items;
Expand Down
14 changes: 7 additions & 7 deletions dashboard/app/k8s_service_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import {KubernetesService} from './k8s_service';
describe('KubernetesService', () => {
let mockResponse: jasmine.SpyObj<IncomingMessage>;
let mockKubeConfig: jasmine.SpyObj<k8s.KubeConfig>;
let mockApiClient: jasmine.SpyObj<k8s.Core_v1Api>;
let mockCustomApiClient: jasmine.SpyObj<k8s.Custom_objectsApi>;
let mockApiClient: jasmine.SpyObj<k8s.CoreV1Api>;
let mockCustomApiClient: jasmine.SpyObj<k8s.CustomObjectsApi>;
let k8sService: KubernetesService;

beforeEach(() => {
Expand All @@ -17,13 +17,13 @@ describe('KubernetesService', () => {
'loadFromDefault', 'getContextObject', 'getCurrentContext',
'makeApiClient'
]);
mockApiClient = jasmine.createSpyObj<k8s.Core_v1Api>(
mockApiClient = jasmine.createSpyObj<k8s.CoreV1Api>(
'mockApiClient', ['listNamespace', 'listNamespacedEvent', 'listNode']);
mockCustomApiClient = jasmine.createSpyObj<k8s.Custom_objectsApi>(
mockCustomApiClient = jasmine.createSpyObj<k8s.CustomObjectsApi>(
'mockCustomApiClient', ['listNamespacedCustomObject']);
mockKubeConfig.makeApiClient.withArgs(k8s.Core_v1Api)
mockKubeConfig.makeApiClient.withArgs(k8s.CoreV1Api)
.and.returnValue(mockApiClient);
mockKubeConfig.makeApiClient.withArgs(k8s.Custom_objectsApi)
mockKubeConfig.makeApiClient.withArgs(k8s.CustomObjectsApi)
.and.returnValue(mockCustomApiClient);

k8sService = new KubernetesService(mockKubeConfig);
Expand Down Expand Up @@ -165,7 +165,7 @@ describe('KubernetesService', () => {
]
} as unknown; // needed to work around TS compiler
mockApiClient.listNamespacedEvent.and.returnValue(Promise.resolve(
{response: mockResponse, body: response as k8s.V1EventList}));
{response: mockResponse, body: response as k8s.CoreV1EventList}));

const events = await k8sService.getEventsForNamespace('kubeflow');
const eventNames = events.map((n) => n.metadata.name);
Expand Down
10 changes: 5 additions & 5 deletions dashboard/app/metrics_service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/** Time-series interval enumeration. */
export enum Interval {
Last5m,
Last15m,
Last30m,
Last60m,
Last180m
Last5m = 'Last5m',
Last15m = 'Last15m',
Last30m = 'Last30m',
Last60m = 'Last60m',
Last180m = 'Last180m',
}

/** Data-point contained in a time series. */
Expand Down
Loading