diff --git a/.travis.yml b/.travis.yml index 782e32204a1..3ded96f080e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,6 @@ jobs: language: node_js node_js: "10.13.0" install: - - npm install --global prettier + - npm install --global prettier@1.19.1 script: - make prettier-check diff --git a/cmd/ui/v1alpha3/main.go b/cmd/ui/v1alpha3/main.go index 7479b5b7707..c8798184777 100644 --- a/cmd/ui/v1alpha3/main.go +++ b/cmd/ui/v1alpha3/main.go @@ -42,8 +42,9 @@ func main() { http.HandleFunc("/katib/fetch_nas_job_info/", kuh.FetchNASJobInfo) http.HandleFunc("/katib/fetch_trial_templates/", kuh.FetchTrialTemplates) - http.HandleFunc("/katib/update_template/", kuh.AddEditDeleteTemplate) - + 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) log.Printf("Serving at %s:%s", *host, *port) diff --git a/manifests/v1alpha3/katib-controller/trialTemplateConfigmapLabeled.yaml b/manifests/v1alpha3/katib-controller/trialTemplateConfigmapLabeled.yaml new file mode 100644 index 00000000000..841ffd137ca --- /dev/null +++ b/manifests/v1alpha3/katib-controller/trialTemplateConfigmapLabeled.yaml @@ -0,0 +1,95 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: trial-template-labeled + namespace: kubeflow + labels: + app: katib-trial-templates +data: + defaultTrialTemplate.yaml: |- + apiVersion: batch/v1 + kind: Job + metadata: + name: {{.Trial}} + namespace: {{.NameSpace}} + spec: + template: + spec: + containers: + - name: {{.Trial}} + image: docker.io/kubeflowkatib/mxnet-mnist + command: + - "python3" + - "/opt/mxnet-mnist/mnist.py" + - "--batch-size=64" + {{- with .HyperParameters}} + {{- range .}} + - "{{.Name}}={{.Value}}" + {{- end}} + {{- end}} + restartPolicy: Never + nasRLCPUTemplate: |- + apiVersion: batch/v1 + kind: Job + metadata: + name: {{.Trial}} + namespace: {{.NameSpace}} + spec: + template: + spec: + containers: + - name: {{.Trial}} + image: docker.io/kubeflowkatib/nasrl-cifar10-cpu + command: + - "python3.5" + - "-u" + - "RunTrial.py" + {{- with .HyperParameters}} + {{- range .}} + - "--{{.Name}}=\"{{.Value}}\"" + {{- end}} + {{- end}} + - "--num_epochs=1" + restartPolicy: Never + pytorchJobTemplate: |- + apiVersion: "kubeflow.org/v1" + kind: PyTorchJob + metadata: + name: {{.Trial}} + namespace: {{.NameSpace}} + spec: + pytorchReplicaSpecs: + Master: + replicas: 1 + restartPolicy: OnFailure + template: + spec: + containers: + - name: pytorch + image: gcr.io/kubeflow-ci/pytorch-dist-mnist-test:v1.0 + imagePullPolicy: Always + command: + - "python" + - "/var/mnist.py" + {{- with .HyperParameters}} + {{- range .}} + - "{{.Name}}={{.Value}}" + {{- end}} + {{- end}} + Worker: + replicas: 2 + restartPolicy: OnFailure + template: + spec: + containers: + - name: pytorch + image: gcr.io/kubeflow-ci/pytorch-dist-mnist-test:v1.0 + imagePullPolicy: Always + command: + - "python" + - "/var/mnist.py" + {{- with .HyperParameters}} + {{- range .}} + - "{{.Name}}={{.Value}}" + {{- end}} + {{- end}} diff --git a/pkg/controller.v1alpha3/consts/const.go b/pkg/controller.v1alpha3/consts/const.go index ff07bc7cc59..998041a54be 100644 --- a/pkg/controller.v1alpha3/consts/const.go +++ b/pkg/controller.v1alpha3/consts/const.go @@ -114,6 +114,9 @@ const ( // AnnotationIstioSidecarInjectValue is the value of Istio Sidecar annotation AnnotationIstioSidecarInjectValue = "false" + + LabelTrialTemplateConfigMapName = "app" + LabelTrialTemplateConfigMapValue = "katib-trial-templates" ) var ( diff --git a/pkg/mock/v1alpha3/util/katibclient/katibclient.go b/pkg/mock/v1alpha3/util/katibclient/katibclient.go index c4868fce3db..ac6ebfc4f44 100644 --- a/pkg/mock/v1alpha3/util/katibclient/katibclient.go +++ b/pkg/mock/v1alpha3/util/katibclient/katibclient.go @@ -224,14 +224,14 @@ func (mr *MockClientMockRecorder) GetTrialList(arg0 interface{}, arg1 ...interfa } // GetTrialTemplates mocks base method -func (m *MockClient) GetTrialTemplates(arg0 ...string) (map[string]string, error) { +func (m *MockClient) GetTrialTemplates(arg0 ...string) (*v1.ConfigMapList, error) { m.ctrl.T.Helper() varargs := []interface{}{} for _, a := range arg0 { varargs = append(varargs, a) } ret := m.ctrl.Call(m, "GetTrialTemplates", varargs...) - ret0, _ := ret[0].(map[string]string) + ret0, _ := ret[0].(*v1.ConfigMapList) ret1, _ := ret[1].(error) return ret0, ret1 } @@ -254,40 +254,35 @@ func (mr *MockClientMockRecorder) InjectClient(arg0 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InjectClient", reflect.TypeOf((*MockClient)(nil).InjectClient), arg0) } -// UpdateExperiment mocks base method -func (m *MockClient) UpdateExperiment(arg0 *v1alpha3.Experiment, arg1 ...string) error { +// UpdateConfigMap mocks base method +func (m *MockClient) UpdateConfigMap(arg0 *v1.ConfigMap) error { m.ctrl.T.Helper() - varargs := []interface{}{arg0} - for _, a := range arg1 { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "UpdateExperiment", varargs...) + ret := m.ctrl.Call(m, "UpdateConfigMap", arg0) ret0, _ := ret[0].(error) return ret0 } -// UpdateExperiment indicates an expected call of UpdateExperiment -func (mr *MockClientMockRecorder) UpdateExperiment(arg0 interface{}, arg1 ...interface{}) *gomock.Call { +// UpdateConfigMap indicates an expected call of UpdateConfigMap +func (mr *MockClientMockRecorder) UpdateConfigMap(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{arg0}, arg1...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateExperiment", reflect.TypeOf((*MockClient)(nil).UpdateExperiment), varargs...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateConfigMap", reflect.TypeOf((*MockClient)(nil).UpdateConfigMap), arg0) } -// UpdateTrialTemplates mocks base method -func (m *MockClient) UpdateTrialTemplates(arg0 map[string]string, arg1 ...string) error { +// UpdateExperiment mocks base method +func (m *MockClient) UpdateExperiment(arg0 *v1alpha3.Experiment, arg1 ...string) error { m.ctrl.T.Helper() varargs := []interface{}{arg0} for _, a := range arg1 { varargs = append(varargs, a) } - ret := m.ctrl.Call(m, "UpdateTrialTemplates", varargs...) + ret := m.ctrl.Call(m, "UpdateExperiment", varargs...) ret0, _ := ret[0].(error) return ret0 } -// UpdateTrialTemplates indicates an expected call of UpdateTrialTemplates -func (mr *MockClientMockRecorder) UpdateTrialTemplates(arg0 interface{}, arg1 ...interface{}) *gomock.Call { +// UpdateExperiment indicates an expected call of UpdateExperiment +func (mr *MockClientMockRecorder) UpdateExperiment(arg0 interface{}, arg1 ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() varargs := append([]interface{}{arg0}, arg1...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateTrialTemplates", reflect.TypeOf((*MockClient)(nil).UpdateTrialTemplates), varargs...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateExperiment", reflect.TypeOf((*MockClient)(nil).UpdateExperiment), varargs...) } diff --git a/pkg/ui/v1alpha3/backend.go b/pkg/ui/v1alpha3/backend.go index a87361196a4..3d4ef4d9165 100644 --- a/pkg/ui/v1alpha3/backend.go +++ b/pkg/ui/v1alpha3/backend.go @@ -13,7 +13,6 @@ import ( experimentv1alpha3 "github.com/kubeflow/katib/pkg/apis/controller/experiments/v1alpha3" api_pb_v1alpha3 "github.com/kubeflow/katib/pkg/apis/manager/v1alpha3" common_v1alpha3 "github.com/kubeflow/katib/pkg/common/v1alpha3" - "github.com/kubeflow/katib/pkg/controller.v1alpha3/consts" "github.com/kubeflow/katib/pkg/util/v1alpha3/katibclient" ) @@ -116,21 +115,20 @@ func (k *KatibUIHandler) DeleteExperiment(w http.ResponseWriter, r *http.Request } } -// FetchTrialTemplates gets the trial templates for the given namespace. +// FetchTrialTemplates gets all trial templates in all namespaces func (k *KatibUIHandler) FetchTrialTemplates(w http.ResponseWriter, r *http.Request) { - //enableCors(&w) - namespace := r.URL.Query()["namespace"][0] - if namespace == "" { - namespace = consts.DefaultKatibNamespace - } - trialTemplates, err := k.katibClient.GetTrialTemplates(namespace) + + trialTemplatesViewList, err := k.getTrialTemplatesViewList() if err != nil { - log.Printf("GetTrialTemplate failed: %v", err) + log.Printf("getTrialTemplatesViewList failed: %v", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } - response, err := json.Marshal(getTemplatesView(trialTemplates)) + 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) @@ -139,29 +137,91 @@ func (k *KatibUIHandler) FetchTrialTemplates(w http.ResponseWriter, r *http.Requ w.Write(response) } -func (k *KatibUIHandler) AddEditDeleteTemplate(w http.ResponseWriter, r *http.Request) { - //enableCors(&w) - //TODO: need to delete? - if r.Method == "OPTIONS" { +//AddTemplate adds template to ConfigMap +//TODO: Add functionality to create new ConfigMap +func (k *KatibUIHandler) AddTemplate(w http.ResponseWriter, r *http.Request) { + var data map[string]interface{} + json.NewDecoder(r.Body).Decode(&data) + + edittedNamespace := data["edittedNamespace"].(string) + edittedConfigMapName := data["edittedConfigMapName"].(string) + edittedName := data["edittedName"].(string) + edittedYaml := data["edittedYaml"].(string) + + newTemplates, err := k.updateTrialTemplates(edittedNamespace, edittedConfigMapName, edittedName, edittedYaml, "", ActionTypeAdd) + if err != nil { + log.Printf("updateTrialTemplates failed: %v", err) + http.Error(w, err.Error(), http.StatusInternalServerError) return } - var data map[string]interface{} - var err error - var templateResponse TemplateResponse + 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 + } + w.Write(response) + +} + +// EditTemplate edits template in ConfigMap +func (k *KatibUIHandler) EditTemplate(w http.ResponseWriter, r *http.Request) { + + var data map[string]interface{} json.NewDecoder(r.Body).Decode(&data) - if data["action"].(string) == "delete" { - templateResponse, err = k.updateTemplates(data, true) - } else { - templateResponse, err = k.updateTemplates(data, false) + + edittedNamespace := data["edittedNamespace"].(string) + edittedConfigMapName := data["edittedConfigMapName"].(string) + edittedName := data["edittedName"].(string) + edittedYaml := data["edittedYaml"].(string) + currentName := data["currentName"].(string) + + newTemplates, err := k.updateTrialTemplates(edittedNamespace, edittedConfigMapName, edittedName, edittedYaml, currentName, ActionTypeEdit) + 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("updateTemplates failed: %v", err) + log.Printf("Marhal failed: %v", err) http.Error(w, err.Error(), http.StatusInternalServerError) return } + w.Write(response) +} + +// DeleteTemplate delete template in ConfigMap +// TODO: Add functionality to delete configMap if there is no templates +func (k *KatibUIHandler) DeleteTemplate(w http.ResponseWriter, r *http.Request) { + + var data map[string]interface{} + json.NewDecoder(r.Body).Decode(&data) + + edittedNamespace := data["edittedNamespace"].(string) + edittedConfigMapName := data["edittedConfigMapName"].(string) + edittedName := data["edittedName"].(string) + + newTemplates, err := k.updateTrialTemplates(edittedNamespace, edittedConfigMapName, edittedName, "", "", ActionTypeDelete) + 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(templateResponse) + response, err := json.Marshal(TrialTemplatesResponse) if err != nil { log.Printf("Marhal failed: %v", err) http.Error(w, err.Error(), http.StatusInternalServerError) diff --git a/pkg/ui/v1alpha3/frontend/src/actions/generalActions.js b/pkg/ui/v1alpha3/frontend/src/actions/generalActions.js index 5d3422f7813..04b8c989213 100644 --- a/pkg/ui/v1alpha3/frontend/src/actions/generalActions.js +++ b/pkg/ui/v1alpha3/frontend/src/actions/generalActions.js @@ -75,6 +75,21 @@ export const closeDialogExperiment = () => ({ type: CLOSE_DIALOG_EXPERIMENT, }); +export const FILTER_TEMPLATES_EXPERIMENT = 'FILTER_TEMPLATES_EXPERIMENT'; + +export const filterTemplatesExperiment = (trialNamespace, trialConfigMapName) => ({ + type: FILTER_TEMPLATES_EXPERIMENT, + trialNamespace, + trialConfigMapName, +}); + +export const CHANGE_TEMPLATE_NAME = 'CHANGE_TEMPLATE_NAME'; + +export const changeTemplateName = templateName => ({ + type: CHANGE_TEMPLATE_NAME, + templateName, +}); + export const VALIDATION_ERROR = 'VALIDATION_ERROR'; export const validationError = message => ({ diff --git a/pkg/ui/v1alpha3/frontend/src/actions/hpCreateActions.js b/pkg/ui/v1alpha3/frontend/src/actions/hpCreateActions.js index 49446b2b413..bd1b1260323 100644 --- a/pkg/ui/v1alpha3/frontend/src/actions/hpCreateActions.js +++ b/pkg/ui/v1alpha3/frontend/src/actions/hpCreateActions.js @@ -124,20 +124,6 @@ export const deleteListParameter = (paramIndex, index) => ({ index, }); -export const CHANGE_TRIAL_HP = 'CHANGE_TRIAL_HP'; - -export const changeTrial = trial => ({ - type: CHANGE_TRIAL_HP, - trial, -}); - -export const CHANGE_TRIAL_NAMESPACE_HP = 'CHANGE_TRIAL_NAMESPACE_HP'; - -export const changeTrialNamespace = namespace => ({ - type: CHANGE_TRIAL_NAMESPACE_HP, - namespace, -}); - 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'; diff --git a/pkg/ui/v1alpha3/frontend/src/actions/nasCreateActions.js b/pkg/ui/v1alpha3/frontend/src/actions/nasCreateActions.js index 08dc6d9e45e..3ac2429645e 100644 --- a/pkg/ui/v1alpha3/frontend/src/actions/nasCreateActions.js +++ b/pkg/ui/v1alpha3/frontend/src/actions/nasCreateActions.js @@ -183,20 +183,6 @@ export const deleteListParameter = (opIndex, paramIndex, listIndex) => ({ listIndex, }); -export const CHANGE_TRIAL_NAS = 'CHANGE_TRIAL_NAS'; - -export const changeTrial = trial => ({ - type: CHANGE_TRIAL_NAS, - trial, -}); - -export const CHANGE_TRIAL_NAMESPACE_NAS = 'CHANGE_TRIAL_NAMESPACE_NAS'; - -export const changeTrialNamespace = namespace => ({ - type: CHANGE_TRIAL_NAMESPACE_NAS, - namespace, -}); - 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'; diff --git a/pkg/ui/v1alpha3/frontend/src/actions/templateActions.js b/pkg/ui/v1alpha3/frontend/src/actions/templateActions.js index bd45cd53c9b..bbbd8a99b9e 100644 --- a/pkg/ui/v1alpha3/frontend/src/actions/templateActions.js +++ b/pkg/ui/v1alpha3/frontend/src/actions/templateActions.js @@ -1,67 +1,97 @@ export const CLOSE_DIALOG = 'CLOSE_DIALOG'; -export const closeDialog = dialogType => ({ +export const closeDialog = () => ({ type: CLOSE_DIALOG, - dialogType, }); export const OPEN_DIALOG = 'OPEN_DIALOG'; -export const openDialog = (dialogType, index = -1, templateType = -1) => ({ +export const openDialog = ( + dialogType, + namespace = '', + configMapName = '', + templateName = '', + templateYaml = '', +) => ({ type: OPEN_DIALOG, dialogType, - index, - templateType, -}); - -export const CHANGE_TEMPLATE = 'CHANGE_TEMPLATE'; - -export const changeTemplate = (field, value) => ({ - type: CHANGE_TEMPLATE, - field, - value, + namespace, + configMapName, + templateName, + 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 = namespace => ({ +export const fetchTrialTemplates = () => ({ type: FETCH_TRIAL_TEMPLATES_REQUEST, - namespace, }); 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 = (name, yaml, kind, action) => ({ +export const addTemplate = (edittedNamespace, edittedConfigMapName, edittedName, edittedYaml) => ({ type: ADD_TEMPLATE_REQUEST, - name, - yaml, - kind, - action, + edittedNamespace, + edittedConfigMapName, + edittedName, + edittedYaml, }); 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 = (name, yaml, kind, action) => ({ +export const editTemplate = ( + edittedNamespace, + edittedConfigMapName, + currentName, + edittedName, + edittedYaml, +) => ({ type: EDIT_TEMPLATE_REQUEST, - name, - yaml, - kind, - action, + edittedNamespace, + edittedConfigMapName, + currentName, + edittedName, + edittedYaml, }); 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 = (name, kind, action) => ({ +export const deleteTemplate = (edittedNamespace, edittedConfigMapName, edittedName) => ({ type: DELETE_TEMPLATE_REQUEST, - name, - kind, - action, + edittedNamespace, + edittedConfigMapName, + edittedName, +}); + +export const CHANGE_TEMPLATE = 'CHANGE_TEMPLATE'; + +export const changeTemplate = ( + edittedTemplateNamespace, + edittedTemplateConfigMapName, + edittedTemplateName, + edittedTemplateYaml, + edittedTemplateConfigMapSelectList, +) => ({ + type: CHANGE_TEMPLATE, + edittedTemplateNamespace, + edittedTemplateConfigMapName, + edittedTemplateName, + edittedTemplateYaml, + edittedTemplateConfigMapSelectList, +}); + +export const FILTER_TEMPLATES = 'FILTER_TEMPLATES'; + +export const filterTemplates = (filteredNamespace, filteredConfigMapName) => ({ + type: FILTER_TEMPLATES, + filteredNamespace, + filteredConfigMapName, }); diff --git a/pkg/ui/v1alpha3/frontend/src/components/HP/Create/HPParameters.jsx b/pkg/ui/v1alpha3/frontend/src/components/HP/Create/HPParameters.jsx index 882d14dc5f7..75401ed04dd 100644 --- a/pkg/ui/v1alpha3/frontend/src/components/HP/Create/HPParameters.jsx +++ b/pkg/ui/v1alpha3/frontend/src/components/HP/Create/HPParameters.jsx @@ -14,13 +14,15 @@ import Objective from './Params/Objective'; import TrialSpecParam from './Params/Trial'; import Parameters from './Params/Parameters'; import Algorithm from './Params/Algorithm'; -import MetricsCollectorSpec from '../../Common/Create/Params/MetricsCollector'; import { submitHPJob } from '../../../actions/hpCreateActions'; +import MetricsCollectorSpec from '../../Common/Create/Params/MetricsCollector'; + import { validationError } from '../../../actions/generalActions'; import * as constants from '../../../constants/constants'; const module = 'hpCreate'; +const generalModule = 'general'; const styles = theme => ({ root: { @@ -193,13 +195,12 @@ const HPParameters = props => { data.spec.metricsCollectorSpec = newMCSpec; - //TODO: Add support not only for default ConfigMap for Trial-Templates data.spec.trialTemplate = { goTemplate: { templateSpec: { - configMapName: 'trial-template', - configMapNamespace: props.trialNamespace, - templatePath: props.trial, + configMapName: props.templateConfigMapName, + configMapNamespace: props.templateNamespace, + templatePath: props.templateName, }, }, }; @@ -252,6 +253,9 @@ const mapStateToProps = state => ({ algorithmName: state[module].algorithmName, algorithmSettings: state[module].algorithmSettings, parameters: state[module].parameters, + templateNamespace: state[generalModule].templateNamespace, + templateConfigMapName: state[generalModule].templateConfigMapName, + templateName: state[generalModule].templateName, trial: state[module].trial, trialNamespace: state[module].trialNamespace, mcSpec: state[module].mcSpec, diff --git a/pkg/ui/v1alpha3/frontend/src/components/HP/Create/Params/Trial.jsx b/pkg/ui/v1alpha3/frontend/src/components/HP/Create/Params/Trial.jsx index f43e0e5cdcf..4f038ba4ff0 100644 --- a/pkg/ui/v1alpha3/frontend/src/components/HP/Create/Params/Trial.jsx +++ b/pkg/ui/v1alpha3/frontend/src/components/HP/Create/Params/Trial.jsx @@ -1,4 +1,6 @@ import React from 'react'; +import { connect } from 'react-redux'; + import withStyles from '@material-ui/styles/withStyles'; import Grid from '@material-ui/core/Grid'; import Tooltip from '@material-ui/core/Tooltip'; @@ -9,14 +11,12 @@ import InputLabel from '@material-ui/core/InputLabel'; import MenuItem from '@material-ui/core/MenuItem'; import FormControl from '@material-ui/core/FormControl'; import Select from '@material-ui/core/Select'; -import TextField from '@material-ui/core/TextField'; -import { connect } from 'react-redux'; -import { changeTrial, changeTrialNamespace } from '../../../../actions/hpCreateActions'; +import { filterTemplatesExperiment, changeTemplateName } from '../../../../actions/generalActions'; import { fetchTrialTemplates } from '../../../../actions/templateActions'; const module = 'hpCreate'; -const templateModule = 'template'; +const generalModule = 'general'; const styles = theme => ({ help: { @@ -31,33 +31,37 @@ const styles = theme => ({ padding: 2, marginBottom: 10, }, - formControl: { + trialForm: { margin: 4, width: '100%', }, - selectEmpty: { - marginTop: 10, + selectForm: { + margin: 4, + width: '20%', + }, + selectNS: { + marginRight: 10, }, }); class TrialSpecParam extends React.Component { componentDidMount() { - this.props.fetchTrialTemplates(this.props.trialNamespace); - this.props.changeTrialNamespace(this.props.trialNamespace); + this.props.fetchTrialTemplates(); } onTrialNamespaceChange = event => { - this.props.fetchTrialTemplates(event.target.value); - this.props.changeTrialNamespace(event.target.value); + this.props.filterTemplatesExperiment(event.target.value, ''); }; - onTrialChange = event => { - this.props.changeTrial(event.target.value); + onTrialConfigMapChange = event => { + this.props.filterTemplatesExperiment(this.props.templateNamespace, event.target.value); }; - render() { - const names = this.props.templates.map((template, i) => template.name); + onTrialTemplateChange = event => { + this.props.changeTemplateName(event.target.value); + }; + render() { const { classes } = this.props; return (
@@ -65,18 +69,46 @@ class TrialSpecParam extends React.Component { - + - {'Namespace'} + {'Namespace and ConfigMapName'} - + + Namespace + + + + ConfigMap + +
@@ -84,25 +116,24 @@ class TrialSpecParam extends React.Component { - + - {'TrialSpec'} + {'Trial Template Name'} - - Trial Spec + + Trial Template } + > + {this.props.trialTemplatesList.map((trialTemplate, i) => { + return ( + + {trialTemplate.Namespace} + + ); + })} + + + + ConfigMap + + @@ -84,25 +116,24 @@ class TrialSpecParam extends React.Component { - + - {'TrialSpec'} + {'Trial Template Name'} - - Trial Spec + + Trial Template } + > + {this.props.trialTemplatesList.map((trialTemplate, i) => { + return ( + + {trialTemplate.Namespace} + + ); + })} + + + + ConfigMap + + +
- {/* */} - @@ -57,8 +45,9 @@ const DeleteDialog = props => { const mapStateToProps = state => { return { deleteOpen: state[module].deleteOpen, - currentTemplateIndex: state[module].currentTemplateIndex, - currentTemplateName: state[module].currentTemplateName, + edittedTemplateNamespace: state[module].edittedTemplateNamespace, + edittedTemplateConfigMapName: state[module].edittedTemplateConfigMapName, + edittedTemplateName: state[module].edittedTemplateName, }; }; diff --git a/pkg/ui/v1alpha3/frontend/src/components/Templates/Common/EditDialog.jsx b/pkg/ui/v1alpha3/frontend/src/components/Templates/Common/EditDialog.jsx index 4338b366915..3cc4c5f6d71 100644 --- a/pkg/ui/v1alpha3/frontend/src/components/Templates/Common/EditDialog.jsx +++ b/pkg/ui/v1alpha3/frontend/src/components/Templates/Common/EditDialog.jsx @@ -1,101 +1,121 @@ import React from 'react'; +import { connect } from 'react-redux'; import withStyles from '@material-ui/styles/withStyles'; +import AceEditor from 'react-ace'; +import 'ace-builds/src-noconflict/theme-sqlserver'; +import 'ace-builds/src-noconflict/mode-yaml'; + import Button from '@material-ui/core/Button'; import Dialog from '@material-ui/core/Dialog'; import DialogActions from '@material-ui/core/DialogActions'; import DialogContent from '@material-ui/core/DialogContent'; import TextField from '@material-ui/core/TextField'; import DialogTitle from '@material-ui/core/DialogTitle'; -import Slide from '@material-ui/core/Slide'; +import Typography from '@material-ui/core/Typography'; -import { connect } from 'react-redux'; -import { closeDialog, changeTemplate, editTemplate } from '../../../actions/templateActions'; +import { closeDialog, editTemplate, changeTemplate } from '../../../actions/templateActions'; const module = 'template'; const styles = theme => ({ + header: { + textAlign: 'center', + width: 650, + }, + headerTypography: { + textAlign: 'center', + marginTop: 5, + fontSize: 19, + }, textField: { - margin: 10, - width: 400, + marginBottom: 10, + width: '100%', }, }); -function Transition(props) { - return ; -} - -// FIX DIALOG TEXTFIELD SIZE - class EditDialog extends React.Component { - state = { - name: '', - yaml: '', + onNameChange = event => { + this.props.changeTemplate( + this.props.edittedTemplateNamespace, + this.props.edittedTemplateConfigMapName, + event.target.value, + this.props.edittedTemplateYaml, + this.props.edittedTemplateConfigMapSelectList, + ); }; - componentDidMount() { - this.setState({ - name: this.props.edittedTemplate.name, - yaml: this.props.edittedTemplate.yaml, - }); - } - - componentWillReceiveProps(newProps) { - this.setState({ - name: newProps.edittedTemplate.name, - yaml: newProps.edittedTemplate.yaml, - }); - } + onYamlChange = newTemplateYaml => { + this.props.changeTemplate( + this.props.edittedTemplateNamespace, + this.props.edittedTemplateConfigMapName, + this.props.edittedTemplateName, + newTemplateYaml, + this.props.edittedTemplateConfigMapSelectList, + ); + }; submitEditTemplate = () => { - this.props.editTemplate(this.state.name, this.state.yaml, this.props.type, 'edit'); + this.props.editTemplate( + this.props.edittedTemplateNamespace, + this.props.edittedTemplateConfigMapName, + this.props.currentTemplateName, + this.props.edittedTemplateName, + this.props.edittedTemplateYaml, + ); }; render() { const { classes } = this.props; return ( -
- - {'Editing a template'} - - - this.setState({ - name: event.target.value, - }) - } - /> -
- - this.setState({ - yaml: event.target.value, - }) - } - /> -
- - - - -
-
+ + + {'Template Editor'} + + {'Namespace: ' + this.props.edittedTemplateNamespace} + + + + {'ConfigMap: ' + this.props.edittedTemplateConfigMapName} + + + + + +
+ +
+ + + + +
); } } @@ -103,11 +123,15 @@ class EditDialog extends React.Component { const mapStateToProps = state => { return { editOpen: state[module].editOpen, - currentTemplateIndex: state[module].currentTemplateIndex, - edittedTemplate: state[module].edittedTemplate, + edittedTemplateNamespace: state[module].edittedTemplateNamespace, + edittedTemplateConfigMapName: state[module].edittedTemplateConfigMapName, + currentTemplateName: state[module].currentTemplateName, + edittedTemplateName: state[module].edittedTemplateName, + edittedTemplateYaml: state[module].edittedTemplateYaml, + edittedTemplateConfigMapSelectList: state[module].edittedTemplateConfigMapSelectList, }; }; -export default connect(mapStateToProps, { closeDialog, changeTemplate, editTemplate })( +export default connect(mapStateToProps, { closeDialog, editTemplate, changeTemplate })( withStyles(styles)(EditDialog), ); diff --git a/pkg/ui/v1alpha3/frontend/src/components/Templates/Common/FilterPanel.jsx b/pkg/ui/v1alpha3/frontend/src/components/Templates/Common/FilterPanel.jsx new file mode 100644 index 00000000000..9a0ae7d4606 --- /dev/null +++ b/pkg/ui/v1alpha3/frontend/src/components/Templates/Common/FilterPanel.jsx @@ -0,0 +1,88 @@ +import React from 'react'; +import { connect } from 'react-redux'; +import { withStyles } from '@material-ui/core/styles'; + +import TextField from '@material-ui/core/TextField'; +import Select from '@material-ui/core/Select'; +import InputLabel from '@material-ui/core/InputLabel'; +import MenuItem from '@material-ui/core/MenuItem'; +import FormControl from '@material-ui/core/FormControl'; + +import { fetchNamespaces } from '../../../actions/generalActions'; +import { filterTemplates } from '../../../actions/templateActions'; + +const module = 'template'; +const generalModule = 'general'; + +const styles = theme => ({ + selectBox: { + marginLeft: theme.spacing.unit, + marginRight: theme.spacing.unit, + width: 200, + height: 56, + }, + textField: { + marginLeft: theme.spacing.unit, + marginRight: theme.spacing.unit, + }, +}); + +class FilterPanel extends React.Component { + componentDidMount() { + this.props.fetchNamespaces(); + this.props.filterTemplates(this.props.filteredNamespace, this.props.filteredConfigMapName); + } + + onNamespaceChange = event => { + this.props.filterTemplates(event.target.value, this.props.filteredConfigMapName); + }; + + onConfigMapNameChange = event => { + this.props.filterTemplates(this.props.filteredNamespace, event.target.value); + }; + + render() { + const { classes } = this.props; + + return ( +
+ + Namespace + + + +
+ ); + } +} + +const mapStateToProps = state => { + return { + namespaces: state[generalModule].namespaces, + filteredNamespace: state[module].filteredNamespace, + filteredConfigMapName: state[module].filteredConfigMapName, + }; +}; + +export default connect(mapStateToProps, { fetchNamespaces, filterTemplates })( + withStyles(styles)(FilterPanel), +); diff --git a/pkg/ui/v1alpha3/frontend/src/components/Templates/Common/TemplateList.jsx b/pkg/ui/v1alpha3/frontend/src/components/Templates/Common/TemplateList.jsx index 74b388df88c..97919ed40b2 100644 --- a/pkg/ui/v1alpha3/frontend/src/components/Templates/Common/TemplateList.jsx +++ b/pkg/ui/v1alpha3/frontend/src/components/Templates/Common/TemplateList.jsx @@ -1,51 +1,163 @@ import React from 'react'; +import { connect } from 'react-redux'; import { withStyles } from '@material-ui/core/styles'; + import ExpansionPanel from '@material-ui/core/ExpansionPanel'; import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary'; import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails'; import Typography from '@material-ui/core/Typography'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import Grid from '@material-ui/core/Grid'; +import Divider from '@material-ui/core/Divider'; +import Button from '@material-ui/core/Button'; +import LinearProgress from '@material-ui/core/LinearProgress'; import TemplatePanel from './TemplatePanel'; +import FilterPanel from './FilterPanel'; +import AddDialog from './AddDialog'; import EditDialog from './EditDialog'; import DeleteDialog from './DeleteDialog'; -import { connect } from 'react-redux'; +import { openDialog } from '../../../actions/templateActions'; const module = 'template'; const styles = theme => ({ - root: { - marginTop: 40, - width: '100%', + namespace: { + marginTop: 25, + marginRight: 15, + fontSize: theme.typography.pxToRem(26), + }, + configMap: { + margin: 15, + fontSize: theme.typography.pxToRem(23), }, - heading: { - fontSize: theme.typography.pxToRem(24), + templatesBlock: { + width: '96%', + margin: '0 auto', + }, + template: { + fontSize: theme.typography.pxToRem(20), fontWeight: theme.typography.fontWeightRegular, }, + divider: { + marginTop: 20, + }, + buttonAdd: { + textAlign: 'center', + }, + noTemplates: { + marginTop: 25, + marginRight: 15, + fontSize: theme.typography.pxToRem(50), + }, + loading: { + marginTop: 30, + }, }); +const dialogTypeAdd = 'add'; + +//TODO: Add functionality to create new ConfigMap with Trial Template class TemplateList extends React.Component { + openAddDialog = () => { + this.props.openDialog( + dialogTypeAdd, + this.props.trialTemplatesList[0].Namespace, + this.props.trialTemplatesList[0].ConfigMapsList[0].ConfigMapName, + ); + }; + render() { const { classes } = this.props; - const templates = - this.props.type === 'trial' ? this.props.trialTemplates : this.props.collectorTemplates; return ( -
- {templates.map((template, index) => { - return ( - - }> - {template.name} - - - - - - - - ); - })} +
+ {this.props.loading ? ( + + ) : ( +
+ {this.props.trialTemplatesList.length != 0 ? ( +
+ +
+ +
+ {this.props.filteredTrialTemplatesList.map((trialTemplate, nsIndex) => { + return ( +
+ + + Namespace: + + + + {trialTemplate.Namespace} + + + +
+
+
+ + {trialTemplate.ConfigMapsList.map((configMap, cmIndex) => { + return ( +
+ + + ConfigMap: + + + + {configMap.ConfigMapName} + + + + + {configMap.TemplatesList.map((template, templateIndex) => { + return ( +
+ + }> + + {template.Name} + + + + + + +
+ ); + })} + +
+ ); + })} +
+ ); + })} + + + + +
+ ) : ( +
+ No Katib Trial Templates +
+ )} +
+ )}
); } @@ -53,9 +165,10 @@ class TemplateList extends React.Component { const mapStateToProps = state => { return { - collectorTemplates: state[module].collectorTemplates, - trialTemplates: state[module].trialTemplates, + filteredTrialTemplatesList: state[module].filteredTrialTemplatesList, + trialTemplatesList: state[module].trialTemplatesList, + loading: state[module].loading, }; }; -export default connect(mapStateToProps, null)(withStyles(styles)(TemplateList)); +export default connect(mapStateToProps, { openDialog })(withStyles(styles)(TemplateList)); diff --git a/pkg/ui/v1alpha3/frontend/src/components/Templates/Common/TemplatePanel.jsx b/pkg/ui/v1alpha3/frontend/src/components/Templates/Common/TemplatePanel.jsx index d24b7be9337..7414c02c676 100644 --- a/pkg/ui/v1alpha3/frontend/src/components/Templates/Common/TemplatePanel.jsx +++ b/pkg/ui/v1alpha3/frontend/src/components/Templates/Common/TemplatePanel.jsx @@ -1,81 +1,84 @@ import React from 'react'; -import makeStyles from '@material-ui/styles/makeStyles'; - -import Button from '@material-ui/core/Button'; -import Grid from '@material-ui/core/Grid'; - import { connect } from 'react-redux'; +import AceEditor from 'react-ace'; +import 'ace-builds/src-noconflict/theme-sqlserver'; +import 'ace-builds/src-noconflict/mode-yaml'; + +import makeStyles from '@material-ui/styles/makeStyles'; +import Button from '@material-ui/core/Button'; import DeleteIcon from '@material-ui/icons/Delete'; import CreateIcon from '@material-ui/icons/Create'; -import TextField from '@material-ui/core/TextField'; import { openDialog } from '../../../actions/templateActions'; -// const module = "template"; - const useStyles = makeStyles({ root: { - flexGrow: 1, + width: '98%', + margin: '0 auto', }, - grid: { + + buttons: { marginTop: 30, - textAlign: 'right', + marginLeft: 20, }, icon: { margin: 4, }, - textField: { - width: '100%', - }, - input: { - color: 'black !important', - fontSize: 24, - }, }); +const dialogTypeEdit = 'edit'; +const dialogTypeDelete = 'delete'; const TemplatePanel = props => { const classes = useStyles(); - const openEditDialog = index => event => { - props.openDialog('edit', index, props.type); + const openEditDialog = (namespace, configMapName, templateName, templateYaml) => event => { + props.openDialog(dialogTypeEdit, namespace, configMapName, templateName, templateYaml); }; - const openDeleteDialog = index => event => { - props.openDialog('delete', index, props.type, 'delete'); + const openDeleteDialog = (namespace, configMapName, templateName) => event => { + props.openDialog(dialogTypeDelete, namespace, configMapName, templateName); }; return (
- + -
- - - - - - - - +
); }; diff --git a/pkg/ui/v1alpha3/frontend/src/components/Templates/Trial.jsx b/pkg/ui/v1alpha3/frontend/src/components/Templates/Trial.jsx index 45029c6430d..3513dfc5c21 100644 --- a/pkg/ui/v1alpha3/frontend/src/components/Templates/Trial.jsx +++ b/pkg/ui/v1alpha3/frontend/src/components/Templates/Trial.jsx @@ -1,51 +1,36 @@ import React from 'react'; -import withStyles from '@material-ui/styles/withStyles'; +import { connect } from 'react-redux'; -import Typography from '@material-ui/core/Typography'; -import Button from '@material-ui/core/Button'; +import withStyles from '@material-ui/styles/withStyles'; import TemplateList from './Common/TemplateList'; -import { connect } from 'react-redux'; -import { openDialog, fetchTrialTemplates } from '../../actions/templateActions'; -import AddDialog from './Common/AddDialog'; +import { fetchTrialTemplates } from '../../actions/templateActions'; +import { fetchNamespaces } from '../../actions/generalActions'; const styles = theme => ({ root: { - flexGrow: 1, - marginTop: 40, + width: '90%', + margin: '0 auto', + marginTop: 10, }, }); class Trial extends React.Component { componentDidMount() { - // TODO: Add possibility to change namespace in Trial Manifest form - // Right now we get templates only from kubeflow namespace - this.props.fetchTrialTemplates(''); + this.props.fetchTrialTemplates(); } - openAddDialog = () => { - this.props.openDialog('add'); - }; - render() { const { classes } = this.props; - const type = 'trial'; - return (
- - Trial Manifests - - - - - +

Trial Templates

+ +
); } } -export default connect(null, { openDialog, fetchTrialTemplates })(withStyles(styles)(Trial)); +export default connect(null, { fetchTrialTemplates, fetchNamespaces })(withStyles(styles)(Trial)); diff --git a/pkg/ui/v1alpha3/frontend/src/reducers/general.js b/pkg/ui/v1alpha3/frontend/src/reducers/general.js index 3da3788b12a..50ee7289c63 100644 --- a/pkg/ui/v1alpha3/frontend/src/reducers/general.js +++ b/pkg/ui/v1alpha3/frontend/src/reducers/general.js @@ -3,6 +3,7 @@ import * as nasCreateActions from '../actions/nasCreateActions'; import * as hpCreateActions from '../actions/hpCreateActions'; import * as hpMonitorActions from '../actions/hpMonitorActions'; import * as nasMonitorActions from '../actions/nasMonitorActions'; +import * as templateActions from '../actions/templateActions'; const initialState = { menuOpen: false, @@ -14,6 +15,13 @@ const initialState = { globalNamespace: '', experiment: {}, dialogExperimentOpen: false, + + templateNamespace: '', + templateConfigMapName: '', + templateName: '', + trialTemplatesList: [], + currentTemplateConfigMapsList: [], + currentTemplateNamesList: [], mcKindsList: ['StdOut', 'File', 'TensorFlowEvent', 'PrometheusMetric', 'Custom', 'None'], mcFileSystemKindsList: ['No File System', 'File', 'Directory'], mcURISchemesList: ['HTTP', 'HTTPS'], @@ -140,6 +148,85 @@ const generalReducer = (state = initialState, action) => { ...state, dialogExperimentOpen: false, }; + case templateActions.FETCH_TRIAL_TEMPLATES_SUCCESS: + let templates = action.trialTemplatesList; + + let configMapNames = templates[0].ConfigMapsList.map(configMap => configMap.ConfigMapName); + + let templateNames = templates[0].ConfigMapsList[0].TemplatesList.map( + template => template.Name, + ); + + return { + ...state, + trialTemplatesList: templates, + templateNamespace: templates[0].Namespace, + templateConfigMapName: templates[0].ConfigMapsList[0].ConfigMapName, + templateName: templates[0].ConfigMapsList[0].TemplatesList[0].Name, + currentTemplateConfigMapsList: configMapNames, + currentTemplateNamesList: templateNames, + }; + + case actions.FILTER_TEMPLATES_EXPERIMENT: + switch (action.trialConfigMapName) { + // Case when we change namespace + case '': + // Get Namespace index + let nsIndex = state.trialTemplatesList.findIndex(function(trialTemplate, i) { + return trialTemplate.Namespace === action.trialNamespace; + }); + + // Get new ConifgMapNames List + configMapNames = state.trialTemplatesList[nsIndex].ConfigMapsList.map( + configMap => configMap.ConfigMapName, + ); + + // Get new Template Names List + templateNames = state.trialTemplatesList[nsIndex].ConfigMapsList[0].TemplatesList.map( + template => template.Name, + ); + + return { + ...state, + templateNamespace: action.trialNamespace, + templateConfigMapName: configMapNames[0], + templateName: templateNames[0], + currentTemplateConfigMapsList: configMapNames, + currentTemplateNamesList: templateNames, + }; + // Case when we change configMap + default: + // Get Namespace index + nsIndex = state.trialTemplatesList.findIndex(function(trialTemplate, i) { + return trialTemplate.Namespace === action.trialNamespace; + }); + + // Get ConfigMap index + let cmIndex = state.trialTemplatesList[nsIndex].ConfigMapsList.findIndex(function( + configMap, + i, + ) { + return configMap.ConfigMapName === action.trialConfigMapName; + }); + + // Get new Template Names List + templateNames = state.trialTemplatesList[nsIndex].ConfigMapsList[ + cmIndex + ].TemplatesList.map(template => template.Name); + + return { + ...state, + templateNamespace: action.trialNamespace, + templateConfigMapName: action.trialConfigMapName, + templateName: templateNames[0], + currentTemplateNamesList: templateNames, + }; + } + case actions.CHANGE_TEMPLATE_NAME: + return { + ...state, + templateName: action.templateName, + }; case actions.VALIDATION_ERROR: return { ...state, diff --git a/pkg/ui/v1alpha3/frontend/src/reducers/hpCreate.js b/pkg/ui/v1alpha3/frontend/src/reducers/hpCreate.js index 4cc043c99fd..3901e7d0bde 100644 --- a/pkg/ui/v1alpha3/frontend/src/reducers/hpCreate.js +++ b/pkg/ui/v1alpha3/frontend/src/reducers/hpCreate.js @@ -95,9 +95,7 @@ const initialState = { }, ], allParameterTypes: ['int', 'double', 'categorical'], - trial: 'defaultTrialTemplate.yaml', currentYaml: '', - trialNamespace: 'kubeflow', mcSpec: { collector: { kind: 'StdOut', @@ -247,16 +245,6 @@ const hpCreateReducer = (state = initialState, action) => { ...state, parameters: parameters, }; - case actions.CHANGE_TRIAL_HP: - return { - ...state, - trial: action.trial, - }; - case actions.CHANGE_TRIAL_NAMESPACE_HP: - return { - ...state, - trialNamespace: action.namespace, - }; // Metrics Collector Kind change case actions.CHANGE_MC_KIND_HP: let newMCSpec = JSON.parse(JSON.stringify(state.mcSpec)); diff --git a/pkg/ui/v1alpha3/frontend/src/reducers/nasCreate.js b/pkg/ui/v1alpha3/frontend/src/reducers/nasCreate.js index b174e005bb6..5482b1649cc 100644 --- a/pkg/ui/v1alpha3/frontend/src/reducers/nasCreate.js +++ b/pkg/ui/v1alpha3/frontend/src/reducers/nasCreate.js @@ -345,8 +345,6 @@ const initialState = { }, ], allParameterTypes: ['int', 'double', 'categorical'], - trial: 'nasRLTrialTemplate.yaml', - trialNamespace: 'kubeflow', currentYaml: '', snackText: '', snackOpen: false, @@ -554,16 +552,6 @@ const nasCreateReducer = (state = initialState, action) => { ...state, operations, }; - case actions.CHANGE_TRIAL_NAS: - return { - ...state, - trial: action.trial, - }; - case actions.CHANGE_TRIAL_NAMESPACE_NAS: - return { - ...state, - trialNamespace: action.namespace, - }; case actions.CLOSE_SNACKBAR: return { ...state, diff --git a/pkg/ui/v1alpha3/frontend/src/reducers/template.js b/pkg/ui/v1alpha3/frontend/src/reducers/template.js index 84f730b31f6..8759a3135a2 100644 --- a/pkg/ui/v1alpha3/frontend/src/reducers/template.js +++ b/pkg/ui/v1alpha3/frontend/src/reducers/template.js @@ -1,19 +1,20 @@ import * as actions from '../actions/templateActions'; const initialState = { - menuOpen: false, addOpen: false, editOpen: false, deleteOpen: false, - trialTemplates: [], - newTemplateName: '', - newTemplateYaml: '', - currentTemplateIndex: '', - edittedTemplate: { - name: '', - yaml: '', - }, + trialTemplatesList: [], + filteredTrialTemplatesList: [], currentTemplateName: '', + edittedTemplateNamespace: '', + edittedTemplateConfigMapName: '', + edittedTemplateName: '', + edittedTemplateYaml: '', + loading: false, + edittedTemplateConfigMapSelectList: [], + filteredNamespace: 'All namespaces', + filteredConfigMapName: '', }; const rootReducer = (state = initialState, action) => { @@ -27,71 +28,71 @@ const rootReducer = (state = initialState, action) => { }; case actions.OPEN_DIALOG: switch (action.dialogType) { - case 'delete': - switch (action.templateType) { - case 'trial': - return { - ...state, - deleteOpen: true, - currentTemplateIndex: action.index, - currentTemplateName: state.trialTemplates[action.index].name, - }; - default: - return { - ...state, - }; - } - case 'edit': - switch (action.templateType) { - case 'trial': - return { - ...state, - editOpen: true, - currentTemplateIndex: action.index, - edittedTemplate: state.trialTemplates[action.index], - }; - default: - return { - ...state, - }; - } case 'add': return { ...state, addOpen: true, + edittedTemplateNamespace: action.namespace, + edittedTemplateConfigMapName: action.configMapName, + edittedTemplateName: '', + edittedTemplateYaml: '', + filteredNamespace: 'All namespaces', + filteredConfigMapName: '', + }; + case 'edit': + return { + ...state, + editOpen: true, + edittedTemplateNamespace: action.namespace, + edittedTemplateConfigMapName: action.configMapName, + edittedTemplateName: action.templateName, + edittedTemplateYaml: action.templateYaml, + currentTemplateName: action.templateName, + filteredNamespace: 'All namespaces', + filteredConfigMapName: '', + }; + case 'delete': + return { + ...state, + deleteOpen: true, + edittedTemplateNamespace: action.namespace, + edittedTemplateConfigMapName: action.configMapName, + edittedTemplateName: action.templateName, + filteredNamespace: 'All namespaces', + filteredConfigMapName: '', }; default: return state; } - case actions.CHANGE_TEMPLATE: - let edittedTemplate = state.edittedTemplate; - edittedTemplate[action.field] = action.value; + case actions.FETCH_TRIAL_TEMPLATES_REQUEST: + return { + ...state, + loading: true, + }; + case actions.FETCH_TRIAL_TEMPLATES_FAILURE: return { ...state, - edittedTemplate: edittedTemplate, + loading: false, }; case actions.FETCH_TRIAL_TEMPLATES_SUCCESS: return { ...state, - trialTemplates: action.templates, + trialTemplatesList: action.trialTemplatesList, + filteredTrialTemplatesList: action.trialTemplatesList, + loading: false, }; case actions.ADD_TEMPLATE_SUCCESS: case actions.DELETE_TEMPLATE_SUCCESS: case actions.EDIT_TEMPLATE_SUCCESS: - switch (action.templateType) { - case 'trial': - return { - ...state, - addOpen: false, - deleteOpen: false, - editOpen: false, - trialTemplates: action.templates, - }; - default: - return { - ...state, - }; - } + return { + ...state, + addOpen: false, + deleteOpen: false, + editOpen: false, + trialTemplatesList: action.trialTemplatesList, + filteredTrialTemplatesList: action.trialTemplatesList, + }; + case actions.ADD_TEMPLATE_FAILURE: case actions.EDIT_TEMPLATE_FAILURE: case actions.DELETE_TEMPLATE_FAILURE: @@ -101,6 +102,48 @@ const rootReducer = (state = initialState, action) => { deleteOpen: false, editOpen: false, }; + case actions.CHANGE_TEMPLATE: + return { + ...state, + edittedTemplateNamespace: action.edittedTemplateNamespace, + edittedTemplateConfigMapName: action.edittedTemplateConfigMapName, + edittedTemplateName: action.edittedTemplateName, + edittedTemplateYaml: action.edittedTemplateYaml, + edittedTemplateConfigMapSelectList: action.edittedTemplateConfigMapSelectList, + }; + case actions.FILTER_TEMPLATES: + let templates = state.trialTemplatesList; + + //Filter ConfigMap + let filteredConfigMaps = []; + for (let i = 0; i < templates.length; i++) { + let configMapsList = []; + for (let j = 0; j < templates[i].ConfigMapsList.length; j++) { + if (templates[i].ConfigMapsList[j].ConfigMapName.includes(action.filteredConfigMapName)) { + configMapsList.push(templates[i].ConfigMapsList[j]); + } + } + if (configMapsList.length != 0) { + let newNamespaceBlock = {}; + newNamespaceBlock.Namespace = templates[i].Namespace; + newNamespaceBlock.ConfigMapsList = configMapsList; + filteredConfigMaps.push(newNamespaceBlock); + } + } + + //Filter Namespace + let filteredTemplates = filteredConfigMaps.filter( + template => + template.Namespace == action.filteredNamespace || + action.filteredNamespace == 'All namespaces', + ); + + return { + ...state, + filteredNamespace: action.filteredNamespace, + filteredConfigMapName: action.filteredConfigMapName, + filteredTrialTemplatesList: filteredTemplates, + }; default: return state; } diff --git a/pkg/ui/v1alpha3/frontend/src/sagas/index.js b/pkg/ui/v1alpha3/frontend/src/sagas/index.js index ccf79b17d2f..7343c9fda4d 100644 --- a/pkg/ui/v1alpha3/frontend/src/sagas/index.js +++ b/pkg/ui/v1alpha3/frontend/src/sagas/index.js @@ -422,19 +422,11 @@ export const fetchTrialTemplates = function*() { while (true) { const action = yield take(templateActions.FETCH_TRIAL_TEMPLATES_REQUEST); try { - const result = yield call(goFetchTrialTemplates, action.namespace); + const result = yield call(goFetchTrialTemplates); if (result.status === 200) { - let data = Object.assign(result.data, {}); - data.map((template, i) => { - Object.keys(template).forEach(key => { - const value = template[key]; - delete template[key]; - template[key.toLowerCase()] = value; - }); - }); yield put({ type: templateActions.FETCH_TRIAL_TEMPLATES_SUCCESS, - templates: data, + trialTemplatesList: result.data.Data, }); } else { yield put({ @@ -451,7 +443,7 @@ export const fetchTrialTemplates = function*() { const goFetchTrialTemplates = function*(namespace) { try { - const result = yield call(axios.get, `/katib/fetch_trial_templates/?namespace=${namespace}`); + const result = yield call(axios.get, `/katib/fetch_trial_templates`); return result; } catch (err) { yield put({ @@ -466,24 +458,15 @@ export const addTemplate = function*() { try { const result = yield call( goAddTemplate, - action.name, - action.yaml, - action.kind, - action.action, + action.edittedNamespace, + action.edittedConfigMapName, + action.edittedName, + action.edittedYaml, ); if (result.status === 200) { - let data = Object.assign(result.data.Data, {}); - data.map((template, i) => { - Object.keys(template).forEach(key => { - const value = template[key]; - delete template[key]; - template[key.toLowerCase()] = value; - }); - }); yield put({ type: templateActions.ADD_TEMPLATE_SUCCESS, - templates: data, - templateType: result.data.TemplateType, + trialTemplatesList: result.data.Data, }); } else { yield put({ @@ -498,15 +481,15 @@ export const addTemplate = function*() { } }; -const goAddTemplate = function*(name, yaml, kind, action) { +const goAddTemplate = function*(edittedNamespace, edittedConfigMapName, edittedName, edittedYaml) { try { const data = { - name, - yaml, - kind, - action, + edittedNamespace, + edittedConfigMapName, + edittedName, + edittedYaml, }; - const result = yield call(axios.post, '/katib/update_template/', data); + const result = yield call(axios.post, '/katib/add_template/', data); return result; } catch (err) { yield put({ @@ -521,24 +504,16 @@ export const editTemplate = function*() { try { const result = yield call( goEditTemplate, - action.name, - action.yaml, - action.kind, - action.action, + action.edittedNamespace, + action.edittedConfigMapName, + action.currentName, + action.edittedName, + action.edittedYaml, ); if (result.status === 200) { - let data = Object.assign(result.data.Data, {}); - data.map((template, i) => { - Object.keys(template).forEach(key => { - const value = template[key]; - delete template[key]; - template[key.toLowerCase()] = value; - }); - }); yield put({ type: templateActions.EDIT_TEMPLATE_SUCCESS, - templates: data, - templateType: result.data.TemplateType, + trialTemplatesList: result.data.Data, }); } else { yield put({ @@ -553,15 +528,22 @@ export const editTemplate = function*() { } }; -const goEditTemplate = function*(name, yaml, kind, action) { +const goEditTemplate = function*( + edittedNamespace, + edittedConfigMapName, + currentName, + edittedName, + edittedYaml, +) { try { const data = { - name, - yaml, - kind, - action, + edittedNamespace, + edittedConfigMapName, + currentName, + edittedName, + edittedYaml, }; - const result = yield call(axios.post, '/katib/update_template/', data); + const result = yield call(axios.post, '/katib/edit_template/', data); return result; } catch (err) { yield put({ @@ -574,20 +556,16 @@ export const deleteTemplate = function*() { while (true) { const action = yield take(templateActions.DELETE_TEMPLATE_REQUEST); try { - const result = yield call(goDeleteTemplate, action.name, action.kind, action.action); + const result = yield call( + goDeleteTemplate, + action.edittedNamespace, + action.edittedConfigMapName, + action.edittedName, + ); if (result.status === 200) { - let data = Object.assign(result.data.Data, {}); - data.map((template, i) => { - Object.keys(template).forEach(key => { - const value = template[key]; - delete template[key]; - template[key.toLowerCase()] = value; - }); - }); yield put({ type: templateActions.DELETE_TEMPLATE_SUCCESS, - templates: data, - templateType: result.data.TemplateType, + trialTemplatesList: result.data.Data, }); } else { yield put({ @@ -602,14 +580,14 @@ export const deleteTemplate = function*() { } }; -const goDeleteTemplate = function*(name, kind, action) { +const goDeleteTemplate = function*(edittedNamespace, edittedConfigMapName, edittedName) { try { const data = { - name, - kind, - action, + edittedNamespace, + edittedConfigMapName, + edittedName, }; - const result = yield call(axios.post, '/katib/update_template/', data); + const result = yield call(axios.post, '/katib/delete_template/', data); return result; } catch (err) { yield put({ diff --git a/pkg/ui/v1alpha3/types.go b/pkg/ui/v1alpha3/types.go index 7c0784092d6..bb9b3deb904 100644 --- a/pkg/ui/v1alpha3/types.go +++ b/pkg/ui/v1alpha3/types.go @@ -1,6 +1,7 @@ package v1alpha3 import ( + "github.com/kubeflow/katib/pkg/controller.v1alpha3/consts" "github.com/kubeflow/katib/pkg/util/v1alpha3/katibclient" ) @@ -9,6 +10,9 @@ const maxMsgSize = 1<<31 - 1 var ( // namespace = "default" allowedHeaders = "Accept, Content-Type, Content-Length, Accept-Encoding, Authorization, X-CSRF-Token" + + TrialTemplateLabel = map[string]string{ + consts.LabelTrialTemplateConfigMapName: consts.LabelTrialTemplateConfigMapValue} ) type Decoder struct { @@ -36,7 +40,21 @@ type JobView struct { Namespace string } -type TemplateView struct { +type TrialTemplatesView struct { + Namespace string + ConfigMapsList []ConfigMapsList +} + +type TrialTemplatesResponse struct { + Data []TrialTemplatesView +} + +type ConfigMapsList struct { + ConfigMapName string + TemplatesList []TemplatesList +} + +type TemplatesList struct { Name string Yaml string } @@ -45,11 +63,6 @@ type KatibUIHandler struct { katibClient katibclient.Client } -type TemplateResponse struct { - TemplateType string - Data []TemplateView -} - type NNView struct { Name string TrialName string @@ -61,6 +74,9 @@ type NNView struct { type JobType string const ( - JobTypeHP = "HP" - JobTypeNAS = "NAS" + JobTypeHP = "HP" + JobTypeNAS = "NAS" + ActionTypeAdd = "add" + ActionTypeEdit = "edit" + ActionTypeDelete = "delete" ) diff --git a/pkg/ui/v1alpha3/util.go b/pkg/ui/v1alpha3/util.go index 289d6b04f10..4c6cbcadc73 100644 --- a/pkg/ui/v1alpha3/util.go +++ b/pkg/ui/v1alpha3/util.go @@ -2,13 +2,14 @@ package v1alpha3 import ( "encoding/json" - "errors" "log" "net/http" "strconv" "strings" gographviz "github.com/awalterschulze/gographviz" + apiv1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func (k *KatibUIHandler) getExperimentList(namespace string, typ JobType) ([]JobView, error) { @@ -46,42 +47,108 @@ func enableCors(w *http.ResponseWriter) { (*w).Header().Set("Access-Control-Allow-Credentials", "true") } -func getTemplatesView(templates map[string]string) []TemplateView { - templatesView := make([]TemplateView, 0) +func (k *KatibUIHandler) getTrialTemplatesViewList() ([]TrialTemplatesView, error) { + trialTemplatesViewList := make([]TrialTemplatesView, 0) - for key := range templates { - templatesView = append(templatesView, TemplateView{Name: key, Yaml: templates[key]}) + // Get all namespaces + namespaceList, err := k.katibClient.GetNamespaceList() + if err != nil { + log.Printf("GetNamespaceList failed: %v", err) + return nil, err + } + + // Get Trial Template ConfigMap for each namespace + for _, namespace := range namespaceList.Items { + ns := namespace.ObjectMeta.Name + trialTemplatesConfigMapList, err := k.katibClient.GetTrialTemplates(ns) + if err != nil { + log.Printf("GetTrialTemplates failed: %v", err) + return nil, err + } + + if len(trialTemplatesConfigMapList.Items) != 0 { + trialTemplatesViewList = append(trialTemplatesViewList, getTrialTemplatesView(trialTemplatesConfigMapList)) + } + } + return trialTemplatesViewList, nil +} +func getTrialTemplatesView(templatesConfigMapList *apiv1.ConfigMapList) TrialTemplatesView { + + trialTemplateView := TrialTemplatesView{ + Namespace: templatesConfigMapList.Items[0].ObjectMeta.Namespace, + ConfigMapsList: []ConfigMapsList{}, + } + for _, configMap := range templatesConfigMapList.Items { + configMapList := ConfigMapsList{ + ConfigMapName: configMap.ObjectMeta.Name, + TemplatesList: []TemplatesList{}, + } + for key := range configMap.Data { + templatesList := TemplatesList{ + Name: key, + Yaml: configMap.Data[key], + } + configMapList.TemplatesList = append(configMapList.TemplatesList, templatesList) + } + + trialTemplateView.ConfigMapsList = append(trialTemplateView.ConfigMapsList, configMapList) } - return templatesView + + return trialTemplateView } -func (k *KatibUIHandler) updateTemplates(newTemplate map[string]interface{}, isDelete bool) (TemplateResponse, error) { - var currentTemplates map[string]string - var err error +func (k *KatibUIHandler) updateTrialTemplates( + edittedNamespace, + edittedConfigMapName, + edittedName, + edittedYaml, + currentName, + actionType string) ([]TrialTemplatesView, error) { - currentTemplates, err = k.katibClient.GetTrialTemplates() + templates, err := k.katibClient.GetConfigMap(edittedConfigMapName, edittedNamespace) if err != nil { - return TemplateResponse{}, errors.New("GetTrialTemplates failed: " + err.Error()) + log.Printf("GetConfigMap failed: %v", err) + return nil, err + } + + if actionType == ActionTypeAdd { + if len(templates) == 0 { + templates = make(map[string]string) + templates[edittedName] = edittedYaml + } else { + templates[edittedName] = edittedYaml + } + } else if actionType == ActionTypeEdit { + delete(templates, currentName) + templates[edittedName] = edittedYaml + } else if actionType == ActionTypeDelete { + delete(templates, edittedName) } - if isDelete { - delete(currentTemplates, newTemplate["name"].(string)) - } else { - currentTemplates[newTemplate["name"].(string)] = newTemplate["yaml"].(string) + templatesConfigMap := &apiv1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: edittedConfigMapName, + Namespace: edittedNamespace, + Labels: TrialTemplateLabel, + }, + Data: templates, } - err = k.katibClient.UpdateTrialTemplates(currentTemplates) + err = k.katibClient.UpdateConfigMap(templatesConfigMap) if err != nil { - return TemplateResponse{}, errors.New("UpdateTrialTemplates failed: " + err.Error()) + log.Printf("UpdateConfigMap failed: %v", err) + return nil, err } - TemplateResponse := TemplateResponse{ - Data: getTemplatesView(currentTemplates), - TemplateType: newTemplate["kind"].(string), + newTemplates, err := k.getTrialTemplatesViewList() + if err != nil { + log.Printf("getTrialTemplatesViewList: %v", err) + return nil, err } - return TemplateResponse, nil -} + return newTemplates, nil + +} func getNodeString(block *Block) string { var nodeString string switch block.Type { diff --git a/pkg/util/v1alpha3/katibclient/katib_client.go b/pkg/util/v1alpha3/katibclient/katib_client.go index 81e4df28652..2aaacf30e37 100644 --- a/pkg/util/v1alpha3/katibclient/katib_client.go +++ b/pkg/util/v1alpha3/katibclient/katib_client.go @@ -19,7 +19,6 @@ import ( "context" apiv1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes/scheme" "sigs.k8s.io/controller-runtime/pkg/client" @@ -42,9 +41,9 @@ type Client interface { GetConfigMap(name string, namespace ...string) (map[string]string, error) GetTrial(name string, namespace ...string) (*trialsv1alpha3.Trial, error) GetTrialList(name string, namespace ...string) (*trialsv1alpha3.TrialList, error) - GetTrialTemplates(namespace ...string) (map[string]string, error) + GetTrialTemplates(namespace ...string) (*apiv1.ConfigMapList, error) GetSuggestion(name string, namespace ...string) (*suggestionsv1alpha3.Suggestion, error) - UpdateTrialTemplates(newTrialTemplates map[string]string, namespace ...string) error + UpdateConfigMap(newConfigMap *apiv1.ConfigMap) error GetNamespaceList() (*apiv1.NamespaceList, error) } @@ -176,29 +175,29 @@ func (k *KatibClient) GetConfigMap(name string, namespace ...string) (map[string return configMap.Data, nil } -// GetTrialTemplates returns the trial template if it exists. -func (k *KatibClient) GetTrialTemplates(namespace ...string) (map[string]string, error) { +// GetTrialTemplates returns all trial templates from the given namespace +func (k *KatibClient) GetTrialTemplates(namespace ...string) (*apiv1.ConfigMapList, error) { ns := getNamespace(namespace...) - data, err := k.GetConfigMap(experimentsv1alpha3.DefaultTrialConfigMapName, ns) - if err != nil && errors.IsNotFound(err) { - return map[string]string{}, nil - } else if err != nil { + templatesConfigMapList := &apiv1.ConfigMapList{} + + templateLabel := map[string]string{consts.LabelTrialTemplateConfigMapName: consts.LabelTrialTemplateConfigMapValue} + listOpt := &client.ListOptions{} + listOpt.MatchingLabels(templateLabel).InNamespace(ns) + + err := k.client.List(context.TODO(), listOpt, templatesConfigMapList) + + if err != nil { return nil, err } - return data, nil -} -func (k *KatibClient) UpdateTrialTemplates(newTrialTemplates map[string]string, namespace ...string) error { - ns := getNamespace(namespace...) - trialTemplates := &apiv1.ConfigMap{} + return templatesConfigMapList, nil - if err := k.client.Get(context.TODO(), types.NamespacedName{Name: experimentsv1alpha3.DefaultTrialConfigMapName, Namespace: ns}, trialTemplates); err != nil { - return err - } - trialTemplates.Data = newTrialTemplates +} + +func (k *KatibClient) UpdateConfigMap(newConfigMap *apiv1.ConfigMap) error { - if err := k.client.Update(context.Background(), trialTemplates); err != nil { + if err := k.client.Update(context.Background(), newConfigMap); err != nil { return err } return nil