[UI] Remove Deprecated Katib UI (#2179)

* [UI] Remove Deprecated Katib UI

* Fix UI Developer doc
This commit is contained in:
Andrey Velichkevich
2023-07-25 09:53:29 +00:00
committed by GitHub
parent 6ab4b6ba67
commit 7d16660c2c
328 changed files with 13179 additions and 42363 deletions
-2
View File
@@ -4,5 +4,3 @@ docs
manifests
pkg/ui/*/frontend/node_modules
pkg/ui/*/frontend/build
pkg/new-ui/*/frontend/node_modules
pkg/new-ui/*/frontend/build
@@ -3,7 +3,7 @@ name: E2E Test with darts-cnn-cifar10
on:
pull_request:
paths-ignore:
- "pkg/new-ui/v1beta1/frontend/**"
- "pkg/ui/v1beta1/frontend/**"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
+1 -1
View File
@@ -3,7 +3,7 @@ name: E2E Test with enas-cnn-cifar10
on:
pull_request:
paths-ignore:
- "pkg/new-ui/v1beta1/frontend/**"
- "pkg/ui/v1beta1/frontend/**"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
+1 -1
View File
@@ -3,7 +3,7 @@ name: E2E Test with mxnet-mnist
on:
pull_request:
paths-ignore:
- "pkg/new-ui/v1beta1/frontend/**"
- "pkg/ui/v1beta1/frontend/**"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -3,7 +3,7 @@ name: E2E Test with pytorch-mnist
on:
pull_request:
paths-ignore:
- "pkg/new-ui/v1beta1/frontend/**"
- "pkg/ui/v1beta1/frontend/**"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
+1 -1
View File
@@ -3,7 +3,7 @@ name: E2E Test with simple-pbt
on:
pull_request:
paths-ignore:
- "pkg/new-ui/v1beta1/frontend/**"
- "pkg/ui/v1beta1/frontend/**"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -3,7 +3,7 @@ name: E2E Test with tf-mnist-with-summaries
on:
pull_request:
paths-ignore:
- "pkg/new-ui/v1beta1/frontend/**"
- "pkg/ui/v1beta1/frontend/**"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -4,7 +4,7 @@ on:
push:
pull_request:
paths-ignore:
- "pkg/new-ui/v1beta1/frontend/**"
- "pkg/ui/v1beta1/frontend/**"
jobs:
algorithm:
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
- component-name: katib-db-manager
dockerfile: cmd/db-manager/v1beta1/Dockerfile
- component-name: katib-ui
dockerfile: cmd/new-ui/v1beta1/Dockerfile
dockerfile: cmd/ui/v1beta1/Dockerfile
- component-name: cert-generator
dockerfile: cmd/cert-generator/v1beta1/Dockerfile
- component-name: file-metrics-collector
+1 -1
View File
@@ -4,7 +4,7 @@ on:
push:
pull_request:
paths-ignore:
- "pkg/new-ui/v1beta1/frontend/**"
- "pkg/ui/v1beta1/frontend/**"
jobs:
trial:
+1 -1
View File
@@ -3,7 +3,7 @@ name: Go Test
on:
pull_request:
paths-ignore:
- "pkg/new-ui/v1beta1/frontend/**"
- "pkg/ui/v1beta1/frontend/**"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
+1 -1
View File
@@ -3,7 +3,7 @@ name: Lint Files
on:
pull_request:
paths-ignore:
- "pkg/new-ui/v1beta1/frontend/**"
- "pkg/ui/v1beta1/frontend/**"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
+9 -9
View File
@@ -3,7 +3,7 @@ name: Frontend Test
on:
pull_request:
paths:
- pkg/new-ui/v1beta1/frontend/**
- pkg/ui/v1beta1/frontend/**
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -25,12 +25,12 @@ jobs:
- name: Format katib code
run: |
npm install prettier --prefix ./pkg/new-ui/v1beta1/frontend
npm install prettier --prefix ./pkg/ui/v1beta1/frontend
make prettier-check
- name: Lint katib code
run: |
cd pkg/new-ui/v1beta1/frontend
cd pkg/ui/v1beta1/frontend
npm run lint-check
frontend-unit-tests:
@@ -48,7 +48,7 @@ jobs:
- name: Fetch Kubeflow and install common code dependencies
run: |
COMMIT=$(cat pkg/new-ui/v1beta1/frontend/COMMIT)
COMMIT=$(cat pkg/ui/v1beta1/frontend/COMMIT)
cd /tmp && git clone https://github.com/kubeflow/kubeflow.git
cd kubeflow
git checkout $COMMIT
@@ -59,13 +59,13 @@ jobs:
- name: Install KWA dependencies
run: |
cd pkg/new-ui/v1beta1/frontend
cd pkg/ui/v1beta1/frontend
npm i
npm link kubeflow
- name: Run unit tests
run: |
cd pkg/new-ui/v1beta1/frontend
cd pkg/ui/v1beta1/frontend
npm run test:prod
frontend-ui-tests:
@@ -81,7 +81,7 @@ jobs:
- name: Fetch Kubeflow and install common code dependencies
run: |
COMMIT=$(cat pkg/new-ui/v1beta1/frontend/COMMIT)
COMMIT=$(cat pkg/ui/v1beta1/frontend/COMMIT)
cd /tmp && git clone https://github.com/kubeflow/kubeflow.git
cd kubeflow
git checkout $COMMIT
@@ -91,11 +91,11 @@ jobs:
npm link ./dist/kubeflow
- name: Install KWA dependencies
run: |
cd pkg/new-ui/v1beta1/frontend
cd pkg/ui/v1beta1/frontend
npm i
npm link kubeflow
- name: Serve UI & run Cypress tests in Chrome and Firefox
run: |
cd pkg/new-ui/v1beta1/frontend
cd pkg/ui/v1beta1/frontend
npm run start & npx wait-on http://localhost:4200
npm run ui-test-ci-all
+1 -1
View File
@@ -3,7 +3,7 @@ name: Python Test
on:
pull_request:
paths-ignore:
- "pkg/new-ui/v1beta1/frontend/**"
- "pkg/ui/v1beta1/frontend/**"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
+1 -1
View File
@@ -143,7 +143,7 @@ endif
# Prettier UI format check for Katib v1beta1.
prettier-check:
npm run format:check --prefix pkg/new-ui/v1beta1/frontend
npm run format:check --prefix pkg/ui/v1beta1/frontend
# Update boilerplate for the source code.
update-boilerplate:
-61
View File
@@ -1,61 +0,0 @@
# --- Clone the kubeflow/kubeflow code ---
FROM ubuntu AS fetch-kubeflow-kubeflow
RUN apt-get update && apt-get install git -y
WORKDIR /kf
COPY ./pkg/new-ui/v1beta1/frontend/COMMIT ./
RUN git clone https://github.com/kubeflow/kubeflow.git && \
COMMIT=$(cat ./COMMIT) && \
cd kubeflow && \
git checkout $COMMIT
# --- Build the frontend kubeflow library ---
FROM node:12 AS frontend-kubeflow-lib
WORKDIR /src
ARG LIB=/kf/kubeflow/components/crud-web-apps/common/frontend/kubeflow-common-lib
COPY --from=fetch-kubeflow-kubeflow $LIB/package*.json ./
RUN npm ci
COPY --from=fetch-kubeflow-kubeflow $LIB/ ./
RUN npm run build
# --- Build the frontend ---
FROM node:12 AS frontend
WORKDIR /src
COPY ./pkg/new-ui/v1beta1/frontend/package*.json ./
RUN npm ci
COPY ./pkg/new-ui/v1beta1/frontend/ .
COPY --from=frontend-kubeflow-lib /src/dist/kubeflow/ ./node_modules/kubeflow/
RUN npm run build:prod
# --- Build the backend ---
FROM golang:alpine AS go-build
ARG TARGETARCH
WORKDIR /go/src/github.com/kubeflow/katib
# Download packages.
COPY go.mod .
COPY go.sum .
RUN go mod download -x
# Copy sources.
COPY cmd/ cmd/
COPY pkg/ pkg/
# Build the binary.
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} go build -a -o katib-ui ./cmd/new-ui/v1beta1
# --- Compose the web app ---
FROM alpine:3.15
WORKDIR /app
COPY --from=go-build /go/src/github.com/kubeflow/katib/katib-ui /app/
COPY --from=frontend /src/dist/static /app/build/static/
ENTRYPOINT ["./katib-ui"]
-76
View File
@@ -1,76 +0,0 @@
/*
Copyright 2022 The Kubeflow Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"flag"
"fmt"
"log"
"net/http"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
common_v1beta1 "github.com/kubeflow/katib/pkg/common/v1beta1"
ui "github.com/kubeflow/katib/pkg/new-ui/v1beta1"
)
var (
port, host, buildDir, dbManagerAddr *string
)
func init() {
port = flag.String("port", "8080", "The port to listen to for incoming HTTP connections")
host = flag.String("host", "0.0.0.0", "The host to listen to for incoming HTTP connections")
buildDir = flag.String("build-dir", "/app/build", "The dir of frontend")
dbManagerAddr = flag.String("db-manager-address", common_v1beta1.GetDBManagerAddr(), "The address of Katib DB manager")
}
func main() {
flag.Parse()
kuh := ui.NewKatibUIHandler(*dbManagerAddr)
log.Printf("Serving the frontend dir %s", *buildDir)
frontend := http.FileServer(http.Dir(*buildDir))
http.HandleFunc("/katib/", kuh.ServeIndex(*buildDir))
http.Handle("/katib/static/", http.StripPrefix("/katib/", frontend))
http.HandleFunc("/katib/fetch_experiments/", kuh.FetchExperiments)
http.HandleFunc("/katib/create_experiment/", kuh.CreateExperiment)
http.HandleFunc("/katib/delete_experiment/", kuh.DeleteExperiment)
http.HandleFunc("/katib/fetch_experiment/", kuh.FetchExperiment)
http.HandleFunc("/katib/fetch_trial/", kuh.FetchTrial)
http.HandleFunc("/katib/fetch_suggestion/", kuh.FetchSuggestion)
http.HandleFunc("/katib/fetch_hp_job_info/", kuh.FetchHPJobInfo)
http.HandleFunc("/katib/fetch_hp_job_trial_info/", kuh.FetchHPJobTrialInfo)
http.HandleFunc("/katib/fetch_nas_job_info/", kuh.FetchNASJobInfo)
http.HandleFunc("/katib/fetch_trial_templates/", kuh.FetchTrialTemplates)
http.HandleFunc("/katib/add_template/", kuh.AddTemplate)
http.HandleFunc("/katib/edit_template/", kuh.EditTemplate)
http.HandleFunc("/katib/delete_template/", kuh.DeleteTemplate)
http.HandleFunc("/katib/fetch_namespaces", kuh.FetchNamespaces)
http.HandleFunc("/katib/fetch_trial_logs/", kuh.FetchTrialLogs)
log.Printf("Serving at %s:%s", *host, *port)
if err := http.ListenAndServe(fmt.Sprintf("%s:%s", *host, *port), nil); err != nil {
panic(err)
}
}
+38 -11
View File
@@ -1,13 +1,40 @@
# Build the Katib UI.
FROM node:12.18.1 AS npm-build
# --- Clone the kubeflow/kubeflow code ---
FROM ubuntu AS fetch-kubeflow-kubeflow
# Build frontend.
ADD /pkg/ui/v1beta1/frontend /frontend
RUN cd /frontend && npm ci
RUN cd /frontend && npm run build
RUN rm -rf /frontend/node_modules
RUN apt-get update && apt-get install git -y
# Build backend.
WORKDIR /kf
COPY ./pkg/ui/v1beta1/frontend/COMMIT ./
RUN git clone https://github.com/kubeflow/kubeflow.git && \
COMMIT=$(cat ./COMMIT) && \
cd kubeflow && \
git checkout $COMMIT
# --- Build the frontend kubeflow library ---
FROM node:12 AS frontend-kubeflow-lib
WORKDIR /src
ARG LIB=/kf/kubeflow/components/crud-web-apps/common/frontend/kubeflow-common-lib
COPY --from=fetch-kubeflow-kubeflow $LIB/package*.json ./
RUN npm ci
COPY --from=fetch-kubeflow-kubeflow $LIB/ ./
RUN npm run build
# --- Build the frontend ---
FROM node:12 AS frontend
WORKDIR /src
COPY ./pkg/ui/v1beta1/frontend/package*.json ./
RUN npm ci
COPY ./pkg/ui/v1beta1/frontend/ .
COPY --from=frontend-kubeflow-lib /src/dist/kubeflow/ ./node_modules/kubeflow/
RUN npm run build:prod
# --- Build the backend ---
FROM golang:alpine AS go-build
ARG TARGETARCH
@@ -24,11 +51,11 @@ COPY cmd/ cmd/
COPY pkg/ pkg/
# Build the binary.
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} go build -a -o katib-ui ./cmd/ui/v1beta1
RUN CGO_ENABLED=0 GOOS=linux GOARCH=${TARGETARCH} go build -a -o katib-ui ./cmd/ui/v1beta1
# Copy the backend and frontend into a thin image.
# --- Compose the web app ---
FROM alpine:3.15
WORKDIR /app
COPY --from=go-build /go/src/github.com/kubeflow/katib/katib-ui /app/
COPY --from=npm-build /frontend/build /app/build
COPY --from=frontend /src/dist/static /app/build/static/
ENTRYPOINT ["./katib-ui"]
+7 -6
View File
@@ -33,7 +33,7 @@ var (
)
func init() {
port = flag.String("port", "80", "The port to listen to for incoming HTTP connections")
port = flag.String("port", "8080", "The port to listen to for incoming HTTP connections")
host = flag.String("host", "0.0.0.0", "The host to listen to for incoming HTTP connections")
buildDir = flag.String("build-dir", "/app/build", "The dir of frontend")
dbManagerAddr = flag.String("db-manager-address", common_v1beta1.GetDBManagerAddr(), "The address of Katib DB manager")
@@ -45,17 +45,17 @@ func main() {
log.Printf("Serving the frontend dir %s", *buildDir)
frontend := http.FileServer(http.Dir(*buildDir))
http.Handle("/katib/", http.StripPrefix("/katib/", frontend))
http.HandleFunc("/katib/", kuh.ServeIndex(*buildDir))
http.Handle("/katib/static/", http.StripPrefix("/katib/", frontend))
http.HandleFunc("/katib/fetch_experiments/", kuh.FetchAllExperiments)
http.HandleFunc("/katib/fetch_experiments/", kuh.FetchExperiments)
http.HandleFunc("/katib/submit_yaml/", kuh.SubmitYamlJob)
http.HandleFunc("/katib/submit_hp_job/", kuh.SubmitParamsJob)
http.HandleFunc("/katib/submit_nas_job/", kuh.SubmitParamsJob)
http.HandleFunc("/katib/create_experiment/", kuh.CreateExperiment)
http.HandleFunc("/katib/delete_experiment/", kuh.DeleteExperiment)
http.HandleFunc("/katib/fetch_experiment/", kuh.FetchExperiment)
http.HandleFunc("/katib/fetch_trial/", kuh.FetchTrial)
http.HandleFunc("/katib/fetch_suggestion/", kuh.FetchSuggestion)
http.HandleFunc("/katib/fetch_hp_job_info/", kuh.FetchHPJobInfo)
@@ -67,6 +67,7 @@ func main() {
http.HandleFunc("/katib/edit_template/", kuh.EditTemplate)
http.HandleFunc("/katib/delete_template/", kuh.DeleteTemplate)
http.HandleFunc("/katib/fetch_namespaces", kuh.FetchNamespaces)
http.HandleFunc("/katib/fetch_trial_logs/", kuh.FetchTrialLogs)
log.Printf("Serving at %s:%s", *host, *port)
if err := http.ListenAndServe(fmt.Sprintf("%s:%s", *host, *port), nil); err != nil {
+1 -1
View File
@@ -132,7 +132,7 @@ Please see [new-algorithm-service.md](./new-algorithm-service.md).
## Katib UI documentation
Please see [Katib UI README](https://github.com/kubeflow/katib/tree/master/pkg/ui/v1beta1).
Please see [Katib UI README](../pkg/ui/v1beta1).
## Design proposals
-1
View File
@@ -6,7 +6,6 @@ require (
github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/awalterschulze/gographviz v2.0.3+incompatible
github.com/c-bata/goptuna v0.8.0
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
github.com/go-sql-driver/mysql v1.5.0
github.com/golang/mock v1.6.0
github.com/golang/protobuf v1.5.2
-2
View File
@@ -422,8 +422,6 @@ github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASx
github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew=
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I=
github.com/go-critic/go-critic v0.6.1/go.mod h1:SdNCfU0yF3UBjtaZGw6586/WocupMOJuiqgom5DsQxM=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-131
View File
@@ -1,131 +0,0 @@
# Katib User Interface
This is the source code for the Katib UI. Current version of Katib UI is v1beta1. On the official Kubeflow website [here](https://www.kubeflow.org/docs/components/katib/experiment/#running-the-experiment-from-the-katib-ui) you can find information how to use Katib UI.
We are using [Angular](https://angular.io/) framework to create frontend and Go as a backend.
We are using [Material UI](https://material.angular.io/) to design frontend. Try to use Material UI components to implement new Katib UI features.
## Folder structure
1. You can find `Dockerfile` and `main.go` - file to serve the UI under `cmd/new-ui/v1beta1`
1. You can find Go backend under `pkg/new-ui/v1beta1`
1. You can find Angular frontend under `pkg/new-ui/v1beta1/frontend`
## Requirements
To make changes to the UI you need to install:
- Tools, defined [here](https://github.com/kubeflow/katib/blob/master/docs/developer-guide.md#requirements).
- `node` (v12.18.1) and `npm` (v6.13). Recommended to install `node` and `npm` using [`nvm`](https://github.com/nvm-sh/nvm). After installing `nvm`, you can run `nvm install 12.18.1` to install `node` version 12.18.1 and run `nvm use 12.18.1` to use that version.
## Development
While development you have different ways to run Katib UI.
1. Build and serve only the frontend. The dev server will also be proxying requests to the backend
2. Build the frontend and serve it via the backend locally
### Serve only the frontend
You can run a webpack dev server that only exposes the frontend files, which can be useful for testing only the UI of the app. There's also a `proxy.conf.json` file which configures the dev server to send the backend requests to port `8000`.
In order to build the UI locally, and expose it with a webpack dev server you will need to:
1. Create a module from the [common library](https://github.com/kubeflow/kubeflow/tree/master/components/crud-web-apps/common/frontend/kubeflow-common-lib)
2. Install the node modules of the app and also link the common-library module
You can build the common library with:
```bash
COMMIT=$(cat ./frontend/COMMIT) \
&& cd /tmp && git clone https://github.com/kubeflow/kubeflow.git \
&& cd kubeflow \
&& git checkout $COMMIT \
&& cd components/crud-web-apps/common/frontend/kubeflow-common-lib
# build the common library module
npm i
npm run build
# link the module to your npm packages
# depending on where you npm stores the global packages you
# might need to use sudo
npm link dist/kubeflow
```
And then build and run the UI locally, on `localhost:4200`, with:
```bash
# If you've already cloned the repo then skip this step and just
# navigate to the pkg/new-ui/v1beta1/frontend dir
cd /tmp && git clone https://github.com/kubeflow/katib.git \
&& cd katib/pkg/new-ui/v1beta1/frontend
npm i
npm link kubeflow
npm run start
```
### Serve the UI from the backend
This is the recommended way to test the web app e2e. In order to build the UI and serve it via the backend, locally, you will need to:
1. Build the UI locally. You have to follow the steps from the previous section, but instead of running `npm run start` you need to run `npm run build:prod`. It builds the frontend artifacts under `frontend/dist/static` folder.
Moreover, you are able to run `npm run build:watch` instead of `npm run build:prod`. In that case, it starts a process which is watching the source code changes and building the frontend artifacts under `frontend/dist/static` folder.
Learn more about Angular scripts in the [official guide](https://angular.io/cli/build).
1. Run `kubectl port-forward svc/katib-db-manager 6789 -n kubeflow` to expose `katib-db-manager` service for external access. You can use [different ways](https://kubernetes.io/docs/tasks/access-application-cluster/) to get external address for Kubernetes service. After exposing service, you should be able to receive information by running `wget <external-ip>:<service-port>`. In case of port-forwarding above, you have to run `wget localhost:6789`.
1. Go to `cmd/new-ui/v1beta1`.
1. Run `main.go` file with appropriate flags, where:
- `--build-dir` - directory with the frontend artifacts.
- `--port` - port to access Katib UI.
- `--db-manager-address` - Katib DB manager external IP and port address.
For example, if you use port-forwarding to expose `katib-db-manager`, run this command:
```
export APP_DISABLE_AUTH=true
go run main.go --build-dir=../../../pkg/new-ui/v1beta1/frontend/dist --port=8080 --db-manager-address=localhost:6789
```
After that, you can access the UI using this URL: `http://localhost:8080/katib/`.
## Production
To run Katib UI in Production, after all changes in frontend and backend, you need to create an image for the UI. Under `/katib` directory run this: `docker build . -f cmd/new-ui/v1beta1/Dockerfile -t <name of your image>` to build the image.
After that, you can modify UI [deployment](https://github.com/kubeflow/katib/blob/master/manifests/v1beta1/components/ui/ui.yaml#L21) with your new image. Then, follow [these steps](https://www.kubeflow.org/docs/components/katib/hyperparameter/#accessing-the-katib-ui) to access Katib UI.
## Code style
To make frontend code consistent and easy to review we use [Prettier](https://prettier.io/). You can find Prettier config [here](https://github.com/kubeflow/katib/tree/master/pkg/new-ui/v1beta1/frontend/.prettierrc.yaml).
Check [here](https://prettier.io/docs/en/install.html), how to install Prettier CLI to check and format your code.
### IDE integration
For VSCode you can install plugin: "Prettier - Code formatter" and it picks Prettier config automatically.
You can edit [settings.json](https://code.visualstudio.com/docs/getstarted/settings#_settings-file-locations) file for VSCode to autoformat on save.
```json
"settings": {
"editor.formatOnSave": true
}
```
For others IDE see [this](https://prettier.io/docs/en/editors.html).
### Check and format code
Before submitting PR check and format your code. To check your code run `npm run format:check` under `/frontend` folder. To format your code run `npm run format:write` under `/frontend` folder.
If all files formatted you can submit the PR.
If you don't want to format some code, [here](https://prettier.io/docs/en/ignore.html) is an instruction how to disable Prettier.
-772
View File
@@ -1,772 +0,0 @@
/*
Copyright 2022 The Kubeflow Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1beta1
import (
"bytes"
"context"
"encoding/json"
"errors"
"io"
"log"
"net/http"
"path/filepath"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"sigs.k8s.io/controller-runtime/pkg/client"
experimentv1beta1 "github.com/kubeflow/katib/pkg/apis/controller/experiments/v1beta1"
suggestionv1beta1 "github.com/kubeflow/katib/pkg/apis/controller/suggestions/v1beta1"
trialsv1beta1 "github.com/kubeflow/katib/pkg/apis/controller/trials/v1beta1"
api_pb_v1beta1 "github.com/kubeflow/katib/pkg/apis/manager/v1beta1"
consts "github.com/kubeflow/katib/pkg/controller.v1beta1/consts"
"github.com/kubeflow/katib/pkg/util/v1beta1/katibclient"
corev1 "k8s.io/api/core/v1"
common "github.com/kubeflow/katib/pkg/apis/controller/common/v1beta1"
mccommon "github.com/kubeflow/katib/pkg/metricscollector/v1beta1/common"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)
func NewKatibUIHandler(dbManagerAddr string) *KatibUIHandler {
kclient, err := katibclient.NewClient(client.Options{})
if err != nil {
log.Printf("NewClient for Katib failed: %v", err)
panic(err)
}
return &KatibUIHandler{
katibClient: kclient,
dbManagerAddr: dbManagerAddr,
}
}
// ServeIndex will return index.html for any non-API URL
func (k *KatibUIHandler) ServeIndex(buildDir string) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
fp := filepath.Join(buildDir, "static/index.html")
log.Printf("Sending file %s for url: %s", fp, r.URL)
// never cache index.html
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate, max-age=0")
// return the contents of index.html
http.ServeFile(w, r, fp)
}
}
func (k *KatibUIHandler) connectManager() (*grpc.ClientConn, api_pb_v1beta1.DBManagerClient) {
conn, err := grpc.Dial(k.dbManagerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Printf("Dial to GRPC failed: %v", err)
return nil, nil
}
c := api_pb_v1beta1.NewDBManagerClient(conn)
return conn, c
}
func (k *KatibUIHandler) CreateExperiment(w http.ResponseWriter, r *http.Request) {
var data map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
log.Printf("Failed to decode body: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
dataJSON, ok := data["postData"]
if !ok {
msg := "Couldn't load the 'postData' field of the request's data"
log.Println(msg)
http.Error(w, msg, http.StatusInternalServerError)
return
}
jsonbody, err := json.Marshal(dataJSON)
if err != nil {
log.Printf("Marshal data for experiment failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
job := experimentv1beta1.Experiment{}
if err := json.Unmarshal(jsonbody, &job); err != nil {
log.Printf("Unmarshal experiment failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
namespace := job.ObjectMeta.Namespace
experimentName := job.ObjectMeta.Name
user, err := IsAuthorized(consts.ActionTypeCreate, namespace, consts.PluralExperiment, "", experimentName, experimentv1beta1.SchemeGroupVersion, k.katibClient.GetClient(), r)
if user == "" && err != nil {
log.Printf("No user provided in kubeflow-userid header.")
http.Error(w, err.Error(), http.StatusUnauthorized)
return
} else if err != nil {
log.Printf("The user: %s is not authorized to create experiment: %s in namespace: %s \n", user, experimentName, namespace)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
err = k.katibClient.CreateRuntimeObject(&job)
if err != nil {
log.Printf("CreateRuntimeObject from parameters failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func (k *KatibUIHandler) FetchExperiments(w http.ResponseWriter, r *http.Request) {
namespaces, ok := r.URL.Query()["namespace"]
if !ok {
log.Printf("No 'namespace' query parameter was provided.")
err := errors.New("no 'namespace' query parameter was provided")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
namespace := namespaces[0]
user, err := IsAuthorized(consts.ActionTypeList, namespace, consts.PluralExperiment, "", "", experimentv1beta1.SchemeGroupVersion, k.katibClient.GetClient(), r)
if user == "" && err != nil {
log.Printf("No user provided in kubeflow-userid header.")
http.Error(w, err.Error(), http.StatusUnauthorized)
return
} else if err != nil {
log.Printf("The user: %s is not authorized to list experiments in namespace: %s \n", user, namespace)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
experiments, err := k.getExperiments([]string{namespace})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
response, err := json.Marshal(experiments)
if err != nil {
log.Printf("Marshal experiments failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, err = w.Write(response); err != nil {
log.Printf("Write experiments failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func (k *KatibUIHandler) DeleteExperiment(w http.ResponseWriter, r *http.Request) {
namespaces, ok := r.URL.Query()["namespace"]
if !ok {
log.Printf("No 'namespace' query parameter was provided.")
err := errors.New("no 'namespace' query parameter was provided")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
experimentNames, ok := r.URL.Query()["experimentName"]
if !ok {
log.Printf("No experimentName provided in Query parameteres! Provide an 'experimentName' param")
err := errors.New("no experimentName provided")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
experimentName := experimentNames[0]
namespace := namespaces[0]
user, err := IsAuthorized(consts.ActionTypeDelete, namespace, consts.PluralExperiment, "", experimentName, experimentv1beta1.SchemeGroupVersion, k.katibClient.GetClient(), r)
if user == "" && err != nil {
log.Printf("No user provided in kubeflow-userid header.")
http.Error(w, err.Error(), http.StatusUnauthorized)
return
} else if err != nil {
log.Printf("The user: %s is not authorized to delete experiment: %s in namespace: %s \n", user, experimentName, namespace)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
experiment, err := k.katibClient.GetExperiment(experimentName, namespace)
if err != nil {
log.Printf("GetExperiment failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = k.katibClient.DeleteRuntimeObject(experiment)
if err != nil {
log.Printf("DeleteRuntimeObject failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
isExperimentDeleted := false
var experiments []ExperimentView
// Waiting until experiment will be deleted
for !isExperimentDeleted {
// At first, try to list experiments in cluster scope
experiments, err = k.getExperiments([]string{""})
if err != nil {
// If failed, just try to list experiments from own namespace
experiments, err = k.getExperiments([]string{})
}
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
isExperimentDeleted = true
for _, experiment := range experiments {
if experiment.Name == experimentName {
isExperimentDeleted = false
break
}
}
}
response, err := json.Marshal(experiments)
if err != nil {
log.Printf("Marshal HP and NAS experiments failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, err = w.Write(response); err != nil {
log.Printf("Write HP and NAS experiments failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// FetchTrialTemplates gets all trial templates in all namespaces
func (k *KatibUIHandler) FetchTrialTemplates(w http.ResponseWriter, r *http.Request) {
trialTemplatesViewList, err := k.getTrialTemplatesViewList(r)
if err != nil {
log.Printf("getTrialTemplatesViewList failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
TrialTemplatesResponse := TrialTemplatesResponse{
Data: trialTemplatesViewList,
}
response, err := json.Marshal(TrialTemplatesResponse)
if err != nil {
log.Printf("Marshal templates failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, err = w.Write(response); err != nil {
log.Printf("Write template failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// AddTemplate adds template to ConfigMap
func (k *KatibUIHandler) AddTemplate(w http.ResponseWriter, r *http.Request) {
var data map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
log.Printf("Failed to decode body: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
updatedConfigMapNamespace := data["updatedConfigMapNamespace"].(string)
updatedConfigMapName := data["updatedConfigMapName"].(string)
updatedConfigMapPath := data["updatedConfigMapPath"].(string)
updatedTemplateYaml := data["updatedTemplateYaml"].(string)
user, err := IsAuthorized(consts.ActionTypeCreate, updatedConfigMapNamespace, corev1.ResourceConfigMaps.String(), "", updatedConfigMapName, corev1.SchemeGroupVersion, k.katibClient.GetClient(), r)
if user == "" && err != nil {
log.Printf("No user provided in kubeflow-userid header.")
http.Error(w, err.Error(), http.StatusUnauthorized)
return
} else if err != nil {
log.Printf("The user: %s is not authorized to add configmap: %s in namespace: %s \n", user, updatedConfigMapName, updatedConfigMapNamespace)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
newTemplates, err := k.updateTrialTemplates(updatedConfigMapNamespace, updatedConfigMapName, "", updatedConfigMapPath, updatedTemplateYaml, ActionTypeAdd, r)
if err != nil {
log.Printf("updateTrialTemplates failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
TrialTemplatesResponse := TrialTemplatesResponse{
Data: newTemplates,
}
response, err := json.Marshal(TrialTemplatesResponse)
if err != nil {
log.Printf("Marhal failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, err = w.Write(response); err != nil {
log.Printf("Write failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// EditTemplate edits template in ConfigMap
func (k *KatibUIHandler) EditTemplate(w http.ResponseWriter, r *http.Request) {
var data map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
log.Printf("Failed to decode body: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
updatedConfigMapNamespace := data["updatedConfigMapNamespace"].(string)
updatedConfigMapName := data["updatedConfigMapName"].(string)
configMapPath := data["configMapPath"].(string)
updatedConfigMapPath := data["updatedConfigMapPath"].(string)
updatedTemplateYaml := data["updatedTemplateYaml"].(string)
user, err := IsAuthorized(consts.ActionTypeUpdate, updatedConfigMapNamespace, corev1.ResourceConfigMaps.String(), "", updatedConfigMapName, corev1.SchemeGroupVersion, k.katibClient.GetClient(), r)
if user == "" && err != nil {
log.Printf("No user provided in kubeflow-userid header.")
http.Error(w, err.Error(), http.StatusUnauthorized)
return
} else if err != nil {
log.Printf("The user: %s is not authorized to edit configmap: %s in namespace: %s \n", user, updatedConfigMapName, updatedConfigMapNamespace)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
newTemplates, err := k.updateTrialTemplates(updatedConfigMapNamespace, updatedConfigMapName, configMapPath, updatedConfigMapPath, updatedTemplateYaml, ActionTypeEdit, r)
if err != nil {
log.Printf("updateTrialTemplates failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
TrialTemplatesResponse := TrialTemplatesResponse{
Data: newTemplates,
}
response, err := json.Marshal(TrialTemplatesResponse)
if err != nil {
log.Printf("Marhal failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, err = w.Write(response); err != nil {
log.Printf("Write failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// DeleteTemplate deletes template in ConfigMap
func (k *KatibUIHandler) DeleteTemplate(w http.ResponseWriter, r *http.Request) {
var data map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
log.Printf("Failed to decode body: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
updatedConfigMapNamespace := data["updatedConfigMapNamespace"].(string)
updatedConfigMapName := data["updatedConfigMapName"].(string)
updatedConfigMapPath := data["updatedConfigMapPath"].(string)
user, err := IsAuthorized(consts.ActionTypeDelete, updatedConfigMapNamespace, corev1.ResourceConfigMaps.String(), "", updatedConfigMapName, corev1.SchemeGroupVersion, k.katibClient.GetClient(), r)
if user == "" && err != nil {
log.Printf("No user provided in kubeflow-userid header.")
http.Error(w, err.Error(), http.StatusUnauthorized)
return
} else if err != nil {
log.Printf("The user: %s is not authorized to delete configmap: %s in namespace: %s \n", user, updatedConfigMapName, updatedConfigMapNamespace)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
newTemplates, err := k.updateTrialTemplates(updatedConfigMapNamespace, updatedConfigMapName, "", updatedConfigMapPath, "", ActionTypeDelete, r)
if err != nil {
log.Printf("updateTrialTemplates failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
TrialTemplatesResponse := TrialTemplatesResponse{
Data: newTemplates,
}
response, err := json.Marshal(TrialTemplatesResponse)
if err != nil {
log.Printf("Marhal failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, err = w.Write(response); err != nil {
log.Printf("Write failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func (k *KatibUIHandler) FetchNamespaces(w http.ResponseWriter, r *http.Request) {
// Get all available namespaces
namespaces, err := k.getAvailableNamespaces()
if err != nil {
log.Printf("GetAvailableNamespaces failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
response, err := json.Marshal(namespaces)
if err != nil {
log.Printf("Marshal namespaces failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, err = w.Write(response); err != nil {
log.Printf("Write namespaces failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// FetchExperiment gets experiment in specific namespace.
func (k *KatibUIHandler) FetchExperiment(w http.ResponseWriter, r *http.Request) {
namespaces, ok := r.URL.Query()["namespace"]
if !ok {
log.Printf("No 'namespace' query parameter was provided.")
err := errors.New("no 'namespace' query parameter was provided")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
experimentNames, ok := r.URL.Query()["experimentName"]
if !ok {
log.Printf("No experimentName provided in Query parameteres! Provide an 'experimentName' param")
err := errors.New("no experimentName provided")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
experimentName := experimentNames[0]
namespace := namespaces[0]
user, err := IsAuthorized(consts.ActionTypeGet, namespace, consts.PluralExperiment, "", experimentName, experimentv1beta1.SchemeGroupVersion, k.katibClient.GetClient(), r)
if user == "" && err != nil {
log.Printf("No user provided in kubeflow-userid header.")
http.Error(w, err.Error(), http.StatusUnauthorized)
return
} else if err != nil {
log.Printf("The user: %s is not authorized to get experiment: %s in namespace: %s \n", user, experimentName, namespace)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
experiment, err := k.katibClient.GetExperiment(experimentName, namespace)
if err != nil {
log.Printf("GetExperiment failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
response, err := json.Marshal(experiment)
if err != nil {
log.Printf("Marshal Experiment failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, err = w.Write(response); err != nil {
log.Printf("Write Experiment failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// FetchSuggestion gets suggestion in specific namespace
func (k *KatibUIHandler) FetchSuggestion(w http.ResponseWriter, r *http.Request) {
namespaces, ok := r.URL.Query()["namespace"]
if !ok {
log.Printf("No namespace provided in Query parameters! Provide a 'namespace' param")
err := errors.New("no 'namespace' provided")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
suggestionNames, ok := r.URL.Query()["suggestionName"]
if !ok {
log.Printf("No experimentName provided in Query parameteres! Provide an 'experimentName' param")
err := errors.New("no experimentName provided")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
suggestionName := suggestionNames[0]
namespace := namespaces[0]
user, err := IsAuthorized(consts.ActionTypeGet, namespace, consts.PluralSuggestion, "", suggestionName, suggestionv1beta1.SchemeGroupVersion, k.katibClient.GetClient(), r)
if user == "" && err != nil {
log.Printf("No user provided in kubeflow-userid header.")
http.Error(w, err.Error(), http.StatusUnauthorized)
return
} else if err != nil {
log.Printf("The user: %s is not authorized to get suggestion: %s in namespace: %s \n", user, suggestionName, namespace)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
suggestion, err := k.katibClient.GetSuggestion(suggestionName, namespace)
if err != nil {
log.Printf("GetSuggestion failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
response, err := json.Marshal(suggestion)
if err != nil {
log.Printf("Marshal Suggestion failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, err = w.Write(response); err != nil {
log.Printf("Write Suggestion failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// FetchTrial gets trial in specific namespace.
func (k *KatibUIHandler) FetchTrial(w http.ResponseWriter, r *http.Request) {
namespaces, ok := r.URL.Query()["namespace"]
if !ok {
log.Printf("No namespace provided in Query parameters! Provide a 'namespace' param")
err := errors.New("no 'namespace' provided")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
trialNames, ok := r.URL.Query()["trialName"]
if !ok {
log.Printf("No trialName provided in Query parameters! Provide a 'trialName' param")
err := errors.New("no 'trialName' provided")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
trialName := trialNames[0]
namespace := namespaces[0]
user, err := IsAuthorized(consts.ActionTypeGet, namespace, consts.PluralTrial, "", trialName, trialsv1beta1.SchemeGroupVersion, k.katibClient.GetClient(), r)
if user == "" && err != nil {
log.Printf("No user provided in kubeflow-userid header.")
http.Error(w, err.Error(), http.StatusUnauthorized)
return
} else if err != nil {
log.Printf("The user: %s is not authorized to get trial: %s from namespace: %s \n", user, trialName, namespace)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
trial, err := k.katibClient.GetTrial(trialName, namespace)
if err != nil {
log.Printf("GetTrial failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
response, err := json.Marshal(trial)
if err != nil {
log.Printf("Marshal Trial failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, err = w.Write(response); err != nil {
log.Printf("Write trial failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// FetchTrialLogs fetches logs for a trial in specific namespace.
func (k *KatibUIHandler) FetchTrialLogs(w http.ResponseWriter, r *http.Request) {
namespaces, ok := r.URL.Query()["namespace"]
if !ok {
log.Printf("No namespace provided in Query parameters! Provide a 'namespace' param")
err := errors.New("no 'namespace' provided")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
trialNames, ok := r.URL.Query()["trialName"]
if !ok {
log.Printf("No trialName provided in Query parameters! Provide a 'trialName' param")
err := errors.New("no 'trialName' provided")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
trialName := trialNames[0]
namespace := namespaces[0]
user, err := IsAuthorized(consts.ActionTypeGet, namespace, consts.PluralTrial, "", trialName, trialsv1beta1.SchemeGroupVersion, k.katibClient.GetClient(), r)
if user == "" && err != nil {
log.Printf("No user provided in kubeflow-userid header.")
http.Error(w, err.Error(), http.StatusUnauthorized)
return
} else if err != nil {
log.Printf("The user: %s is not authorized to get trial: %s in namespace: %s \n", user, trialName, namespace)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
trial := &trialsv1beta1.Trial{}
if err := k.katibClient.GetClient().Get(context.Background(), types.NamespacedName{Name: trialName, Namespace: namespace}, trial); err != nil {
log.Printf("GetLogs failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// TODO: Use controller-runtime client instead of kubernetes client to get logs, once this is available
clientset, err := createKubernetesClientset()
if err != nil {
log.Printf("GetLogs failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
podName, err := fetchMasterPodName(clientset, trial)
if err != nil {
log.Printf("GetLogs failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
user, err = IsAuthorized(consts.ActionTypeGet, namespace, corev1.ResourcePods.String(), "log", podName, corev1.SchemeGroupVersion, k.katibClient.GetClient(), r)
if user == "" && err != nil {
log.Printf("No user provided in kubeflow-userid header.")
http.Error(w, err.Error(), http.StatusUnauthorized)
return
} else if err != nil {
log.Printf("The user: %s is not authorized to get pod logs: %s in namespace: %s \n", user, podName, namespace)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
podLogOpts := corev1.PodLogOptions{}
podLogOpts.Container = trial.Spec.PrimaryContainerName
if trial.Spec.MetricsCollector.Collector.Kind == common.StdOutCollector {
podLogOpts.Container = mccommon.MetricLoggerCollectorContainerName
}
logs, err := fetchPodLogs(clientset, namespace, podName, podLogOpts)
if err != nil {
log.Printf("GetLogs failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
response, err := json.Marshal(logs)
if err != nil {
log.Printf("Marshal logs failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, err = w.Write(response); err != nil {
log.Printf("Write logs failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// createKubernetesClientset returns kubernetes clientset
func createKubernetesClientset() (*kubernetes.Clientset, error) {
cfg, err := config.GetConfig()
if err != nil {
return nil, err
}
clientset, err := kubernetes.NewForConfig(cfg)
if err != nil {
return nil, err
}
return clientset, nil
}
// fetchMasterPodName returns name of the master pod for a trial
func fetchMasterPodName(clientset *kubernetes.Clientset, trial *trialsv1beta1.Trial) (string, error) {
selectionLabel := consts.LabelTrialName + "=" + trial.ObjectMeta.Name
for primaryKey, primaryValue := range trial.Spec.PrimaryPodLabels {
selectionLabel = selectionLabel + "," + primaryKey + "=" + primaryValue
}
podList, err := clientset.CoreV1().Pods(trial.ObjectMeta.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: selectionLabel})
if err != nil {
return "", err
}
if len(podList.Items) == 0 {
return "", errors.New(`Failed to find logs for this Trial. Make sure you've set "spec.trialTemplate.retain"
field to "true" in the Experiment definition. If this error persists then the Pod's logs are not currently
persisted in the cluster.`)
}
// If Pod is Running or Succeeded Pod, return it.
for _, pod := range podList.Items {
if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodRunning {
return pod.Name, nil
}
}
// Otherwise, return the first Failed Pod.
for _, pod := range podList.Items {
if pod.Status.Phase == corev1.PodFailed {
return pod.Name, nil
}
}
// Otherwise, return error since Pod is in the Pending state.
return "", errors.New("Failed to get logs for this Trial. Pod is in the Pending or Unknown state.")
}
// fetchPodLogs returns logs of a pod for the given job name and namespace
func fetchPodLogs(clientset *kubernetes.Clientset, namespace string, podName string, podLogOpts corev1.PodLogOptions) (string, error) {
req := clientset.CoreV1().Pods(namespace).GetLogs(podName, &podLogOpts)
podLogs, err := req.Stream(context.Background())
if err != nil {
return "", err
}
defer podLogs.Close()
buf := new(bytes.Buffer)
_, err = io.Copy(buf, podLogs)
if err != nil {
return "", err
}
str := buf.String()
return str, nil
}
-56
View File
@@ -1,56 +0,0 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out
# dependencies
/node_modules
# profiling files
chrome-profiler-events*.json
speed-measure-plugin*.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db
# Rok files
**/browse-in-rok*
# Font files (automatically generating)
/src/assets/fonts
# Cypress
cypress/screenshots
cypress/downloads
@@ -1,5 +0,0 @@
singleQuote: true
printWidth: 80
trailingComma: all
tabWidth: 2
arrowParens: avoid
File diff suppressed because it is too large Load Diff
-84
View File
@@ -1,84 +0,0 @@
{
"name": "frontend",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "npm run copyLibAssets && ng serve --proxy-config proxy.conf.json",
"build:watch": "npm run copyLibAssets && ng build --base-href /katib/ --deploy-url /katib/static/ --outputPath dist/static --watch",
"build:prod": "npm run copyLibAssets && ng build --base-href /katib/ --deploy-url /katib/static/ --outputPath dist/static --configuration production",
"copyLibAssets": "cp -r ./node_modules/kubeflow/assets/* ./src/assets/",
"test": "ng test",
"test:prod": "ng test --browsers=ChromeHeadless --watch=false",
"lint-check": "ng lint",
"lint": "ng lint --fix",
"format:check": "prettier --check 'src/**/*.{jsx,js,ts,html,scss,css}' || node scripts/check-format-error.js",
"format:write": "prettier --write 'src/**/*.{jsx,js,ts,html,scss,css}'",
"ui-test": "cypress open . --e2e",
"ui-test-ci": "cypress run . --e2e",
"ui-test-ci-all": "cypress run . --e2e --browser=chrome && cypress run . --e2e --browser=firefox"
},
"private": true,
"dependencies": {
"@angular/animations": "~12.2.7",
"@angular/cdk": "^12.2.7",
"@angular/cdk-experimental": "^12.2.7",
"@angular/common": "~12.2.7",
"@angular/compiler": "~12.2.7",
"@angular/core": "~12.2.7",
"@angular/forms": "~12.2.7",
"@angular/localize": "^12.2.7",
"@angular/material": "^12.2.7",
"@angular/platform-browser": "~12.2.7",
"@angular/platform-browser-dynamic": "~12.2.7",
"@angular/router": "~12.2.7",
"@fortawesome/angular-fontawesome": "^0.9.0",
"@fortawesome/fontawesome-svg-core": "^1.2.26",
"@fortawesome/free-brands-svg-icons": "^5.12.0",
"@fortawesome/free-solid-svg-icons": "^5.12.0",
"@swimlane/ngx-charts": "^19.0.1",
"date-fns": "1.29.0",
"echarts": "^5.3.2",
"echarts-stat": "^1.2.0",
"js-yaml": "^4.0.0",
"lodash-es": "4.17.21",
"material-icons": "^0.7.7",
"monaco-editor": "^0.33.0",
"ngx-echarts": "^8.0.1",
"ngx-json-viewer": "^2.4.0",
"rxjs": "~6.6.7",
"tslib": "^2.0.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "~12.2.7",
"@angular-eslint/builder": "12.7.0",
"@angular-eslint/eslint-plugin": "12.7.0",
"@angular-eslint/eslint-plugin-template": "12.7.0",
"@angular-eslint/schematics": "12.7.0",
"@angular-eslint/template-parser": "12.7.0",
"@angular/cli": "~12.2.7",
"@angular/compiler-cli": "~12.2.7",
"@angular/language-service": "~12.2.7",
"@cypress/schematic": "^2.3.0",
"@kubernetes/client-node": "^0.12.2",
"@types/jasmine": "~3.6.0",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^12.11.1",
"@typescript-eslint/eslint-plugin": "4.28.2",
"@typescript-eslint/parser": "4.28.2",
"codelyzer": "^6.0.0",
"cypress": "^10.10.0",
"eslint": "^7.26.0",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~6.3.16",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"prettier": "2.3.2",
"ts-node": "~7.0.0",
"typescript": "~4.3.5",
"wait-on": "^7.0.1"
}
}
@@ -1,6 +0,0 @@
console.error();
console.error('-----------------------------------------------');
console.error('Please run `npm run format:write` to format your code.');
console.error('-----------------------------------------------');
console.error();
process.exit(1);
-320
View File
@@ -1,320 +0,0 @@
/*
Copyright 2022 The Kubeflow Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1beta1
import (
"context"
"encoding/json"
"errors"
"log"
"net/http"
"strconv"
"strings"
"time"
commonv1beta1 "github.com/kubeflow/katib/pkg/apis/controller/common/v1beta1"
experimentv1beta1 "github.com/kubeflow/katib/pkg/apis/controller/experiments/v1beta1"
trialv1beta1 "github.com/kubeflow/katib/pkg/apis/controller/trials/v1beta1"
api_pb_v1beta1 "github.com/kubeflow/katib/pkg/apis/manager/v1beta1"
"github.com/kubeflow/katib/pkg/controller.v1beta1/consts"
)
const kfpRunIDAnnotation = "kubeflow-kale.org/kfp-run-uuid"
func (k *KatibUIHandler) FetchHPJobInfo(w http.ResponseWriter, r *http.Request) {
//enableCors(&w)
namespaces, ok := r.URL.Query()["namespace"]
if !ok {
log.Printf("No namespace provided in Query parameters! Provide a 'namespace' param")
err := errors.New("no 'namespace' provided")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
experimentNames, ok := r.URL.Query()["experimentName"]
if !ok {
log.Printf("No experimentName provided in Query parameteres! Provide an 'experimentName' param")
err := errors.New("no experimentName provided")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
experimentName := experimentNames[0]
namespace := namespaces[0]
user, err := IsAuthorized(consts.ActionTypeGet, namespace, consts.PluralExperiment, "", experimentName, experimentv1beta1.SchemeGroupVersion, k.katibClient.GetClient(), r)
if user == "" && err != nil {
log.Printf("No user provided in kubeflow-userid header.")
http.Error(w, err.Error(), http.StatusUnauthorized)
return
} else if err != nil {
log.Printf("The user: %s is not authorized to get experiments from namespace: %s \n", user, namespace)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
log.Printf("Start FetchHPJobInfo for Experiment: %v in namespace: %v", experimentName, namespace)
conn, c := k.connectManager()
defer conn.Close()
resultText := "Status,trialName"
experiment, err := k.katibClient.GetExperiment(experimentName, namespace)
if err != nil {
log.Printf("GetExperiment from HP job failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Printf("Got Experiment %v", experimentName)
metricsList := map[string]int{}
metricsName := experiment.Spec.Objective.ObjectiveMetricName
resultText += "," + metricsName
metricsList[metricsName] = 0
for i, m := range experiment.Spec.Objective.AdditionalMetricNames {
resultText += "," + m
metricsList[m] = i + 1
}
log.Printf("Got metrics names")
paramList := map[string]int{}
for i, p := range experiment.Spec.Parameters {
resultText += "," + p.Name
paramList[p.Name] = i + len(metricsList)
}
log.Printf("Got Parameters names")
_, err = IsAuthorized(consts.ActionTypeList, namespace, consts.PluralTrial, "", "", trialv1beta1.SchemeGroupVersion, k.katibClient.GetClient(), r)
if user == "" && err != nil {
log.Printf("No user provided in kubeflow-userid header.")
http.Error(w, err.Error(), http.StatusUnauthorized)
return
} else if err != nil {
log.Printf("The user: %s is not authorized to list trials from namespace: %s \n", user, namespace)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
trialList, err := k.katibClient.GetTrialList(experimentName, namespace)
if err != nil {
log.Printf("GetTrialList from HP job failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Printf("Got Trial List - Count: %v", len(trialList.Items))
// append a column for the Pipeline UID associated with the Trial
if havePipelineUID(trialList.Items) {
resultText += ",KFP Run"
}
foundPipelineUID := false
for _, t := range trialList.Items {
runUid, ok := t.GetAnnotations()[kfpRunIDAnnotation]
if !ok {
log.Printf("Trial %s has no pipeline run.", t.Name)
runUid = ""
} else {
foundPipelineUID = true
}
var lastTrialCondition string
// Take only the latest condition
if len(t.Status.Conditions) > 0 {
lastTrialCondition = string(t.Status.Conditions[len(t.Status.Conditions)-1].Type)
}
trialResText := make([]string, len(metricsList)+len(paramList))
if t.IsSucceeded() || t.IsEarlyStopped() {
obsLogResp, err := c.GetObservationLog(
context.Background(),
&api_pb_v1beta1.GetObservationLogRequest{
TrialName: t.Name,
StartTime: "",
EndTime: "",
},
)
if err != nil {
log.Printf("GetObservationLog from HP job failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
for _, m := range obsLogResp.ObservationLog.MetricLogs {
if trialResText[metricsList[m.Metric.Name]] == "" {
trialResText[metricsList[m.Metric.Name]] = m.Metric.Value
} else {
currentValue, _ := strconv.ParseFloat(m.Metric.Value, 64)
bestValue, _ := strconv.ParseFloat(trialResText[metricsList[m.Metric.Name]], 64)
if t.Spec.Objective.Type == commonv1beta1.ObjectiveTypeMinimize && currentValue < bestValue {
trialResText[metricsList[m.Metric.Name]] = m.Metric.Value
} else if t.Spec.Objective.Type == commonv1beta1.ObjectiveTypeMaximize && currentValue > bestValue {
trialResText[metricsList[m.Metric.Name]] = m.Metric.Value
}
}
}
}
for _, trialParam := range t.Spec.ParameterAssignments {
trialResText[paramList[trialParam.Name]] = trialParam.Value
}
resultText += "\n" + lastTrialCondition + "," + t.Name + "," + strings.Join(trialResText, ",")
if foundPipelineUID {
resultText += "," + runUid
}
}
log.Printf("Logs parsed, results:\n %v", resultText)
response, err := json.Marshal(resultText)
if err != nil {
log.Printf("Marshal result text for HP job failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, err = w.Write(response); err != nil {
log.Printf("Write result text for HP job failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// FetchHPJobTrialInfo returns all metrics for the HP Job Trial
func (k *KatibUIHandler) FetchHPJobTrialInfo(w http.ResponseWriter, r *http.Request) {
//enableCors(&w)
namespaces, ok := r.URL.Query()["namespace"]
if !ok {
log.Printf("No namespace provided in Query parameters! Provide a 'namespace' param")
err := errors.New("no 'namespace' provided")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
trialNames, ok := r.URL.Query()["trialName"]
if !ok {
log.Printf("No trialName provided in Query parameters! Provide a 'trialName' param")
err := errors.New("no 'trialName' provided")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
trialName := trialNames[0]
namespace := namespaces[0]
conn, c := k.connectManager()
defer conn.Close()
user, err := IsAuthorized(consts.ActionTypeList, namespace, consts.PluralTrial, "", trialName, trialv1beta1.SchemeGroupVersion, k.katibClient.GetClient(), r)
if user == "" && err != nil {
log.Printf("No user provided in kubeflow-userid header.")
http.Error(w, err.Error(), http.StatusUnauthorized)
return
} else if err != nil {
log.Printf("The user: %s is not authorized to get trial: %s from namespace: %s \n", user, trialName, namespace)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
trial, err := k.katibClient.GetTrial(trialName, namespace)
if err != nil {
log.Printf("GetTrial from HP job failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
objectiveType := trial.Spec.Objective.Type
// resultArray - array of arrays, where [i][0] - metricName, [i][1] - metricTime, [i][2] - metricValue
var resultArray [][]string
resultArray = append(resultArray, strings.Split("metricName,time,value", ","))
obsLogResp, err := c.GetObservationLog(
context.Background(),
&api_pb_v1beta1.GetObservationLogRequest{
TrialName: trialName,
StartTime: "",
EndTime: "",
},
)
if err != nil {
log.Printf("GetObservationLog failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// prevMetricTimeValue is the dict, where key = metric name,
// value = array, where [0] - Last metric time, [1] - Best metric value for this time
prevMetricTimeValue := make(map[string][]string)
for _, m := range obsLogResp.ObservationLog.MetricLogs {
parsedCurrentTime, _ := time.Parse(time.RFC3339Nano, m.TimeStamp)
formatCurrentTime := parsedCurrentTime.Format("2006-01-02T15:04:05")
if _, found := prevMetricTimeValue[m.Metric.Name]; !found {
prevMetricTimeValue[m.Metric.Name] = []string{"", ""}
}
newMetricValue, err := strconv.ParseFloat(m.Metric.Value, 64)
if err != nil {
log.Printf("ParseFloat for new metric value: %v failed: %v", m.Metric.Value, err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var prevMetricValue float64
if prevMetricTimeValue[m.Metric.Name][1] != "" {
prevMetricValue, err = strconv.ParseFloat(prevMetricTimeValue[m.Metric.Name][1], 64)
if err != nil {
log.Printf("ParseFloat for prev metric value: %v failed: %v", prevMetricTimeValue[m.Metric.Name][1], err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
if formatCurrentTime == prevMetricTimeValue[m.Metric.Name][0] &&
((objectiveType == commonv1beta1.ObjectiveTypeMinimize &&
newMetricValue < prevMetricValue) ||
(objectiveType == commonv1beta1.ObjectiveTypeMaximize &&
newMetricValue > prevMetricValue)) {
prevMetricTimeValue[m.Metric.Name][1] = m.Metric.Value
for i := len(resultArray) - 1; i >= 0; i-- {
if resultArray[i][0] == m.Metric.Name {
resultArray[i][2] = m.Metric.Value
break
}
}
} else if formatCurrentTime != prevMetricTimeValue[m.Metric.Name][0] {
resultArray = append(resultArray, []string{m.Metric.Name, formatCurrentTime, m.Metric.Value})
prevMetricTimeValue[m.Metric.Name][0] = formatCurrentTime
prevMetricTimeValue[m.Metric.Name][1] = m.Metric.Value
}
}
var resultText string
for _, metric := range resultArray {
resultText += strings.Join(metric, ",") + "\n"
}
response, err := json.Marshal(resultText)
if err != nil {
log.Printf("Marshal result text in Trial info failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, err = w.Write(response); err != nil {
log.Printf("Write result text in Trial info failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
-109
View File
@@ -1,109 +0,0 @@
/*
Copyright 2022 The Kubeflow Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1beta1
import (
"context"
"encoding/json"
"log"
"net/http"
"strconv"
trialsv1beta1 "github.com/kubeflow/katib/pkg/apis/controller/trials/v1beta1"
api_pb_v1beta1 "github.com/kubeflow/katib/pkg/apis/manager/v1beta1"
)
func (k *KatibUIHandler) FetchNASJobInfo(w http.ResponseWriter, r *http.Request) {
//enableCors(&w)
experimentName := r.URL.Query()["experimentName"][0]
namespace := r.URL.Query()["namespace"][0]
responseRaw := make([]NNView, 0)
var architecture string
var decoder string
conn, c := k.connectManager()
defer conn.Close()
trials, err := k.katibClient.GetTrialList(experimentName, namespace)
if err != nil {
log.Printf("GetTrialList from NAS job failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
log.Printf("Got Trial List")
for i, t := range trials.Items {
succeeded := false
for _, condition := range t.Status.Conditions {
if condition.Type == trialsv1beta1.TrialSucceeded {
succeeded = true
}
}
if succeeded {
obsLogResp, err := c.GetObservationLog(
context.Background(),
&api_pb_v1beta1.GetObservationLogRequest{
TrialName: t.Name,
StartTime: "",
EndTime: "",
},
)
if err != nil {
log.Printf("GetObservationLog from NAS job failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
metricsName := make([]string, 0)
metricsValue := make([]string, 0)
for _, m := range obsLogResp.ObservationLog.MetricLogs {
metricsName = append(metricsName, m.Metric.Name)
metricsValue = append(metricsValue, m.Metric.Value)
}
for _, trialParam := range t.Spec.ParameterAssignments {
if trialParam.Name == "architecture" {
architecture = trialParam.Value
}
if trialParam.Name == "nn_config" {
decoder = trialParam.Value
}
}
responseRaw = append(responseRaw, NNView{
Name: "Generation " + strconv.Itoa(i),
TrialName: t.Name,
Architecture: generateNNImage(architecture, decoder),
MetricsName: metricsName,
MetricsValue: metricsValue,
})
}
}
log.Printf("Logs parsed, result: %v", responseRaw)
response, err := json.Marshal(responseRaw)
if err != nil {
log.Printf("Marshal result in NAS job failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, err = w.Write(response); err != nil {
log.Printf("Write result in NAS job failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
-97
View File
@@ -1,97 +0,0 @@
/*
Copyright 2022 The Kubeflow Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1beta1
import (
v1beta1experiment "github.com/kubeflow/katib/pkg/apis/controller/experiments/v1beta1"
"github.com/kubeflow/katib/pkg/controller.v1beta1/consts"
"github.com/kubeflow/katib/pkg/util/v1beta1/katibclient"
)
var (
TrialTemplateLabel = map[string]string{
consts.LabelTrialTemplateConfigMapName: consts.LabelTrialTemplateConfigMapValue}
)
type Decoder struct {
Layers int `json:"num_layers"`
InputSize []int `json:"input_size"`
OutputSize []int `json:"output_size"`
Embedding map[int]*Block `json:"embedding"`
}
type Block struct {
ID int `json:"opt_id"`
Type string `json:"opt_type"`
Param Option `json:"opt_params"`
}
type Option struct {
FilterNumber string `json:"num_filter"`
FilterSize string `json:"filter_size"`
Stride string `json:"stride"`
}
type ExperimentView struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
Type string `json:"type"`
Status string `json:"status"`
v1beta1experiment.ExperimentStatus
}
type TrialTemplatesDataView struct {
ConfigMapNamespace string
ConfigMaps []ConfigMap
}
type TrialTemplatesResponse struct {
Data []TrialTemplatesDataView
}
type ConfigMap struct {
ConfigMapName string
Templates []Template
}
type Template struct {
Path string
Yaml string
}
type KatibUIHandler struct {
katibClient katibclient.Client
dbManagerAddr string
}
type NNView struct {
Name string
TrialName string
Architecture string
MetricsName []string
MetricsValue []string
}
type JobType string
const (
ExperimentTypeHP = "hp"
ExperimentTypeNAS = "nas"
ActionTypeAdd = "add"
ActionTypeEdit = "edit"
ActionTypeDelete = "delete"
)
-338
View File
@@ -1,338 +0,0 @@
/*
Copyright 2022 The Kubeflow Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1beta1
import (
"encoding/json"
"log"
"net/http"
"sort"
"strconv"
"strings"
"github.com/kubeflow/katib/pkg/controller.v1beta1/consts"
gographviz "github.com/awalterschulze/gographviz"
trialv1beta1 "github.com/kubeflow/katib/pkg/apis/controller/trials/v1beta1"
apiv1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func (k *KatibUIHandler) getExperiments(namespace []string) ([]ExperimentView, error) {
experiments := []ExperimentView{}
el, err := k.katibClient.GetExperimentList(namespace...)
if err != nil {
log.Printf("GetExperimentList failed: %v", err)
return nil, err
}
for _, experiment := range el.Items {
experimentLastCondition, err := experiment.GetLastConditionType()
if err != nil {
log.Printf("GetLastConditionType failed: %v", err)
return nil, err
}
tp := ExperimentTypeHP
if experiment.Spec.NasConfig != nil {
tp = ExperimentTypeNAS
}
newExperiment := ExperimentView{
experiment.Name,
experiment.Namespace,
tp,
string(experimentLastCondition),
experiment.Status,
}
experiments = append(experiments, newExperiment)
}
return experiments, nil
}
func havePipelineUID(trials []trialv1beta1.Trial) bool {
for _, t := range trials {
_, ok := t.GetAnnotations()[kfpRunIDAnnotation]
if ok {
return true
}
}
return false
}
func (k *KatibUIHandler) getTrialTemplatesViewList(r *http.Request) ([]TrialTemplatesDataView, error) {
trialTemplatesDataView := make([]TrialTemplatesDataView, 0)
// Get all available namespaces
namespaces, err := k.getAvailableNamespaces()
if err != nil {
log.Printf("GetAvailableNamespaces failed: %v", err)
return nil, err
}
// Get Trial Template ConfigMap for each namespace
for _, ns := range namespaces {
trialTemplatesConfigMapList, err := k.katibClient.GetTrialTemplates(ns)
if err != nil {
log.Printf("GetTrialTemplates failed: %v", err)
return nil, err
}
// Iterate over the trialTemplatesConfigMapList from all namespaces and filter out the
// configmaps that belong to namespaces other than kubeflow and the ones that the user has
// access privileges.
var newTrialTemplatesConfigMapList []apiv1.ConfigMap
for _, cmap := range trialTemplatesConfigMapList.Items {
if ns == consts.DefaultKatibNamespace {
// Add the configmaps from kubeflow namespace (no user can have access to kubeflow ns, so we hardcode it)
newTrialTemplatesConfigMapList = append(newTrialTemplatesConfigMapList, cmap)
} else {
// for all other namespaces check authorization rbac
configmapName := cmap.ObjectMeta.Name
user, err := IsAuthorized(consts.ActionTypeGet, ns, apiv1.ResourceConfigMaps.String(), "", configmapName, apiv1.SchemeGroupVersion, k.katibClient.GetClient(), r)
if err != nil {
log.Printf("The user: %s is not authorized to view configMap: %s in namespace: %s \n", user, configmapName, ns)
return nil, err
} else {
log.Printf("The user: %s is authorized to view configMap: %s in namespace: %s", user, configmapName, ns)
newTrialTemplatesConfigMapList = append(newTrialTemplatesConfigMapList, cmap)
}
}
}
if len(trialTemplatesConfigMapList.Items) != 0 {
newTrialTemplatesView := getTrialTemplatesView(newTrialTemplatesConfigMapList)
// ConfigMap with templates must exists in namespace
if len(newTrialTemplatesView.ConfigMaps) > 0 {
trialTemplatesDataView = append(trialTemplatesDataView, newTrialTemplatesView)
}
}
}
return trialTemplatesDataView, nil
}
func (k *KatibUIHandler) getAvailableNamespaces() ([]string, error) {
var namespaces []string
namespaceList, err := k.katibClient.GetNamespaceList()
if err != nil {
namespaces = append(namespaces, consts.DefaultKatibNamespace)
return namespaces, nil
}
for _, ns := range namespaceList.Items {
namespaces = append(namespaces, ns.ObjectMeta.Name)
}
return namespaces, nil
}
func getTrialTemplatesView(templatesConfigMapList []apiv1.ConfigMap) TrialTemplatesDataView {
trialTemplatesDataView := TrialTemplatesDataView{
ConfigMapNamespace: templatesConfigMapList[0].ObjectMeta.Namespace,
ConfigMaps: []ConfigMap{},
}
for _, configMap := range templatesConfigMapList {
newConfigMap := ConfigMap{
ConfigMapName: configMap.ObjectMeta.Name,
Templates: []Template{},
}
for key := range configMap.Data {
newTemplate := Template{
Path: key,
Yaml: configMap.Data[key],
}
newConfigMap.Templates = append(newConfigMap.Templates, newTemplate)
}
// Sort Trial Templates by Path
sort.SliceStable(newConfigMap.Templates, func(i, j int) bool {
return newConfigMap.Templates[i].Path <= newConfigMap.Templates[j].Path
})
// Templates with data must exists in ConfigMap
if len(newConfigMap.Templates) > 0 {
trialTemplatesDataView.ConfigMaps = append(trialTemplatesDataView.ConfigMaps, newConfigMap)
}
}
return trialTemplatesDataView
}
func (k *KatibUIHandler) updateTrialTemplates(
updatedConfigMapNamespace,
updatedConfigMapName,
configMapPath,
updatedConfigMapPath,
updatedTemplateYaml,
actionType string,
r *http.Request) ([]TrialTemplatesDataView, error) {
templates, err := k.katibClient.GetConfigMap(updatedConfigMapName, updatedConfigMapNamespace)
if err != nil && !(errors.IsNotFound(err) && actionType == ActionTypeAdd) {
log.Printf("GetConfigMap failed: %v", err)
return nil, err
}
if actionType == ActionTypeAdd {
if len(templates) == 0 {
templates = make(map[string]string)
templates[updatedConfigMapPath] = updatedTemplateYaml
} else {
templates[updatedConfigMapPath] = updatedTemplateYaml
}
} else if actionType == ActionTypeEdit {
delete(templates, configMapPath)
templates[updatedConfigMapPath] = updatedTemplateYaml
} else if actionType == ActionTypeDelete {
delete(templates, updatedConfigMapPath)
}
templatesConfigMap := &apiv1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: updatedConfigMapName,
Namespace: updatedConfigMapNamespace,
Labels: TrialTemplateLabel,
},
Data: templates,
}
// If templates is empty delete Trial template configMap
if len(templates) == 0 {
err = k.katibClient.DeleteRuntimeObject(templatesConfigMap)
if err != nil {
log.Printf("DeleteRuntimeObject failed: %v", err)
return nil, err
}
// If len(templates) == 1 and adding template, we must create new ConfigMap
} else if len(templates) == 1 && actionType == ActionTypeAdd {
err = k.katibClient.CreateRuntimeObject(templatesConfigMap)
if err != nil {
log.Printf("CreateRuntimeObject failed: %v", err)
return nil, err
}
// Otherwise updating configMap
} else {
err = k.katibClient.UpdateRuntimeObject(templatesConfigMap)
if err != nil {
log.Printf("UpdateRuntimeObject failed: %v", err)
return nil, err
}
}
newTemplates, err := k.getTrialTemplatesViewList(r)
if err != nil {
log.Printf("getTrialTemplatesViewList: %v", err)
return nil, err
}
return newTemplates, nil
}
func getNodeString(block *Block) string {
var nodeString string
switch block.Type {
case "convolution":
nodeString += block.Param.FilterSize + "x" + block.Param.FilterSize
nodeString += " conv\n"
nodeString += block.Param.FilterSize + " channels"
case "separable_convolution":
nodeString += block.Param.FilterSize + "x" + block.Param.FilterSize
nodeString += " sep_conv\n"
nodeString += block.Param.FilterSize + " channels"
case "depthwise_convolution":
nodeString += block.Param.FilterSize + "x" + block.Param.FilterSize
nodeString += " depth_conv\n"
case "reduction":
// TODO: Need to be fixed
nodeString += "3x3 max_pooling"
}
return strconv.Quote(nodeString)
}
func generateNNImage(architecture string, decoder string) string {
var architectureInt [][]int
if err := json.Unmarshal([]byte(architecture), &architectureInt); err != nil {
panic(err)
}
/*
Always has num_layers, input_size, output_size and embeding
Embeding is a map: int to Parameter
Parameter has id, type, Option
Beforehand substitute all ' to " and wrap the string in `
*/
replacedDecoder := strings.Replace(decoder, `'`, `"`, -1)
var decoderParsed Decoder
err := json.Unmarshal([]byte(replacedDecoder), &decoderParsed)
if err != nil {
panic(err)
}
graphAst, _ := gographviz.ParseString(`digraph G {}`)
graph := gographviz.NewGraph()
if err := gographviz.Analyse(graphAst, graph); err != nil {
panic(err)
}
if err = graph.AddNode("G", "0", map[string]string{"label": strconv.Quote("Input")}); err != nil {
panic(err)
}
var i int
for i = 0; i < len(architectureInt); i++ {
if err = graph.AddNode("G", strconv.Itoa(i+1), map[string]string{"label": getNodeString(decoderParsed.Embedding[architectureInt[i][0]])}); err != nil {
panic(err)
}
if err = graph.AddEdge(strconv.Itoa(i), strconv.Itoa(i+1), true, nil); err != nil {
panic(err)
}
for j := 1; j < i+1; j++ {
if architectureInt[i][j] == 1 {
if err = graph.AddEdge(strconv.Itoa(j-1), strconv.Itoa(i+1), true, nil); err != nil {
panic(err)
}
}
}
}
if err = graph.AddNode("G", strconv.Itoa(i+1), map[string]string{"label": strconv.Quote("GlobalAvgPool")}); err != nil {
panic(err)
}
if err = graph.AddEdge(strconv.Itoa(i), strconv.Itoa(i+1), true, nil); err != nil {
panic(err)
}
if err = graph.AddNode("G", strconv.Itoa(i+2), map[string]string{"label": strconv.Quote("FullConnect\nSoftmax")}); err != nil {
panic(err)
}
if err = graph.AddEdge(strconv.Itoa(i+1), strconv.Itoa(i+2), true, nil); err != nil {
panic(err)
}
if err = graph.AddNode("G", strconv.Itoa(i+3), map[string]string{"label": strconv.Quote("Output")}); err != nil {
panic(err)
}
if err = graph.AddEdge(strconv.Itoa(i+2), strconv.Itoa(i+3), true, nil); err != nil {
panic(err)
}
s := graph.String()
return s
}
+60 -24
View File
@@ -1,17 +1,17 @@
# Katib User Interface
This is the source code for the Katib UI. Current version of Katib UI is v1beta1. On the official Kubeflow website [here](https://www.kubeflow.org/docs/components/katib/experiment/#running-the-experiment-from-the-katib-ui) you can find information how to use Katib UI.
We are using [React](https://reactjs.org/) framework to create frontend and Go as a backend.
We are using [Angular](https://angular.io/) framework to create frontend and Go as a backend.
We are using [Material UI](https://material-ui.com/) to design frontend. Try to use Material UI components to implement new Katib UI features.
We are using [Material UI](https://material.angular.io/) to design frontend. Try to use Material UI components to implement new Katib UI features.
## Folder structure
1. You can find `Dockerfile` and `main.go` - file to serve the UI under [cmd/ui/v1beta1](https://github.com/kubeflow/katib/tree/master/cmd/ui/v1beta1).
1. You can find `Dockerfile` and `main.go` - file to serve the UI under [`cmd/ui/v1beta1`](../../../cmd/ui/v1beta1/)
1. You can find Go backend under [pkg/ui/v1beta1](https://github.com/kubeflow/katib/tree/master/pkg/ui/v1beta1).
1. You can find Go backend under [`pkg/ui/v1beta1`](../../../pkg/ui/v1beta1)
1. You can find React frontend under [pkg/ui/v1beta1/frontend](https://github.com/kubeflow/katib/tree/master/pkg/ui/v1beta1/frontend).
1. You can find Angular frontend under [`pkg/ui/v1beta1/frontend`](../../../pkg/ui/v1beta1/frontend/)
## Requirements
@@ -19,61 +19,97 @@ To make changes to the UI you need to install:
- Tools, defined [here](https://github.com/kubeflow/katib/blob/master/docs/developer-guide.md#requirements).
- `node` (v12 or later) and `npm` (v6.13 or later). Recommended to install `node` and `npm` using [`nvm`](https://github.com/nvm-sh/nvm). After installing `nvm`, you can run `nvm install 12.18.1` to install `node` version 12.18.1 and run `nvm use 12.18.1` to use that version.
- `node` (v12.18.1) and `npm` (v6.13). Recommended to install `node` and `npm` using [`nvm`](https://github.com/nvm-sh/nvm). After installing `nvm`, you can run `nvm install 12.18.1` to install `node` version 12.18.1 and run `nvm use 12.18.1` to use that version.
## Development
While development you have different ways to run Katib UI.
### First time
1. Build and serve only the frontend. The dev server will also be proxying requests to the backend
2. Build the frontend and serve it via the backend locally
1. Clone the repository.
### Serve only the frontend
1. Go to `/frontend` folder.
You can run a webpack dev server that only exposes the frontend files, which can be useful for testing only the UI of the app. There's also a `proxy.conf.json` file which configures the dev server to send the backend requests to port `8000`.
1. Run `npm install` to install all dependencies.
In order to build the UI locally, and expose it with a webpack dev server you will need to:
It creates `/frontend/node_modules` folder with all dependencies from [`package.json`](https://github.com/kubeflow/katib/blob/master/pkg/ui/v1beta1/frontend/package.json). If you want to add new package, run `npm install <package>@<version>`. That should update `/frontend/package.json` and `/frontend/package-lock.json` with the new dependency.
1. Create a module from the [common library](https://github.com/kubeflow/kubeflow/tree/master/components/crud-web-apps/common/frontend/kubeflow-common-lib)
2. Install the node modules of the app and also link the common-library module
### Start frontend server
You can build the common library with:
If you want to edit only frontend without connection to the backend, you can start frontend server in your local environment. For it, run `npm run start` under `/frontend` folder. You can access the UI using this URL: `http://localhost:3000/`.
```bash
COMMIT=$(cat ./frontend/COMMIT) \
&& cd /tmp && git clone https://github.com/kubeflow/kubeflow.git \
&& cd kubeflow \
&& git checkout $COMMIT \
&& cd components/crud-web-apps/common/frontend/kubeflow-common-lib
### Serve UI frontend and backend
# build the common library module
npm i
npm run build
You can serve Katib UI locally. To make it you need to follow these steps:
# link the module to your npm packages
# depending on where you npm stores the global packages you
# might need to use sudo
npm link dist/kubeflow
```
1. Run `npm run build` under `/frontend` folder. It creates `/frontend/build` directory with optimized production build.
And then build and run the UI locally, on `localhost:4200`, with:
If your `node` memory limit is not enough to build the frontend, you may see this error while building: `FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory`. To fix it, you can try to increase `node` memory limit. For that, change [`build`](https://github.com/kubeflow/katib/blob/master/pkg/ui/v1beta1/frontend/package.json#L28) script to `react-scripts --max_old_space_size=4096 build` to increase `node` memory up to 4 Gb.
```bash
# If you've already cloned the repo then skip this step and just
# navigate to the pkg/ui/v1beta1/frontend dir
cd /tmp && git clone https://github.com/kubeflow/katib.git \
&& cd katib/pkg/ui/v1beta1/frontend
npm i
npm link kubeflow
npm run start
```
### Serve the UI from the backend
This is the recommended way to test the web app e2e. In order to build the UI and serve it via the backend, locally, you will need to:
1. Build the UI locally. You have to follow the steps from the previous section, but instead of running `npm run start` you need to run `npm run build:prod`. It builds the frontend artifacts under `frontend/dist/static` folder.
Moreover, you are able to run `npm run build:watch` instead of `npm run build:prod`. In that case, it starts a process which is watching the source code changes and building the frontend artifacts under `frontend/dist/static` folder.
Learn more about Angular scripts in the [official guide](https://angular.io/cli/build).
1. Run `kubectl port-forward svc/katib-db-manager 6789 -n kubeflow` to expose `katib-db-manager` service for external access. You can use [different ways](https://kubernetes.io/docs/tasks/access-application-cluster/) to get external address for Kubernetes service. After exposing service, you should be able to receive information by running `wget <external-ip>:<service-port>`. In case of port-forwarding above, you have to run `wget localhost:6789`.
1. Go to `cmd/ui/v1beta1`.
1. Go to [`cmd/ui/v1beta1`](../../../cmd/ui/v1beta1/)
1. Run `main.go` file with appropriate flags, where:
- `--build-dir` - builded frontend directory.
- `--build-dir` - directory with the frontend artifacts.
- `--port` - port to access Katib UI.
- `--db-manager-address` - Katib DB manager external IP and port address.
For example, if you clone Katib repository to `/home` folder and use port-forwarding to expose `katib-db-manager`, run this command:
For example, if you use port-forwarding to expose `katib-db-manager`, run this command:
```
go run main.go --build-dir=/home/katib/pkg/ui/v1beta1/frontend/build --port=8080 --db-manager-address=localhost:6789
export APP_DISABLE_AUTH=true
go run main.go --build-dir=../../../pkg/ui/v1beta1/frontend/dist --port=8080 --db-manager-address=localhost:6789
```
After that, you can access the UI using this URL: `http://localhost:8080/katib/`.
## Production
To run Katib UI in Production, after all changes in frontend and backend, you need to create an image for the UI. Under `/katib` directory run this: `docker build . -f cmd/ui/v1beta1/Dockerfile -t <name of your image>` to build the image. If Docker resources are not enough to build the frontend, you get `node` out of memory error. You can try to increase Docker resources or modify `package.json` as detailed [above](https://github.com/kubeflow/katib/tree/master/pkg/ui/v1beta1#serve-ui-frontend-and-backend) at step 1.
To run Katib UI in Production, after all changes in frontend and backend, you need to create an image for the UI. Under `/katib` directory run this: `docker build . -f cmd/ui/v1beta1/Dockerfile -t <name of your image>` to build the image.
After that, you can modify UI [deployment](https://github.com/kubeflow/katib/blob/master/manifests/v1beta1/components/ui/ui.yaml#L21) with your new image. Then, follow [these steps](https://www.kubeflow.org/docs/components/katib/hyperparameter/#accessing-the-katib-ui) to access Katib UI.
After that, you can modify the [UI Deployment](../../../manifests/v1beta1/components/ui/ui.yaml) with
your new image. Then, follow
[these steps](https://www.kubeflow.org/docs/components/katib/hyperparameter/#accessing-the-katib-ui) to access Katib UI.
## Code style
To make frontend code consistent and easy to review we use [Prettier](https://prettier.io/). You can find Prettier config [here](https://github.com/kubeflow/katib/tree/master/pkg/ui/v1beta1/frontend/.prettierrc.yaml).
To make frontend code consistent and easy to review we use [Prettier](https://prettier.io/).
You can find Prettier config [here](../../../pkg/ui/v1beta1/frontend/.prettierrc.yaml).
Check [here](https://prettier.io/docs/en/install.html), how to install Prettier CLI to check and format your code.
### IDE integration
+434 -76
View File
@@ -17,19 +17,33 @@ limitations under the License.
package v1beta1
import (
"bytes"
"context"
"encoding/json"
"errors"
"io"
"log"
"net/http"
"path/filepath"
"github.com/ghodss/yaml"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
experimentv1beta1 "github.com/kubeflow/katib/pkg/apis/controller/experiments/v1beta1"
suggestionv1beta1 "github.com/kubeflow/katib/pkg/apis/controller/suggestions/v1beta1"
trialsv1beta1 "github.com/kubeflow/katib/pkg/apis/controller/trials/v1beta1"
api_pb_v1beta1 "github.com/kubeflow/katib/pkg/apis/manager/v1beta1"
consts "github.com/kubeflow/katib/pkg/controller.v1beta1/consts"
"github.com/kubeflow/katib/pkg/util/v1beta1/katibclient"
corev1 "k8s.io/api/core/v1"
common "github.com/kubeflow/katib/pkg/apis/controller/common/v1beta1"
mccommon "github.com/kubeflow/katib/pkg/metricscollector/v1beta1/common"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
"sigs.k8s.io/controller-runtime/pkg/client/config"
)
func NewKatibUIHandler(dbManagerAddr string) *KatibUIHandler {
@@ -38,12 +52,27 @@ func NewKatibUIHandler(dbManagerAddr string) *KatibUIHandler {
log.Printf("NewClient for Katib failed: %v", err)
panic(err)
}
return &KatibUIHandler{
katibClient: kclient,
dbManagerAddr: dbManagerAddr,
}
}
// ServeIndex will return index.html for any non-API URL
func (k *KatibUIHandler) ServeIndex(buildDir string) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
fp := filepath.Join(buildDir, "static/index.html")
log.Printf("Sending file %s for url: %s", fp, r.URL)
// never cache index.html
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate, max-age=0")
// return the contents of index.html
http.ServeFile(w, r, fp)
}
}
func (k *KatibUIHandler) connectManager() (*grpc.ClientConn, api_pb_v1beta1.DBManagerClient) {
conn, err := grpc.Dial(k.dbManagerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
@@ -54,7 +83,7 @@ func (k *KatibUIHandler) connectManager() (*grpc.ClientConn, api_pb_v1beta1.DBMa
return conn, c
}
func (k *KatibUIHandler) SubmitYamlJob(w http.ResponseWriter, r *http.Request) {
func (k *KatibUIHandler) CreateExperiment(w http.ResponseWriter, r *http.Request) {
var data map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
@@ -63,72 +92,73 @@ func (k *KatibUIHandler) SubmitYamlJob(w http.ResponseWriter, r *http.Request) {
return
}
job := experimentv1beta1.Experiment{}
if yamlContent, ok := data["yaml"].(string); ok {
err := yaml.Unmarshal([]byte(yamlContent), &job)
if err != nil {
log.Printf("Unmarshal YAML content failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = k.katibClient.CreateRuntimeObject(&job)
if err != nil {
log.Printf("CreateRuntimeObject from YAML failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
}
func (k *KatibUIHandler) SubmitParamsJob(w http.ResponseWriter, r *http.Request) {
//enableCors(&w)
var data map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
log.Printf("Failed to decode body: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
dataJSON, ok := data["postData"]
if !ok {
msg := "Couldn't load the 'postData' field of the request's data"
log.Println(msg)
http.Error(w, msg, http.StatusInternalServerError)
return
}
if data, ok := data["postData"]; ok {
jsonbody, err := json.Marshal(data)
if err != nil {
log.Printf("Marshal data for experiment failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
job := experimentv1beta1.Experiment{}
if err := json.Unmarshal(jsonbody, &job); err != nil {
log.Printf("Unmarshal experiment failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
dataMap := data.(map[string]interface{})
job.TypeMeta = metav1.TypeMeta{
APIVersion: "kubeflow.org/v1beta1",
Kind: "Experiment",
}
job.ObjectMeta = metav1.ObjectMeta{
Name: dataMap["metadata"].(map[string]interface{})["name"].(string),
Namespace: dataMap["metadata"].(map[string]interface{})["namespace"].(string),
}
err = k.katibClient.CreateRuntimeObject(&job)
if err != nil {
log.Printf("CreateRuntimeObject from parameters failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
}
// FetchAllExperiments gets HP and NAS experiments in all namespaces.
func (k *KatibUIHandler) FetchAllExperiments(w http.ResponseWriter, r *http.Request) {
// At first, try to list experiments in cluster scope
experiments, err := k.getExperiments([]string{""})
jsonbody, err := json.Marshal(dataJSON)
if err != nil {
// If failed, just try to list experiments from own namespace
experiments, err = k.getExperiments([]string{})
log.Printf("Marshal data for experiment failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
job := experimentv1beta1.Experiment{}
if err := json.Unmarshal(jsonbody, &job); err != nil {
log.Printf("Unmarshal experiment failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
namespace := job.ObjectMeta.Namespace
experimentName := job.ObjectMeta.Name
user, err := IsAuthorized(consts.ActionTypeCreate, namespace, consts.PluralExperiment, "", experimentName, experimentv1beta1.SchemeGroupVersion, k.katibClient.GetClient(), r)
if user == "" && err != nil {
log.Printf("No user provided in kubeflow-userid header.")
http.Error(w, err.Error(), http.StatusUnauthorized)
return
} else if err != nil {
log.Printf("The user: %s is not authorized to create experiment: %s in namespace: %s \n", user, experimentName, namespace)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
err = k.katibClient.CreateRuntimeObject(&job)
if err != nil {
log.Printf("CreateRuntimeObject from parameters failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func (k *KatibUIHandler) FetchExperiments(w http.ResponseWriter, r *http.Request) {
namespaces, ok := r.URL.Query()["namespace"]
if !ok {
log.Printf("No 'namespace' query parameter was provided.")
err := errors.New("no 'namespace' query parameter was provided")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
namespace := namespaces[0]
user, err := IsAuthorized(consts.ActionTypeList, namespace, consts.PluralExperiment, "", "", experimentv1beta1.SchemeGroupVersion, k.katibClient.GetClient(), r)
if user == "" && err != nil {
log.Printf("No user provided in kubeflow-userid header.")
http.Error(w, err.Error(), http.StatusUnauthorized)
return
} else if err != nil {
log.Printf("The user: %s is not authorized to list experiments in namespace: %s \n", user, namespace)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
experiments, err := k.getExperiments([]string{namespace})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -139,16 +169,44 @@ func (k *KatibUIHandler) FetchAllExperiments(w http.ResponseWriter, r *http.Requ
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, err = w.Write(response); err != nil {
log.Printf("Write experiments failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func (k *KatibUIHandler) DeleteExperiment(w http.ResponseWriter, r *http.Request) {
experimentName := r.URL.Query()["experimentName"][0]
namespace := r.URL.Query()["namespace"][0]
namespaces, ok := r.URL.Query()["namespace"]
if !ok {
log.Printf("No 'namespace' query parameter was provided.")
err := errors.New("no 'namespace' query parameter was provided")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
experimentNames, ok := r.URL.Query()["experimentName"]
if !ok {
log.Printf("No experimentName provided in Query parameteres! Provide an 'experimentName' param")
err := errors.New("no experimentName provided")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
experimentName := experimentNames[0]
namespace := namespaces[0]
user, err := IsAuthorized(consts.ActionTypeDelete, namespace, consts.PluralExperiment, "", experimentName, experimentv1beta1.SchemeGroupVersion, k.katibClient.GetClient(), r)
if user == "" && err != nil {
log.Printf("No user provided in kubeflow-userid header.")
http.Error(w, err.Error(), http.StatusUnauthorized)
return
} else if err != nil {
log.Printf("The user: %s is not authorized to delete experiment: %s in namespace: %s \n", user, experimentName, namespace)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
experiment, err := k.katibClient.GetExperiment(experimentName, namespace)
if err != nil {
@@ -156,6 +214,7 @@ func (k *KatibUIHandler) DeleteExperiment(w http.ResponseWriter, r *http.Request
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = k.katibClient.DeleteRuntimeObject(experiment)
if err != nil {
log.Printf("DeleteRuntimeObject failed: %v", err)
@@ -196,14 +255,16 @@ func (k *KatibUIHandler) DeleteExperiment(w http.ResponseWriter, r *http.Request
return
}
if _, err = w.Write(response); err != nil {
panic(err)
log.Printf("Write HP and NAS experiments failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// FetchTrialTemplates gets all trial templates in all namespaces
func (k *KatibUIHandler) FetchTrialTemplates(w http.ResponseWriter, r *http.Request) {
trialTemplatesViewList, err := k.getTrialTemplatesViewList()
trialTemplatesViewList, err := k.getTrialTemplatesViewList(r)
if err != nil {
log.Printf("getTrialTemplatesViewList failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -220,7 +281,7 @@ func (k *KatibUIHandler) FetchTrialTemplates(w http.ResponseWriter, r *http.Requ
return
}
if _, err = w.Write(response); err != nil {
log.Printf("Write templates failed: %v", err)
log.Printf("Write template failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
@@ -228,6 +289,7 @@ func (k *KatibUIHandler) FetchTrialTemplates(w http.ResponseWriter, r *http.Requ
// AddTemplate adds template to ConfigMap
func (k *KatibUIHandler) AddTemplate(w http.ResponseWriter, r *http.Request) {
var data map[string]interface{}
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
log.Printf("Failed to decode body: %v", err)
@@ -240,7 +302,18 @@ func (k *KatibUIHandler) AddTemplate(w http.ResponseWriter, r *http.Request) {
updatedConfigMapPath := data["updatedConfigMapPath"].(string)
updatedTemplateYaml := data["updatedTemplateYaml"].(string)
newTemplates, err := k.updateTrialTemplates(updatedConfigMapNamespace, updatedConfigMapName, "", updatedConfigMapPath, updatedTemplateYaml, ActionTypeAdd)
user, err := IsAuthorized(consts.ActionTypeCreate, updatedConfigMapNamespace, corev1.ResourceConfigMaps.String(), "", updatedConfigMapName, corev1.SchemeGroupVersion, k.katibClient.GetClient(), r)
if user == "" && err != nil {
log.Printf("No user provided in kubeflow-userid header.")
http.Error(w, err.Error(), http.StatusUnauthorized)
return
} else if err != nil {
log.Printf("The user: %s is not authorized to add configmap: %s in namespace: %s \n", user, updatedConfigMapName, updatedConfigMapNamespace)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
newTemplates, err := k.updateTrialTemplates(updatedConfigMapNamespace, updatedConfigMapName, "", updatedConfigMapPath, updatedTemplateYaml, ActionTypeAdd, r)
if err != nil {
log.Printf("updateTrialTemplates failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -280,7 +353,18 @@ func (k *KatibUIHandler) EditTemplate(w http.ResponseWriter, r *http.Request) {
updatedConfigMapPath := data["updatedConfigMapPath"].(string)
updatedTemplateYaml := data["updatedTemplateYaml"].(string)
newTemplates, err := k.updateTrialTemplates(updatedConfigMapNamespace, updatedConfigMapName, configMapPath, updatedConfigMapPath, updatedTemplateYaml, ActionTypeEdit)
user, err := IsAuthorized(consts.ActionTypeUpdate, updatedConfigMapNamespace, corev1.ResourceConfigMaps.String(), "", updatedConfigMapName, corev1.SchemeGroupVersion, k.katibClient.GetClient(), r)
if user == "" && err != nil {
log.Printf("No user provided in kubeflow-userid header.")
http.Error(w, err.Error(), http.StatusUnauthorized)
return
} else if err != nil {
log.Printf("The user: %s is not authorized to edit configmap: %s in namespace: %s \n", user, updatedConfigMapName, updatedConfigMapNamespace)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
newTemplates, err := k.updateTrialTemplates(updatedConfigMapNamespace, updatedConfigMapName, configMapPath, updatedConfigMapPath, updatedTemplateYaml, ActionTypeEdit, r)
if err != nil {
log.Printf("updateTrialTemplates failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -317,7 +401,18 @@ func (k *KatibUIHandler) DeleteTemplate(w http.ResponseWriter, r *http.Request)
updatedConfigMapName := data["updatedConfigMapName"].(string)
updatedConfigMapPath := data["updatedConfigMapPath"].(string)
newTemplates, err := k.updateTrialTemplates(updatedConfigMapNamespace, updatedConfigMapName, "", updatedConfigMapPath, "", ActionTypeDelete)
user, err := IsAuthorized(consts.ActionTypeDelete, updatedConfigMapNamespace, corev1.ResourceConfigMaps.String(), "", updatedConfigMapName, corev1.SchemeGroupVersion, k.katibClient.GetClient(), r)
if user == "" && err != nil {
log.Printf("No user provided in kubeflow-userid header.")
http.Error(w, err.Error(), http.StatusUnauthorized)
return
} else if err != nil {
log.Printf("The user: %s is not authorized to delete configmap: %s in namespace: %s \n", user, updatedConfigMapName, updatedConfigMapNamespace)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
newTemplates, err := k.updateTrialTemplates(updatedConfigMapNamespace, updatedConfigMapName, "", updatedConfigMapPath, "", ActionTypeDelete, r)
if err != nil {
log.Printf("updateTrialTemplates failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -366,8 +461,35 @@ func (k *KatibUIHandler) FetchNamespaces(w http.ResponseWriter, r *http.Request)
// FetchExperiment gets experiment in specific namespace.
func (k *KatibUIHandler) FetchExperiment(w http.ResponseWriter, r *http.Request) {
experimentName := r.URL.Query()["experimentName"][0]
namespace := r.URL.Query()["namespace"][0]
namespaces, ok := r.URL.Query()["namespace"]
if !ok {
log.Printf("No 'namespace' query parameter was provided.")
err := errors.New("no 'namespace' query parameter was provided")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
experimentNames, ok := r.URL.Query()["experimentName"]
if !ok {
log.Printf("No experimentName provided in Query parameteres! Provide an 'experimentName' param")
err := errors.New("no experimentName provided")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
experimentName := experimentNames[0]
namespace := namespaces[0]
user, err := IsAuthorized(consts.ActionTypeGet, namespace, consts.PluralExperiment, "", experimentName, experimentv1beta1.SchemeGroupVersion, k.katibClient.GetClient(), r)
if user == "" && err != nil {
log.Printf("No user provided in kubeflow-userid header.")
http.Error(w, err.Error(), http.StatusUnauthorized)
return
} else if err != nil {
log.Printf("The user: %s is not authorized to get experiment: %s in namespace: %s \n", user, experimentName, namespace)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
experiment, err := k.katibClient.GetExperiment(experimentName, namespace)
if err != nil {
@@ -390,8 +512,35 @@ func (k *KatibUIHandler) FetchExperiment(w http.ResponseWriter, r *http.Request)
// FetchSuggestion gets suggestion in specific namespace
func (k *KatibUIHandler) FetchSuggestion(w http.ResponseWriter, r *http.Request) {
suggestionName := r.URL.Query()["suggestionName"][0]
namespace := r.URL.Query()["namespace"][0]
namespaces, ok := r.URL.Query()["namespace"]
if !ok {
log.Printf("No namespace provided in Query parameters! Provide a 'namespace' param")
err := errors.New("no 'namespace' provided")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
suggestionNames, ok := r.URL.Query()["suggestionName"]
if !ok {
log.Printf("No experimentName provided in Query parameteres! Provide an 'experimentName' param")
err := errors.New("no experimentName provided")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
suggestionName := suggestionNames[0]
namespace := namespaces[0]
user, err := IsAuthorized(consts.ActionTypeGet, namespace, consts.PluralSuggestion, "", suggestionName, suggestionv1beta1.SchemeGroupVersion, k.katibClient.GetClient(), r)
if user == "" && err != nil {
log.Printf("No user provided in kubeflow-userid header.")
http.Error(w, err.Error(), http.StatusUnauthorized)
return
} else if err != nil {
log.Printf("The user: %s is not authorized to get suggestion: %s in namespace: %s \n", user, suggestionName, namespace)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
suggestion, err := k.katibClient.GetSuggestion(suggestionName, namespace)
if err != nil {
@@ -412,3 +561,212 @@ func (k *KatibUIHandler) FetchSuggestion(w http.ResponseWriter, r *http.Request)
return
}
}
// FetchTrial gets trial in specific namespace.
func (k *KatibUIHandler) FetchTrial(w http.ResponseWriter, r *http.Request) {
namespaces, ok := r.URL.Query()["namespace"]
if !ok {
log.Printf("No namespace provided in Query parameters! Provide a 'namespace' param")
err := errors.New("no 'namespace' provided")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
trialNames, ok := r.URL.Query()["trialName"]
if !ok {
log.Printf("No trialName provided in Query parameters! Provide a 'trialName' param")
err := errors.New("no 'trialName' provided")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
trialName := trialNames[0]
namespace := namespaces[0]
user, err := IsAuthorized(consts.ActionTypeGet, namespace, consts.PluralTrial, "", trialName, trialsv1beta1.SchemeGroupVersion, k.katibClient.GetClient(), r)
if user == "" && err != nil {
log.Printf("No user provided in kubeflow-userid header.")
http.Error(w, err.Error(), http.StatusUnauthorized)
return
} else if err != nil {
log.Printf("The user: %s is not authorized to get trial: %s from namespace: %s \n", user, trialName, namespace)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
trial, err := k.katibClient.GetTrial(trialName, namespace)
if err != nil {
log.Printf("GetTrial failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
response, err := json.Marshal(trial)
if err != nil {
log.Printf("Marshal Trial failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, err = w.Write(response); err != nil {
log.Printf("Write trial failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// FetchTrialLogs fetches logs for a trial in specific namespace.
func (k *KatibUIHandler) FetchTrialLogs(w http.ResponseWriter, r *http.Request) {
namespaces, ok := r.URL.Query()["namespace"]
if !ok {
log.Printf("No namespace provided in Query parameters! Provide a 'namespace' param")
err := errors.New("no 'namespace' provided")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
trialNames, ok := r.URL.Query()["trialName"]
if !ok {
log.Printf("No trialName provided in Query parameters! Provide a 'trialName' param")
err := errors.New("no 'trialName' provided")
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
trialName := trialNames[0]
namespace := namespaces[0]
user, err := IsAuthorized(consts.ActionTypeGet, namespace, consts.PluralTrial, "", trialName, trialsv1beta1.SchemeGroupVersion, k.katibClient.GetClient(), r)
if user == "" && err != nil {
log.Printf("No user provided in kubeflow-userid header.")
http.Error(w, err.Error(), http.StatusUnauthorized)
return
} else if err != nil {
log.Printf("The user: %s is not authorized to get trial: %s in namespace: %s \n", user, trialName, namespace)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
trial := &trialsv1beta1.Trial{}
if err := k.katibClient.GetClient().Get(context.Background(), types.NamespacedName{Name: trialName, Namespace: namespace}, trial); err != nil {
log.Printf("GetLogs failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// TODO: Use controller-runtime client instead of kubernetes client to get logs, once this is available
clientset, err := createKubernetesClientset()
if err != nil {
log.Printf("GetLogs failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
podName, err := fetchMasterPodName(clientset, trial)
if err != nil {
log.Printf("GetLogs failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
user, err = IsAuthorized(consts.ActionTypeGet, namespace, corev1.ResourcePods.String(), "log", podName, corev1.SchemeGroupVersion, k.katibClient.GetClient(), r)
if user == "" && err != nil {
log.Printf("No user provided in kubeflow-userid header.")
http.Error(w, err.Error(), http.StatusUnauthorized)
return
} else if err != nil {
log.Printf("The user: %s is not authorized to get pod logs: %s in namespace: %s \n", user, podName, namespace)
http.Error(w, err.Error(), http.StatusForbidden)
return
}
podLogOpts := corev1.PodLogOptions{}
podLogOpts.Container = trial.Spec.PrimaryContainerName
if trial.Spec.MetricsCollector.Collector.Kind == common.StdOutCollector {
podLogOpts.Container = mccommon.MetricLoggerCollectorContainerName
}
logs, err := fetchPodLogs(clientset, namespace, podName, podLogOpts)
if err != nil {
log.Printf("GetLogs failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
response, err := json.Marshal(logs)
if err != nil {
log.Printf("Marshal logs failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, err = w.Write(response); err != nil {
log.Printf("Write logs failed: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// createKubernetesClientset returns kubernetes clientset
func createKubernetesClientset() (*kubernetes.Clientset, error) {
cfg, err := config.GetConfig()
if err != nil {
return nil, err
}
clientset, err := kubernetes.NewForConfig(cfg)
if err != nil {
return nil, err
}
return clientset, nil
}
// fetchMasterPodName returns name of the master pod for a trial
func fetchMasterPodName(clientset *kubernetes.Clientset, trial *trialsv1beta1.Trial) (string, error) {
selectionLabel := consts.LabelTrialName + "=" + trial.ObjectMeta.Name
for primaryKey, primaryValue := range trial.Spec.PrimaryPodLabels {
selectionLabel = selectionLabel + "," + primaryKey + "=" + primaryValue
}
podList, err := clientset.CoreV1().Pods(trial.ObjectMeta.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: selectionLabel})
if err != nil {
return "", err
}
if len(podList.Items) == 0 {
return "", errors.New(`Failed to find logs for this Trial. Make sure you've set "spec.trialTemplate.retain"
field to "true" in the Experiment definition. If this error persists then the Pod's logs are not currently
persisted in the cluster.`)
}
// If Pod is Running or Succeeded Pod, return it.
for _, pod := range podList.Items {
if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodRunning {
return pod.Name, nil
}
}
// Otherwise, return the first Failed Pod.
for _, pod := range podList.Items {
if pod.Status.Phase == corev1.PodFailed {
return pod.Name, nil
}
}
// Otherwise, return error since Pod is in the Pending state.
return "", errors.New("Failed to get logs for this Trial. Pod is in the Pending or Unknown state.")
}
// fetchPodLogs returns logs of a pod for the given job name and namespace
func fetchPodLogs(clientset *kubernetes.Clientset, namespace string, podName string, podLogOpts corev1.PodLogOptions) (string, error) {
req := clientset.CoreV1().Pods(namespace).GetLogs(podName, &podLogOpts)
podLogs, err := req.Stream(context.Background())
if err != nil {
return "", err
}
defer podLogs.Close()
buf := new(bytes.Buffer)
_, err = io.Copy(buf, podLogs)
if err != nil {
return "", err
}
str := buf.String()
return str, nil
}
+47 -14
View File
@@ -1,23 +1,56 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# profiling files
chrome-profiler-events*.json
speed-measure-plugin*.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
Thumbs.db
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Rok files
**/browse-in-rok*
# build
/build
# Font files (automatically generating)
/src/assets/fonts
# Cypress
cypress/screenshots
cypress/downloads
+1 -1
View File
@@ -1,5 +1,5 @@
singleQuote: true
printWidth: 100
printWidth: 80
trailingComma: all
tabWidth: 2
arrowParens: avoid
+12329 -13856
View File
File diff suppressed because it is too large Load Diff
+78 -31
View File
@@ -1,37 +1,84 @@
{
"name": "frontend",
"homepage": ".",
"version": "0.1.0",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "npm run copyLibAssets && ng serve --proxy-config proxy.conf.json",
"build:watch": "npm run copyLibAssets && ng build --base-href /katib/ --deploy-url /katib/static/ --outputPath dist/static --watch",
"build:prod": "npm run copyLibAssets && ng build --base-href /katib/ --deploy-url /katib/static/ --outputPath dist/static --configuration production",
"copyLibAssets": "cp -r ./node_modules/kubeflow/assets/* ./src/assets/",
"test": "ng test",
"test:prod": "ng test --browsers=ChromeHeadless --watch=false",
"lint-check": "ng lint",
"lint": "ng lint --fix",
"format:check": "prettier --check 'src/**/*.{jsx,js,ts,html,scss,css}' || node scripts/check-format-error.js",
"format:write": "prettier --write 'src/**/*.{jsx,js,ts,html,scss,css}'",
"ui-test": "cypress open . --e2e",
"ui-test-ci": "cypress run . --e2e",
"ui-test-ci-all": "cypress run . --e2e --browser=chrome && cypress run . --e2e --browser=firefox"
},
"private": true,
"dependencies": {
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"ace-builds": "^1.4.8",
"axios": "^0.21.2",
"graphviz-react": "^1.0.6",
"js-yaml": "^3.13.1",
"plotly.js": "^1.45.0",
"react": "^16.8.3",
"react-ace": "^8.0.0",
"react-dom": "^16.8.3",
"react-plotly.js": "^2.3.0",
"react-redux": "^6.0.1",
"react-router-dom": "^4.3.1",
"react-scripts": "5.0.1",
"redux": "^4.0.1",
"redux-logger": "^3.0.6",
"redux-saga": "^1.0.1"
"@angular/animations": "~12.2.7",
"@angular/cdk": "^12.2.7",
"@angular/cdk-experimental": "^12.2.7",
"@angular/common": "~12.2.7",
"@angular/compiler": "~12.2.7",
"@angular/core": "~12.2.7",
"@angular/forms": "~12.2.7",
"@angular/localize": "^12.2.7",
"@angular/material": "^12.2.7",
"@angular/platform-browser": "~12.2.7",
"@angular/platform-browser-dynamic": "~12.2.7",
"@angular/router": "~12.2.7",
"@fortawesome/angular-fontawesome": "^0.9.0",
"@fortawesome/fontawesome-svg-core": "^1.2.26",
"@fortawesome/free-brands-svg-icons": "^5.12.0",
"@fortawesome/free-solid-svg-icons": "^5.12.0",
"@swimlane/ngx-charts": "^19.0.1",
"date-fns": "1.29.0",
"echarts": "^5.3.2",
"echarts-stat": "^1.2.0",
"js-yaml": "^4.0.0",
"lodash-es": "4.17.21",
"material-icons": "^0.7.7",
"monaco-editor": "^0.33.0",
"ngx-echarts": "^8.0.1",
"ngx-json-viewer": "^2.4.0",
"rxjs": "~6.6.7",
"tslib": "^2.0.0",
"zone.js": "~0.11.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"format:check": "prettier --check 'src/**/*.{jsx,js}' || node scripts/check-format-error.js",
"format:write": "prettier --write 'src/**/*.{jsx,js}'"
},
"browserslist": [
">0.2%",
"not dead",
"not ie <= 11",
"not op_mini all"
]
"devDependencies": {
"@angular-devkit/build-angular": "~12.2.7",
"@angular-eslint/builder": "12.7.0",
"@angular-eslint/eslint-plugin": "12.7.0",
"@angular-eslint/eslint-plugin-template": "12.7.0",
"@angular-eslint/schematics": "12.7.0",
"@angular-eslint/template-parser": "12.7.0",
"@angular/cli": "~12.2.7",
"@angular/compiler-cli": "~12.2.7",
"@angular/language-service": "~12.2.7",
"@cypress/schematic": "^2.3.0",
"@kubernetes/client-node": "^0.12.2",
"@types/jasmine": "~3.6.0",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^12.11.1",
"@typescript-eslint/eslint-plugin": "4.28.2",
"@typescript-eslint/parser": "4.28.2",
"codelyzer": "^6.0.0",
"cypress": "^10.10.0",
"eslint": "^7.26.0",
"jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~6.3.16",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~3.0.2",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.5.0",
"prettier": "2.3.2",
"ts-node": "~7.0.0",
"typescript": "~4.3.5",
"wait-on": "^7.0.1"
}
}
-35
View File
@@ -1,35 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<script defer src="/dashboard_lib.bundle.js"></script>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/logo.png" />
<meta http-equiv="Access-Control-Allow-Origin" content="*" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<style>
body {
background-color: #f7f7f7;
}
</style>
<title>Katib UI</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

@@ -1,15 +0,0 @@
{
"short_name": "Katib UI",
"name": "Dashboard for Katib",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
@@ -1,219 +0,0 @@
export const TOGGLE_MENU = 'TOGGLE_MENU';
export const toggleMenu = state => {
return {
type: TOGGLE_MENU,
state,
};
};
export const CLOSE_SNACKBAR = 'CLOSE_SNACKBAR';
export const closeSnackbar = () => {
return {
type: CLOSE_SNACKBAR,
};
};
export const SUBMIT_YAML_REQUEST = 'SUBMIT_YAML_REQUEST';
export const SUBMIT_YAML_FAILURE = 'SUBMIT_YAML_FAILURE';
export const SUBMIT_YAML_SUCCESS = 'SUBMIT_YAML_SUCCESS';
export const submitYaml = (yaml, globalNamespace) => ({
type: SUBMIT_YAML_REQUEST,
yaml,
globalNamespace,
});
export const DELETE_EXPERIMENT_REQUEST = 'DELETE_EXPERIMENT_REQUEST';
export const DELETE_EXPERIMENT_FAILURE = 'DELETE_EXPERIMENT_FAILURE';
export const DELETE_EXPERIMENT_SUCCESS = 'DELETE_EXPERIMENT_SUCCESS';
export const deleteExperiment = (name, namespace) => ({
type: DELETE_EXPERIMENT_REQUEST,
name,
namespace,
});
export const OPEN_DELETE_EXPERIMENT_DIALOG = 'OPEN_DELETE_EXPERIMENT_DIALOG';
export const openDeleteExperimentDialog = (name, namespace) => ({
type: OPEN_DELETE_EXPERIMENT_DIALOG,
name,
namespace,
});
export const CLOSE_DELETE_EXPERIMENT_DIALOG = 'CLOSE_DELETE_EXPERIMENT_DIALOG';
export const closeDeleteExperimentDialog = () => ({
type: CLOSE_DELETE_EXPERIMENT_DIALOG,
});
export const FETCH_NAMESPACES_REQUEST = 'FETCH_NAMESPACES_REQUEST';
export const FETCH_NAMESPACES_SUCCESS = 'FETCH_NAMESPACES_SUCCESS';
export const FETCH_NAMESPACES_FAILURE = 'FETCH_NAMESPACES_FAILURE';
export const fetchNamespaces = () => ({
type: FETCH_NAMESPACES_REQUEST,
});
export const CHANGE_GLOBAL_NAMESPACE = 'CHANGE_GLOBAL_NAMESPACE';
export const FETCH_EXPERIMENT_REQUEST = 'FETCH_EXPERIMENT_REQUEST';
export const FETCH_EXPERIMENT_SUCCESS = 'FETCH_EXPERIMENT_SUCCESS';
export const FETCH_EXPERIMENT_FAILURE = 'FETCH_EXPERIMENT_FAILURE';
export const fetchExperiment = (name, namespace) => ({
type: FETCH_EXPERIMENT_REQUEST,
name,
namespace,
});
export const CLOSE_DIALOG_EXPERIMENT = 'CLOSE_DIALOG_EXPERIMENT';
export const closeDialogExperiment = () => ({
type: CLOSE_DIALOG_EXPERIMENT,
});
export const FETCH_SUGGESTION_REQUEST = 'FETCH_SUGGESTION_REQUEST';
export const FETCH_SUGGESTION_SUCCESS = 'FETCH_SUGGESTION_SUCCESS';
export const FETCH_SUGGESTION_FAILURE = 'FETCH_SUGGESTION_FAILURE';
export const fetchSuggestion = (name, namespace) => ({
type: FETCH_SUGGESTION_REQUEST,
name,
namespace,
});
export const CLOSE_DIALOG_SUGGESTION = 'CLOSE_DIALOG_SUGGESTION';
export const closeDialogSuggestion = () => ({
type: CLOSE_DIALOG_SUGGESTION,
});
export const FETCH_EXPERIMENTS_REQUEST = 'FETCH_EXPERIMENTS_REQUEST';
export const FETCH_EXPERIMENTS_SUCCESS = 'FETCH_EXPERIMENTS_SUCCESS';
export const FETCH_EXPERIMENTS_FAILURE = 'FETCH_EXPERIMENTS_FAILURE';
export const fetchExperiments = () => ({
type: FETCH_EXPERIMENTS_REQUEST,
});
export const FILTER_EXPERIMENTS = 'FILTER_EXPERIMENTS';
export const filterExperiments = (experimentName, experimentNamespace) => ({
type: FILTER_EXPERIMENTS,
experimentName,
experimentNamespace,
});
export const CHANGE_STATUS = 'CHANGE_STATUS';
export const changeStatus = (filter, checked) => ({
type: CHANGE_STATUS,
filter,
checked,
});
export const CHANGE_EARLY_STOPPING_ALGORITHM = 'CHANGE_EARLY_STOPPING_ALGORITHM';
export const changeEarlyStoppingAlgorithm = algorithmName => ({
type: CHANGE_EARLY_STOPPING_ALGORITHM,
algorithmName,
});
export const ADD_EARLY_STOPPING_SETTING = 'ADD_EARLY_STOPPING_SETTING';
export const addEarlyStoppingSetting = () => ({
type: ADD_EARLY_STOPPING_SETTING,
});
export const CHANGE_EARLY_STOPPING_SETTING = 'CHANGE_EARLY_STOPPING_SETTING';
export const changeEarlyStoppingSetting = (index, field, value) => ({
type: CHANGE_EARLY_STOPPING_SETTING,
index,
field,
value,
});
export const DELETE_EARLY_STOPPING_SETTING = 'DELETE_EARLY_STOPPING_SETTING';
export const deleteEarlyStoppingSetting = index => ({
type: DELETE_EARLY_STOPPING_SETTING,
index,
});
export const CHANGE_TRIAL_TEMPLATE_SOURCE = 'CHANGE_TRIAL_TEMPLATE_SOURCE';
export const changeTrialTemplateSource = source => ({
type: CHANGE_TRIAL_TEMPLATE_SOURCE,
source,
});
export const ADD_PRIMARY_POD_LABEL = 'ADD_PRIMARY_POD_LABEL';
export const addPrimaryPodLabel = () => ({
type: ADD_PRIMARY_POD_LABEL,
});
export const CHANGE_PRIMARY_POD_LABEL = 'CHANGE_PRIMARY_POD_LABEL';
export const changePrimaryPodLabel = (fieldName, index, value) => ({
type: CHANGE_PRIMARY_POD_LABEL,
fieldName,
index,
value,
});
export const DELETE_PRIMARY_POD_LABEL = 'DELETE_PRIMARY_POD_LABEL';
export const deletePrimaryPodLabel = index => ({
type: DELETE_PRIMARY_POD_LABEL,
index,
});
export const CHANGE_TRIAL_TEMPLATE_SPEC = 'CHANGE_TRIAL_TEMPLATE_SPEC';
export const changeTrialTemplateSpec = (name, value) => ({
type: CHANGE_TRIAL_TEMPLATE_SPEC,
name,
value,
});
export const FILTER_TEMPLATES_EXPERIMENT = 'FILTER_TEMPLATES_EXPERIMENT';
export const filterTemplatesExperiment = (
configMapNamespaceIndex,
configMapNameIndex,
configMapPathIndex,
) => ({
type: FILTER_TEMPLATES_EXPERIMENT,
configMapNamespaceIndex,
configMapNameIndex,
configMapPathIndex,
});
export const CHANGE_TRIAL_TEMPLATE_YAML = 'CHANGE_TRIAL_TEMPLATE_YAML';
export const changeTrialTemplateYAML = templateYAML => ({
type: CHANGE_TRIAL_TEMPLATE_YAML,
templateYAML,
});
export const CHANGE_TRIAL_PARAMETERS = 'CHANGE_TRIAL_PARAMETERS';
export const changeTrialParameters = (index, name, reference, description) => ({
type: CHANGE_TRIAL_PARAMETERS,
index,
name,
reference,
description,
});
export const VALIDATION_ERROR = 'VALIDATION_ERROR';
export const validationError = message => ({
type: VALIDATION_ERROR,
message,
});
@@ -1,217 +0,0 @@
export const CHANGE_YAML_HP = 'CHANGE_YAML_HP';
export const changeYaml = yaml => ({
type: CHANGE_YAML_HP,
payload: yaml,
});
export const CHANGE_META_HP = 'CHANGE_META_HP';
export const changeMeta = (name, value) => ({
type: CHANGE_META_HP,
name,
value,
});
export const CHANGE_SPEC_HP = 'CHANGE_SPEC_HP';
export const changeSpec = (name, value) => ({
type: CHANGE_SPEC_HP,
name,
value,
});
export const CHANGE_OBJECTIVE_HP = 'CHANGE_OBJECTIVE_HP';
export const changeObjective = (name, value) => ({
type: CHANGE_OBJECTIVE_HP,
name,
value,
});
export const ADD_METRICS_HP = 'ADD_METRICS_HP';
export const addMetrics = () => ({
type: ADD_METRICS_HP,
});
export const DELETE_METRICS_HP = 'DELETE_METRICS_HP';
export const deleteMetrics = index => ({
type: DELETE_METRICS_HP,
index,
});
export const EDIT_METRICS_HP = 'EDIT_METRICS_HP';
export const editMetrics = (index, value) => ({
type: EDIT_METRICS_HP,
index,
value,
});
export const CHANGE_METRIC_STRATEGY_HP = 'CHANGE_METRIC_STRATEGY_HP';
export const metricStrategyChange = (index, strategy) => ({
type: CHANGE_METRIC_STRATEGY_HP,
index,
strategy,
});
export const CHANGE_ALGORITHM_NAME_HP = 'CHANGE_ALGORITHM_NAME_HP';
export const changeAlgorithmName = algorithmName => ({
type: CHANGE_ALGORITHM_NAME_HP,
algorithmName,
});
export const ADD_ALGORITHM_SETTING_HP = 'ADD_ALGORITHM_SETTING_HP';
export const addAlgorithmSetting = () => ({
type: ADD_ALGORITHM_SETTING_HP,
});
export const CHANGE_ALGORITHM_SETTING_HP = 'CHANGE_ALGORITHM_SETTING_HP';
export const changeAlgorithmSetting = (index, field, value) => ({
type: CHANGE_ALGORITHM_SETTING_HP,
field,
value,
index,
});
export const DELETE_ALGORITHM_SETTING_HP = 'DELETE_ALGORITHM_SETTING_HP';
export const deleteAlgorithmSetting = index => ({
type: DELETE_ALGORITHM_SETTING_HP,
index,
});
export const ADD_PARAMETER_HP = 'CHANGE_PARAMETER_HP';
export const addParameter = () => ({
type: ADD_PARAMETER_HP,
});
export const EDIT_PARAMETER_HP = 'EDIT_PARAMTER_HP';
export const editParameter = (index, field, value) => ({
type: EDIT_PARAMETER_HP,
index,
field,
value,
});
export const DELETE_PARAMETER_HP = 'DELETE_PARAMETER_HP';
export const deleteParameter = index => ({
type: DELETE_PARAMETER_HP,
index,
});
export const ADD_LIST_PARAMETER_HP = 'ADD_LIST_PARAMETER_HP';
export const addListParameter = paramIndex => ({
type: ADD_LIST_PARAMETER_HP,
paramIndex,
});
export const EDIT_LIST_PARAMETER_HP = 'EDIT_LIST_PARAMETER_HP';
export const editListParameter = (paramIndex, index, value) => ({
type: EDIT_LIST_PARAMETER_HP,
paramIndex,
index,
value,
});
export const DELETE_LIST_PARAMETER_HP = 'DELETE_LIST_PARAMETER_HP';
export const deleteListParameter = (paramIndex, index) => ({
type: DELETE_LIST_PARAMETER_HP,
paramIndex,
index,
});
export const SUBMIT_HP_JOB_REQUEST = 'SUBMIT_HP_JOB_REQUEST';
export const SUBMIT_HP_JOB_SUCCESS = 'SUBMIT_HP_JOB_SUCCESS';
export const SUBMIT_HP_JOB_FAILURE = 'SUBMIT_HP_JOB_FAILURE';
export const submitHPJob = data => ({
type: SUBMIT_HP_JOB_REQUEST,
data,
});
export const CHANGE_MC_KIND_HP = 'CHANGE_MC_KIND_HP';
export const changeMCKindHP = kind => ({
type: CHANGE_MC_KIND_HP,
kind,
});
export const CHANGE_MC_FILE_SYSTEM_HP = 'CHANGE_MC_FILE_SYSTEM_HP';
export const changeMCFileSystemHP = (kind, path) => ({
type: CHANGE_MC_FILE_SYSTEM_HP,
kind,
path,
});
export const CHANGE_MC_HTTP_GET_HP = 'CHANGE_MC_HTTP_GET_HP';
export const changeMCHttpGetHP = (port, path, scheme, host) => ({
type: CHANGE_MC_HTTP_GET_HP,
port,
path,
scheme,
host,
});
export const ADD_MC_HTTP_GET_HEADER_HP = 'ADD_MC_HTTP_GET_HEADER_HP';
export const addMCHttpGetHeaderHP = () => ({
type: ADD_MC_HTTP_GET_HEADER_HP,
});
export const CHANGE_MC_HTTP_GET_HEADER_HP = 'CHANGE_MC_HTTP_GET_HEADER_HP';
export const changeMCHttpGetHeaderHP = (fieldName, value, index) => ({
type: CHANGE_MC_HTTP_GET_HEADER_HP,
fieldName,
value,
index,
});
export const DELETE_MC_HTTP_GET_HEADER_HP = 'DELETE_MC_HTTP_GET_HEADER_HP';
export const deleteMCHttpGetHeaderHP = index => ({
type: DELETE_MC_HTTP_GET_HEADER_HP,
index,
});
export const ADD_MC_METRICS_FORMAT_HP = 'ADD_MC_METRICS_FORMAT_HP';
export const addMCMetricsFormatHP = () => ({
type: ADD_MC_METRICS_FORMAT_HP,
});
export const CHANGE_MC_METRIC_FORMAT_HP = 'CHANGE_MC_METRIC_FORMAT_HP';
export const changeMCMetricsFormatHP = (format, index) => ({
type: CHANGE_MC_METRIC_FORMAT_HP,
format,
index,
});
export const DELETE_MC_METRIC_FORMAT_HP = 'DELETE_MC_METRIC_FORMAT_HP';
export const deleteMCMetricsFormatHP = index => ({
type: DELETE_MC_METRIC_FORMAT_HP,
index,
});
export const CHANGE_MC_CUSTOM_CONTAINER_HP = 'CHANGE_MC_CUSTOM_CONTAINER_HP';
export const changeMCCustomContainerHP = yamlContainer => ({
type: CHANGE_MC_CUSTOM_CONTAINER_HP,
yamlContainer,
});
@@ -1,25 +0,0 @@
export const FETCH_HP_JOB_INFO_REQUEST = 'FETCH_HP_JOB_INFO_REQUEST';
export const FETCH_HP_JOB_INFO_SUCCESS = 'FETCH_HP_JOB_INFO_SUCCESS';
export const FETCH_HP_JOB_INFO_FAILURE = 'FETCH_HP_JOB_INFO_FAILURE';
export const fetchHPJobInfo = (name, namespace) => ({
type: FETCH_HP_JOB_INFO_REQUEST,
name,
namespace,
});
export const FETCH_HP_JOB_TRIAL_INFO_REQUEST = 'FETCH_HP_JOB_TRIAL_INFO_REQUEST';
export const FETCH_HP_JOB_TRIAL_INFO_SUCCESS = 'FETCH_HP_JOB_TRIAL_INFO_SUCCESS';
export const FETCH_HP_JOB_TRIAL_INFO_FAILURE = 'FETCH_HP_JOB_TRIAL_INFO_FAILURE';
export const fetchHPJobTrialInfo = (trialName, namespace) => ({
type: FETCH_HP_JOB_TRIAL_INFO_REQUEST,
trialName,
namespace,
});
export const CLOSE_DIALOG_TRIAL = 'CLOSE_DIALOG_TRIAL';
export const closeDialogTrial = () => ({
type: CLOSE_DIALOG_TRIAL,
});
@@ -1,282 +0,0 @@
export const CHANGE_YAML_NAS = 'CHANGE_YAML_NAS';
export const changeYaml = yaml => ({
type: CHANGE_YAML_NAS,
payload: yaml,
});
export const CHANGE_META_NAS = 'CHANGE_META_NAS';
export const changeMeta = (name, value) => ({
type: CHANGE_META_NAS,
name,
value,
});
export const CHANGE_SPEC_NAS = 'CHANGE_SPEC_NAS';
export const changeSpec = (name, value) => ({
type: CHANGE_SPEC_NAS,
name,
value,
});
export const CHANGE_OBJECTIVE_NAS = 'CHANGE_OBJECTIVE_NAS';
export const changeObjective = (name, value) => ({
type: CHANGE_OBJECTIVE_NAS,
name,
value,
});
export const ADD_METRICS_NAS = 'ADD_METRICS_NAS';
export const addMetrics = () => ({
type: ADD_METRICS_NAS,
});
export const DELETE_METRICS_NAS = 'DELETE_METRICS_NAS';
export const deleteMetrics = index => ({
type: DELETE_METRICS_NAS,
index,
});
export const EDIT_METRICS_NAS = 'EDIT_METRICS_NAS';
export const editMetrics = (index, value) => ({
type: EDIT_METRICS_NAS,
index,
value,
});
export const CHANGE_METRIC_STRATEGY_NAS = 'CHANGE_METRIC_STRATEGY_NAS';
export const metricStrategyChange = (index, strategy) => ({
type: CHANGE_METRIC_STRATEGY_NAS,
index,
strategy,
});
export const CHANGE_ALGORITHM_NAME_NAS = 'CHANGE_ALGORITHM_NAME_NAS';
export const changeAlgorithmName = algorithmName => ({
type: CHANGE_ALGORITHM_NAME_NAS,
algorithmName,
});
export const ADD_ALGORITHM_SETTING_NAS = 'ADD_ALGORITHM_SETTING_NAS';
export const addAlgorithmSetting = () => ({
type: ADD_ALGORITHM_SETTING_NAS,
});
export const CHANGE_ALGORITHM_SETTING_NAS = 'CHANGE_ALGORITHM_SETTING_NAS';
export const changeAlgorithmSetting = (index, field, value) => ({
type: CHANGE_ALGORITHM_SETTING_NAS,
field,
value,
index,
});
export const DELETE_ALGORITHM_SETTING_NAS = 'DELETE_ALGORITHM_SETTING_NAS';
export const deleteAlgorithmSetting = index => ({
type: DELETE_ALGORITHM_SETTING_NAS,
index,
});
export const EDIT_NUM_LAYERS = 'EDIT_NUM_LAYERS';
export const editNumLayers = value => ({
type: EDIT_NUM_LAYERS,
value,
});
export const ADD_SIZE = 'ADD_SIZE';
export const addSize = sizeType => ({
type: ADD_SIZE,
sizeType,
});
export const EDIT_SIZE = 'EDIT_SIZE';
export const editSize = (sizeType, index, value) => ({
type: EDIT_SIZE,
sizeType,
index,
value,
});
export const DELETE_SIZE = 'DELETE_SIZE';
export const deleteSize = (sizeType, index) => ({
type: DELETE_SIZE,
sizeType,
index,
});
export const ADD_OPERATION = 'ADD_OPERATION';
export const addOperation = () => ({
type: ADD_OPERATION,
});
export const DELETE_OPERATION = 'DELETE_OPERATION';
export const deleteOperation = index => ({
type: DELETE_OPERATION,
index,
});
export const CHANGE_OPERATION = 'CHANGE_OPERATION';
export const changeOperation = (index, value) => ({
type: CHANGE_OPERATION,
index,
value,
});
export const ADD_PARAMETER_NAS = 'ADD_PARAMETER_NAS';
export const addParameter = opIndex => ({
type: ADD_PARAMETER_NAS,
opIndex,
});
export const CHANGE_PARAMETER_NAS = 'CHANGE_PARAMETER_NAS';
export const changeParameter = (opIndex, paramIndex, field, value) => ({
type: CHANGE_PARAMETER_NAS,
opIndex,
paramIndex,
field,
value,
});
export const DELETE_PARAMETER_NAS = 'DELETE_PARAMETER_NAS';
export const deleteParameter = (opIndex, paramIndex) => ({
type: DELETE_PARAMETER_NAS,
opIndex,
paramIndex,
});
export const ADD_LIST_PARAMETER_NAS = 'ADD_LIST_PARAMETER_NAS';
export const addListParameter = (opIndex, paramIndex) => ({
type: ADD_LIST_PARAMETER_NAS,
opIndex,
paramIndex,
});
export const EDIT_LIST_PARAMETER_NAS = 'EDIT_LIST_PARAMETER_NAS';
export const editListParameter = (opIndex, paramIndex, listIndex, value) => ({
type: EDIT_LIST_PARAMETER_NAS,
opIndex,
paramIndex,
listIndex,
value,
});
export const DELETE_LIST_PARAMETER_NAS = 'DELETE_LIST_PARAMETER_NAS';
export const deleteListParameter = (opIndex, paramIndex, listIndex) => ({
type: DELETE_LIST_PARAMETER_NAS,
opIndex,
paramIndex,
listIndex,
});
export const SUBMIT_NAS_JOB_REQUEST = 'SUBMIT_NAS_JOB_REQUEST';
export const SUBMIT_NAS_JOB_SUCCESS = 'SUBMIT_NAS_JOB_SUCCESS';
export const SUBMIT_NAS_JOB_FAILURE = 'SUBMIT_NAS_JOB_FAILURE';
export const submitNASJob = data => ({
type: SUBMIT_NAS_JOB_REQUEST,
data,
});
export const CLOSE_SNACKBAR = 'CLOSE_SNACKBAR';
export const closeSnackbar = () => ({
type: CLOSE_SNACKBAR,
});
export const CHANGE_MC_KIND_NAS = 'CHANGE_MC_KIND_NAS';
export const changeMCKindNAS = kind => ({
type: CHANGE_MC_KIND_NAS,
kind,
});
export const CHANGE_MC_FILE_SYSTEM_NAS = 'CHANGE_MC_FILE_SYSTEM_NAS';
export const changeMCFileSystemNAS = (kind, path) => ({
type: CHANGE_MC_FILE_SYSTEM_NAS,
kind,
path,
});
export const CHANGE_MC_HTTP_GET_NAS = 'CHANGE_MC_HTTP_GET_NAS';
export const changeMCHttpGetNAS = (port, path, scheme, host) => ({
type: CHANGE_MC_HTTP_GET_NAS,
port,
path,
scheme,
host,
});
export const ADD_MC_HTTP_GET_HEADER_NAS = 'ADD_MC_HTTP_GET_HEADER_NAS';
export const addMCHttpGetHeaderNAS = () => ({
type: ADD_MC_HTTP_GET_HEADER_NAS,
});
export const CHANGE_MC_HTTP_GET_HEADER_NAS = 'CHANGE_MC_HTTP_GET_HEADER_NAS';
export const changeMCHttpGetHeaderNAS = (fieldName, value, index) => ({
type: CHANGE_MC_HTTP_GET_HEADER_NAS,
fieldName,
value,
index,
});
export const DELETE_MC_HTTP_GET_HEADER_NAS = 'DELETE_MC_HTTP_GET_HEADER_NAS';
export const deleteMCHttpGetHeaderNAS = index => ({
type: DELETE_MC_HTTP_GET_HEADER_NAS,
index,
});
export const ADD_MC_METRICS_FORMAT_NAS = 'ADD_MC_METRICS_FORMAT_NAS';
export const addMCMetricsFormatNAS = () => ({
type: ADD_MC_METRICS_FORMAT_NAS,
});
export const CHANGE_MC_METRIC_FORMAT_NAS = 'CHANGE_MC_METRIC_FORMAT_NAS';
export const changeMCMetricsFormatNAS = (format, index) => ({
type: CHANGE_MC_METRIC_FORMAT_NAS,
format,
index,
});
export const DELETE_MC_METRIC_FORMAT_NAS = 'DELETE_MC_METRIC_FORMAT_NAS';
export const deleteMCMetricsFormatNAS = index => ({
type: DELETE_MC_METRIC_FORMAT_NAS,
index,
});
export const CHANGE_MC_CUSTOM_CONTAINER_NAS = 'CHANGE_MC_CUSTOM_CONTAINER_NAS';
export const changeMCCustomContainerNAS = yamlContainer => ({
type: CHANGE_MC_CUSTOM_CONTAINER_NAS,
yamlContainer,
});
@@ -1,9 +0,0 @@
export const FETCH_NAS_JOB_INFO_REQUEST = 'FETCH_NAS_JOB_INFO_REQUEST';
export const FETCH_NAS_JOB_INFO_SUCCESS = 'FETCH_NAS_JOB_INFO_SUCCESS';
export const FETCH_NAS_JOB_INFO_FAILURE = 'FETCH_NAS_JOB_INFO_FAILURE';
export const fetchNASJobInfo = (experimentName, namespace) => ({
type: FETCH_NAS_JOB_INFO_REQUEST,
experimentName,
namespace,
});
@@ -1,104 +0,0 @@
export const CLOSE_DIALOG = 'CLOSE_DIALOG';
export const closeDialog = () => ({
type: CLOSE_DIALOG,
});
export const OPEN_DIALOG = 'OPEN_DIALOG';
export const openDialog = (
dialogType,
configMapNamespace = '',
configMapName = '',
configMapPath = '',
templateYaml = '',
) => ({
type: OPEN_DIALOG,
dialogType,
configMapNamespace,
configMapName,
configMapPath,
templateYaml,
});
export const FETCH_TRIAL_TEMPLATES_REQUEST = 'FETCH_TRIAL_TEMPLATES_REQUEST';
export const FETCH_TRIAL_TEMPLATES_SUCCESS = 'FETCH_TRIAL_TEMPLATES_SUCCESS';
export const FETCH_TRIAL_TEMPLATES_FAILURE = 'FETCH_TRIAL_TEMPLATES_FAILURE';
export const fetchTrialTemplates = () => ({
type: FETCH_TRIAL_TEMPLATES_REQUEST,
});
export const ADD_TEMPLATE_REQUEST = 'ADD_TEMPLATE_REQUEST';
export const ADD_TEMPLATE_SUCCESS = 'ADD_TEMPLATE_SUCCESS';
export const ADD_TEMPLATE_FAILURE = 'ADD_TEMPLATE_FAILURE';
export const addTemplate = (
updatedConfigMapNamespace,
updatedConfigMapName,
updatedConfigMapPath,
updatedTemplateYaml,
) => ({
type: ADD_TEMPLATE_REQUEST,
updatedConfigMapNamespace,
updatedConfigMapName,
updatedConfigMapPath,
updatedTemplateYaml,
});
export const EDIT_TEMPLATE_REQUEST = 'EDIT_TEMPLATE_REQUEST';
export const EDIT_TEMPLATE_SUCCESS = 'EDIT_TEMPLATE_SUCCESS';
export const EDIT_TEMPLATE_FAILURE = 'EDIT_TEMPLATE_FAILURE';
export const editTemplate = (
updatedConfigMapNamespace,
updatedConfigMapName,
configMapPath,
updatedConfigMapPath,
updatedTemplateYaml,
) => ({
type: EDIT_TEMPLATE_REQUEST,
updatedConfigMapNamespace,
updatedConfigMapName,
configMapPath,
updatedConfigMapPath,
updatedTemplateYaml,
});
export const DELETE_TEMPLATE_REQUEST = 'DELETE_TEMPLATE_REQUEST';
export const DELETE_TEMPLATE_SUCCESS = 'DELETE_TEMPLATE_SUCCESS';
export const DELETE_TEMPLATE_FAILURE = 'DELETE_TEMPLATE_FAILURE';
export const deleteTemplate = (
updatedConfigMapNamespace,
updatedConfigMapName,
updatedConfigMapPath,
) => ({
type: DELETE_TEMPLATE_REQUEST,
updatedConfigMapNamespace,
updatedConfigMapName,
updatedConfigMapPath,
});
export const CHANGE_TEMPLATE = 'CHANGE_TEMPLATE';
export const changeTemplate = (
updatedConfigMapNamespace,
updatedConfigMapName,
updatedConfigMapPath,
updatedTemplateYaml,
) => ({
type: CHANGE_TEMPLATE,
updatedConfigMapNamespace,
updatedConfigMapName,
updatedConfigMapPath,
updatedTemplateYaml,
});
export const FILTER_TEMPLATES = 'FILTER_TEMPLATES';
export const filterTemplates = (filteredConfigMapNamespace, filteredConfigMapName) => ({
type: FILTER_TEMPLATES,
filteredConfigMapNamespace,
filteredConfigMapName,
});

Some files were not shown because too many files have changed in this diff Show More