From 6d2efd24487c19b487ae547da888af74cd5e8962 Mon Sep 17 00:00:00 2001 From: Hattori Keigo Date: Thu, 14 Mar 2019 16:02:02 +0900 Subject: [PATCH 01/92] Use `formik` instead `redux-form` --- frontend/package.json | 2 +- frontend/yarn.lock | 82 ++++++++++++++++++++++++++++++------------- 2 files changed, 59 insertions(+), 25 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 0df199a..6545a9a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -25,6 +25,7 @@ "babel-preset-react": "^6.24.1", "css-loader": "^0.28.11", "express": "^4.16.3", + "formik": "^1.5.1", "html-webpack-plugin": "3.0.7", "node-sass": "^4.9.0", "react": "^16.4.1", @@ -34,7 +35,6 @@ "react-router-dom": "^4.3.1", "reactstrap": "^6.1.0", "redux": "^4.0.0", - "redux-form": "^7.4.2", "redux-thunk": "^2.3.0", "sass-loader": "^7.0.3", "style-loader": "^0.21.0", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index f7553e0..5f52502 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1877,6 +1877,14 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" +create-react-context@^0.2.2: + version "0.2.3" + resolved "https://registry.yarnpkg.com/create-react-context/-/create-react-context-0.2.3.tgz#9ec140a6914a22ef04b8b09b7771de89567cb6f3" + integrity sha512-CQBmD0+QGgTaxDL3OX1IDXYqjkp2It4RIbcb99jS6AEg27Ga+a9G3JtK6SIu0HBwPLZlmwt9F7UwWA4Bn92Rag== + dependencies: + fbjs "^0.8.0" + gud "^1.0.0" + cross-spawn@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" @@ -2099,6 +2107,11 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== +deepmerge@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170" + integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA== + define-properties@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" @@ -2403,11 +2416,6 @@ es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14: es6-symbol "~3.1.1" next-tick "1" -es6-error@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" - integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== - es6-iterator@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" @@ -2660,7 +2668,7 @@ faye-websocket@~0.11.0: dependencies: websocket-driver ">=0.5.1" -fbjs@^0.8.16: +fbjs@^0.8.0, fbjs@^0.8.16: version "0.8.17" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90= @@ -2806,6 +2814,21 @@ form-data@~2.1.1: combined-stream "^1.0.5" mime-types "^2.1.12" +formik@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/formik/-/formik-1.5.1.tgz#ef5687e1ade5b1fe5f1d51435b422238aad107e9" + integrity sha512-FBWGBKQkcCE4d5b5l2fKccD9d1QxNxw/0bQTRvp3EjzA8Bnjmsm9H/Oy0375UA8P3FPmfJkF4cXLLdEqK7fP5A== + dependencies: + create-react-context "^0.2.2" + deepmerge "^2.1.1" + hoist-non-react-statics "^2.5.5" + lodash "^4.17.11" + lodash-es "^4.17.11" + prop-types "^15.6.1" + react-fast-compare "^2.0.1" + tiny-warning "^1.0.2" + tslib "^1.9.3" + forwarded@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" @@ -3022,6 +3045,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.4: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg= +gud@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" + integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw== + handle-thing@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4" @@ -3171,7 +3199,7 @@ hoek@2.x.x: resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" integrity sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0= -hoist-non-react-statics@^2.5.0, hoist-non-react-statics@^2.5.4: +hoist-non-react-statics@^2.5.0, hoist-non-react-statics@^2.5.5: version "2.5.5" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw== @@ -3963,7 +3991,12 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" -lodash-es@^4.17.10, lodash-es@^4.17.5, lodash-es@^4.2.1: +lodash-es@^4.17.11: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.11.tgz#145ab4a7ac5c5e52a3531fb4f310255a152b4be0" + integrity sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q== + +lodash-es@^4.17.5, lodash-es@^4.2.1: version "4.17.10" resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.10.tgz#62cd7104cdf5dd87f235a837f0ede0e8e5117e05" integrity sha512-iesFYPmxYYGTcmQK0sL8bX3TGHyM6b2qREaB4kamHfQyfPJP0xgoGxp19nsH16nsfquLdiyKyX3mQkfiSGV8Rg== @@ -4023,11 +4056,16 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.3.0, lodash@~4.17.10: +lodash@^4.0.0, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@^4.3.0, lodash@~4.17.10: version "4.17.10" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" integrity sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg== +lodash@^4.17.11: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== + log-symbols@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" @@ -5483,6 +5521,11 @@ react-dom@^16.4.1: object-assign "^4.1.1" prop-types "^15.6.0" +react-fast-compare@^2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" + integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== + react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" @@ -5641,20 +5684,6 @@ reduce-function-call@^1.0.1: dependencies: balanced-match "^0.4.2" -redux-form@^7.4.2: - version "7.4.2" - resolved "https://registry.yarnpkg.com/redux-form/-/redux-form-7.4.2.tgz#d6061088fb682eb9fc5fb9749bd8b102f03154b0" - integrity sha512-QxC36s4Lelx5Cr8dbpxqvl23dwYOydeAX8c6YPmgkz/Dhj053C16S2qoyZN6LO6HJ2oUF00rKsAyE94GwOUhFA== - dependencies: - es6-error "^4.1.1" - hoist-non-react-statics "^2.5.4" - invariant "^2.2.4" - is-promise "^2.1.0" - lodash "^4.17.10" - lodash-es "^4.17.10" - prop-types "^15.6.1" - react-lifecycles-compat "^3.0.4" - redux-thunk@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622" @@ -6568,6 +6597,11 @@ timers-browserify@^2.0.4: dependencies: setimmediate "^1.0.4" +tiny-warning@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.2.tgz#1dfae771ee1a04396bdfde27a3adcebc6b648b28" + integrity sha512-rru86D9CpQRLvsFG5XFdy0KdLAvjdQDyZCsRcuu60WtzFylDM3eAWSxEVz5kzL2Gp544XiUvPbVKtOA/txLi9Q== + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -6650,7 +6684,7 @@ ts-loader@^4.4.1: micromatch "^3.1.4" semver "^5.0.1" -tslib@^1.9.0: +tslib@^1.9.0, tslib@^1.9.3: version "1.9.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== From e1bcbc83703dec3f3eb267dfb2a6fab98e9dde6a Mon Sep 17 00:00:00 2001 From: Hattori Keigo Date: Thu, 14 Mar 2019 16:02:46 +0900 Subject: [PATCH 02/92] Update constants --- .../src/components/App/Admin/constants.ts | 53 +++++++++++++++---- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/App/Admin/constants.ts b/frontend/src/components/App/Admin/constants.ts index 377f7fa..90bf0c7 100644 --- a/frontend/src/components/App/Admin/constants.ts +++ b/frontend/src/components/App/Admin/constants.ts @@ -1,17 +1,48 @@ -export enum role { +export enum dataServerMode { + local = 'local', + ceph_s3 = 'ceph_s3', + aws_s3 = 'aws_s3' +} + +export enum projectRole { + member = 'member', + admin = 'admin' +} + +export enum applicationRole { viewer = 'viewer', editor = 'editor', - owner = 'owner' + admin = 'admin' } -export const apiConvert = (param) => { - if (param === 'Role.viewer') { - return role.viewer.toString() - } else if (param === 'Role.editor') { - return role.editor.toString() - } else if (param === 'Role.owner') { - return role.owner.toString() +export const apiConvertDataServerMode = (param) => { + if (param === 'DataServerModeEnum.local') { + return dataServerMode.local.toString() + } else if (param === 'DataServerModeEnum.ceph_s3') { + return dataServerMode.ceph_s3.toString() + } else if (param === 'DataServerModeEnum.aws_s3') { + return dataServerMode.aws_s3.toString() } else { - return role.viewer.toString() + return false } -} +}; + +export const apiConvertProjectRole = (param) => { + if (param === 'ProjectRole.member') { + return projectRole.member.toString() + } else if (param === 'ProjectRole.admin') { + return projectRole.admin.toString() + } else { + return false + } +}; + +export const apiConvertApplicationRole = (param) => { + if (param === 'ApplicationRole.admin') { + return applicationRole.admin.toString() + } else if (param === 'ApplicationRole.editor') { + return applicationRole.editor.toString() + } else { + return applicationRole.viewer.toString() + } +}; From 649a16423ecee107144b27a3c7012beed09487b6 Mon Sep 17 00:00:00 2001 From: Hattori Keigo Date: Thu, 14 Mar 2019 16:03:26 +0900 Subject: [PATCH 03/92] Update `/apis` --- frontend/src/apis/Core/index.tsx | 14 +- frontend/src/apis/index.tsx | 1023 ++++++++++++++++++------------ 2 files changed, 633 insertions(+), 404 deletions(-) diff --git a/frontend/src/apis/Core/index.tsx b/frontend/src/apis/Core/index.tsx index 2fcc983..7cb1a59 100644 --- a/frontend/src/apis/Core/index.tsx +++ b/frontend/src/apis/Core/index.tsx @@ -2,7 +2,7 @@ * Core implementations for handling external APIs */ -export const JWT_TOKEN_KEY = 'jwt_token' +export const JWT_TOKEN_KEY = 'jwt_token'; export enum APIErrorType { unknown, @@ -14,7 +14,7 @@ export enum APIErrorType { * */ export class APIError implements Error { - name = 'APIError' + name = 'APIError'; constructor( public message: string, @@ -86,7 +86,7 @@ export async function getRequest( ) { const options = { method: 'GET', - } + }; return rawRequest(entryPoint, convert, options) } @@ -101,7 +101,7 @@ export async function postJsonRequest( headers: { 'Content-Type': 'application/json' } - } + }; return rawRequest(entryPoint, convert, options) } @@ -116,7 +116,7 @@ export async function putJsonRequest( headers: { 'Content-Type': 'application/json' } - } + }; return rawRequest(entryPoint, convert, options) } @@ -126,7 +126,7 @@ export async function deleteRequest( ) { const options = { method: 'DELETE', - } + }; return rawRequest(entryPoint, convert, options) } @@ -199,7 +199,7 @@ export async function rawRequest( * * @param entryPoints {string[]} Target API entrypoints * @param convert - * @param paramsList {any[]} + * @param requestList {any[]} */ export async function rawMultiRequest( entryPoints: string[], diff --git a/frontend/src/apis/index.tsx b/frontend/src/apis/index.tsx index ac8b90f..f139eb0 100644 --- a/frontend/src/apis/index.tsx +++ b/frontend/src/apis/index.tsx @@ -1,462 +1,590 @@ import * as APICore from './Core' -import { apiConvert } from '@components/App/Admin/constants' +import { apiConvertDataServerMode, apiConvertProjectRole, apiConvertApplicationRole } from '@components/App/Admin/constants' -export class Application { - constructor( - public name: string = '', - public id: string = '', - public description: string = '', - public date: Date = null, - public kubernetesId: number = null - ) { } -} +const snakelize = (value: string): string => (value.split(/(?=[A-Z])/).join('_').toLowerCase()); +const camelize = (value: string): string => { + const splitted = value.split('_'); + const tail = + splitted.length > 1 + ? splitted.slice(1).map((v: string) => v[0].toUpperCase() + v.slice(1)).reduce((p, c) => (p + c)) + : ''; -export class Service { - constructor( - public id: string = '', - public name: string = '', - public serviceLevel: string = '', - public modelId: string = null, - public host: string = '', - public description: string = '', - ) { } -} + return splitted[0] + tail +}; +const convertKeys = (params, func) => Object.keys(params) + .map((value) => ({ [func(value)]: params[value] })) + .reduce((l, r) => Object.assign(l, r), {}); -export class AuthToken { - constructor( - public jwt: string = '' - ) { } +// POST or PATCH +export interface ProjectParam { + id?: number, + displayName: string + description: string + registeredDate?: Date + method: string } +export async function saveProject(params: ProjectParam) { + const requestBody = { + ...convertKeys(params, snakelize), + method: {} = {} + }; -export class UserRole { - constructor( - public applicationId: number, - public role: string = '' - ) { } -} + const convert = (response) => response.status; -export class UserInfo { - constructor( - public user: { - userUid: string - userName: string - }, - public roles: UserRole[], - ) { } + if (params.method === 'post') { + return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects`, requestBody, convert, 'POST') + } else if (params.method === 'patch') { + return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.id}`, requestBody, convert, 'PATCH') + } + + throw new RangeError(`You specified wrong save method ${params.method}`) +} + +export interface DataServerParam { + projectId: number + dataServerMode: string + cephAccessKey: string + cephSecretKey: string + cephHost: string + cephPort: number + cephIsSecure: boolean + cephBucketName: string + awsAccessKey: string + awsSecretKey: string + awsBucketName: string + registeredDate?: Date + method: string } +export async function saveDataServer(params: DataServerParam) { + const requestBody = { + ...convertKeys(params, snakelize), + method: {} = {} + }; -export class AccessControlList { - constructor( - public userUid: string, - public userName: string, - public role: string, - ) { } + const convert = (response) => response.status; + + return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/data_servers`, requestBody, convert, params.method === 'post' ? 'POST' : 'PATCH'); } -export interface ServiceSimple { - id: number - name: string // Corresponds to `display_name` - applicationId: number - serviceLevel: string // Corresponds to `service_level` - modelId?: number +export interface KubernetesParam { + id?: number, + projectId: number description: string + displayName: string + exposedHost: string + exposedPort: number registeredDate?: Date + configFile: File | string + method: string } +export async function saveKubernetes(params: KubernetesParam) { + const requestBody = { + ...convertKeys(params, snakelize), + file: params.configFile, + method: {} = {}, + configFile: {} = {} + }; -export interface ServiceKubernetes { - // General - id: number - name: string - serviceLevel: string - // Scale - policyMaxSurge - policyMaxUnavailable - policyWaitSeconds - replicasDefault - replicasMaximum - replicasMinimum - autoscaleCpuThreshold - // Resource - resourceLimitCpu - resourceLimitMemory - resourceRequestCpu - resourceRequestMemory - // Model boot - serviceBootScript - serviceGitBranch - serviceGitUrl - servicePort - commitMessage - containerImage -} - -export class Environment { - constructor( - public name: string = '', - public services: Set = new Set(), - ) { } -} + const convert = (response) => response.status; -export class ModelResponse { - constructor( - public status: string = '', - public message: string = '' - ) { } + if (params.method === 'post') { + return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/kubernetes`, requestBody, convert, 'POST') + } else if (params.method === 'patch') { + return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/kubernetes/${params.id}`, requestBody, convert, 'PATCH') + } + + throw new RangeError(`You specified wrong save method ${params.method}`) } -export class Model { - constructor( - public name: string = '', - public id: string = '', - public registeredDate: Date = null, - ) { } +export interface ApplicationParam { + id?: string, + projectId: number + description: string + applicationName: string + registeredDate?: Date + method: string } +export async function saveApplication(params: ApplicationParam) { + const requestBody = { + ...convertKeys(params, snakelize), + method: {} = {}, + }; -const snakelize = (value: string): string => (value.split(/(?=[A-Z])/).join('_').toLowerCase()) -const camelize = (value: string): string => { - const splitted = value.split('_') - const tail = - splitted.length > 1 - ? splitted.slice(1).map((v: string) => v[0].toUpperCase() + v.slice(1)).reduce((p, c) => (p + c)) - : '' + const convert = (response) => response.status; - return splitted[0] + tail + if (params.method === 'post') { + return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications`, requestBody, convert, 'POST') + } else if (params.method === 'patch') { + return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.id}`, requestBody, convert, 'PATCH') + } + + throw new RangeError(`You specified wrong save method ${params.method}`) } -const convertKeys = (params, func) => Object.keys(params) - .map((value) => ({ [func(value)]: params[value] })) - .reduce((l, r) => Object.assign(l, r), {}) - -// POST or PATCH or PUT APIs -/** - * Kubernetes host setting - * - * @type {number} id Unique id for the host setting - * @type {string} displayName Name to display on the frontend - * @type {string} dnsName DNS of services in Kubernetes - * @type {File | string} configFile Generated config file generated - * by kubectl or fetched path - */ -export interface KubernetesHost { - id?: number, - description: string, - displayName: string, - dnsName: string, - registeredDate?: Date, - configFile: File | string - // DB - dbMysqlDbname - dbMysqlHost - dbMysqlPassword - dbMysqlPort - dbMysqlUser - // Deployment dir - hostModelDir - podModelDir -} -export type SaveKubernetesHostParam = KubernetesHost & { method: string } -export async function saveKubernetesHost(param: SaveKubernetesHostParam) { + +export interface UpdateServiceParam { + id: string + projectId: number + applicationId: number + description: string + displayName: string + version: string +} +export async function updateService(params: UpdateServiceParam): Promise { const requestBody = { - ...convertKeys(param, snakelize), - file: param.configFile, + ...convertKeys(params, snakelize), method: {} = {}, - configFile: {} = {} - } + }; - const convert = (response) => response.status + const convert = (response) => response.status; - if (param.method === 'add') { - return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/kubernetes/`, requestBody, convert) - } else if (param.method === 'edit') { - return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/kubernetes/${param.id}`, requestBody, convert, 'PATCH') - } + return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/services/${params.id}`, requestBody, convert, 'PATCH') +} - throw new RangeError(`You specified wrong save method ${param.method}`) +export interface SingleServiceParam { + isKubernetes: boolean + projectId: number + applicationId: number + description: string + displayName: string + serviceLevel: string + version: string + serviceModelAssignment: number + serviceInsecureHost: string + serviceInsecurePort: number + registeredDate?: Date + method: string } +export interface DeploymentParam { + id?: string + replicasDefault?: number + replicasMinimum?: number + replicasMaximum?: number + autoscaleCpuThreshold?: number + policyMaxSurge?: number + policyMaxUnavailable?: number + policyWaitSeconds?: number + containerImage?: string + serviceGitUrl?: string + serviceGitBranch?: string + serviceBootScript?: string + resourceRequestCpu?: number + resourceRequestMemory?: string + resourceLimitCpu?: number + resourceLimitMemory?: string + debugMode?: boolean +} +export type ServiceDeploymentParam = SingleServiceParam & DeploymentParam +export async function saveServiceDeployment(params: ServiceDeploymentParam): Promise { + const requestBody = { + ...convertKeys(params, snakelize), + method: {} = {}, + }; -export async function addApplication(params): Promise { - const convert = (result) => result.status - const requestBody = convertKeys(params, snakelize) + const convert = (response) => response.status; - if (params.applicationType === 'kubernetes') { - return APICore.formDataRequest( - `${process.env.API_HOST}:${process.env.API_PORT}/api/kubernetes/${params.kubernetesId}/applications`, - { - ...requestBody, - app_name: params.name, - app_dnsName: params.appDnsName - }, - convert - ) - } else if (params.applicationType === 'simple') { - return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/applications/`, requestBody, convert) + if (params.isKubernetes) { + if (params.method === 'post') { + return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/service_deployment`, requestBody, convert, 'POST') + } else if (params.method === 'patch') { + return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/service_deployment/${params.id}`, requestBody, convert, 'PATCH') + } + } else { + return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/single_service_registration`, requestBody, convert, 'POST') } - throw new RangeError(`You specified wrong parameter type ${params.applicationType}`) + + throw new RangeError(`You specified wrong save method ${params.method}`) } -export interface SaveServiceParam { - id?: string - applicationType: string - applicationId: string - kubernetesId?: string +export interface ServiceRoutingParam { + projectId: number + applicationId: number serviceLevel: string - name: string - modelId: string - description: string - mode: string - saveDescription?: boolean -} -export async function saveService(params: SaveServiceParam): Promise { - const requestBody = Object.keys(params) - .map((value) => ({ [snakelize(value)]: params[value] })) - .reduce((l, r) => Object.assign(l, r), {}) - const convert = (result) => result.status as boolean - const idUrlString = params.id ? `/${params.id}` : '' - - // Just save description - if (params.saveDescription) { - return APICore.formDataRequest( - `${process.env.API_HOST}:${process.env.API_PORT}/api/applications/${params.applicationId}/services${idUrlString}`, - requestBody, - convert, - params.mode === 'add' ? 'POST' : 'PATCH' - ) - } + serviceIds: string[] + serviceWeights: number[] +} +export async function updateServiceRouting(params: ServiceRoutingParam): Promise { + const requestBody = { + ...convertKeys(params, snakelize), + method: {} = {}, + }; - if (params.applicationType === 'kubernetes') { - return APICore.formDataRequest( - `${process.env.API_HOST}:${process.env.API_PORT}/api/kubernetes/${params.kubernetesId}/applications/${params.applicationId}/services${idUrlString}`, - requestBody, - convert, - params.mode === 'add' ? 'POST' : 'PATCH' - ) - } else if (params.applicationType === 'simple') { - return APICore.formDataRequest( - `${process.env.API_HOST}:${process.env.API_PORT}/api/applications/${params.applicationId}/services`, - requestBody, - convert, - 'PATCH' // Only modification is available for simple app - ) - } - throw new RangeError(`You specified wrong parameter type ${params.applicationType}`) + const convert = (response) => response.status; + + return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/service_routing`, requestBody, convert, 'PATCH') } -export interface SaveModelParam { - id?: string +export interface UploadModelParam { + projectId: number applicationId: string description: string + file: File } -export async function saveModel(params: SaveModelParam): Promise { - const requestBody = Object.keys(params) - .map((value) => ({ [snakelize(value)]: params[value] })) - .reduce((l, r) => Object.assign(l, r), {}) - const convert = (result) => result.status as boolean - const idUrlString = params.id ? `/${params.id}` : '' +export async function uploadModel(params: UploadModelParam) { + const requestBody = { + ...params + }; - return APICore.formDataRequest( - `${process.env.API_HOST}:${process.env.API_PORT}/api/applications/${params.applicationId}/models${idUrlString}`, - requestBody, - convert, - 'PATCH' - ) + const convert = (response) => response.status; + + return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/models`, requestBody, convert, 'POST') } -export interface UploadModelParam { +export interface UpdateModelParam { + id?: number + projectId: number applicationId: string - name: string description: string - file: File } -export async function uploadModel(param: UploadModelParam) { +export async function updateModel(params: UpdateModelParam): Promise { const requestBody = { - ...param - } + ...params + }; - return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/applications/${param.applicationId}/models`, requestBody) + const convert = (response) => response.status; + + return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/models/${params.id}`, requestBody, convert, 'PATCH') } // GET APIs -export function fetchAllKubernetesHosts() { - const convert = - (results): KubernetesHost => results.map((result) => ({ - ...convertKeys(result, camelize), - id: result.kubernetes_id, - registeredDate: new Date(result.register_date * 1000), - configFile: result.config_path, - config_path: {} = {}, - register_date: {} = {} - })) - return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/kubernetes/`, convert) -} - -export function fetchAllApplications() { +export class Project { + constructor( + public name: string, + public id: string, + public description: string = '', + public date: Date = null + ) { } +} +export function fetchAllProjects(): Promise { const convert = (results) => results.map( - (variable): Application => { + (result): Project => { return { - id: variable.application_id, - name: variable.application_name, - description: variable.description, - date: new Date(variable.register_date * 1000), - kubernetesId: variable.kubernetes_id + ...result, + id: result.project_id, + name: result.display_name, + date: new Date(result.register_date * 1000) } } - ) - - return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/applications/`, convert) + ); + return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects`, convert) } - -interface FetchApplicationByIdParam { - id: string +interface FetchProjectByIdParam { + id: number } -export async function fetchApplicationById(params: FetchApplicationByIdParam): Promise { +export async function fetchProjectById(params: FetchProjectByIdParam): Promise { const convert = (result) => ( { ...result, - name: result.application_name, - id: result.application_id, - date: new Date(result.register_date * 1000), - kubernetesId: result.kubernetes_id + name: result.display_name, + id: result.project_id, + date: new Date(result.register_date * 1000) } - ) - return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/applications/${params.id}`, convert) + ); + return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.id}`, convert) } -export async function fetchKubernetesHostById(params: any): Promise { +export class DataServer { + constructor( + public projectId: number, + public mode: string | boolean, + public cephAccessKey: string = null, + public cephSecretKey: string = null, + public cephHost: string = null, + public cephPort: number = null, + public cephIsSecure: boolean = null, + public cephBucketName: string = null, + public awsAccessKey: string = null, + public awsSecretKey: string = null, + public awsBucketName: string = null, + public date: Date = null + ) { } +} +interface FetchDataServerByIdParam { + id: number +} +export async function fetchDataServer(params: FetchDataServerByIdParam): Promise { const convert = (result) => ( { ...convertKeys(result, camelize), - id: result.kubernetes_id, - registeredDate: new Date(result.register_date * 1000), - configFile: result.config_path, - config_path: {} = {}, - register_date: {} = {} + mode: apiConvertDataServerMode(result.data_server_mode), + date: new Date(result.register_date * 1000) } - ) - return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/kubernetes/${params.id}`, convert) + ); + return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.id}/data_servers`, convert) } -export interface FetchModelsParam { - id?: string - applicationId: string +export class Kubernetes { + constructor( + public name: string, + public id: number, + public projectId: number, + public description: string = '', + public configPath: string, + public exposedHost: string, + public exposedPort: number, + public date: Date = null + ) { } } -export async function fetchAllModels(params: FetchModelsParam): Promise { +interface FetchKubernetesById { + id?: number + projectId: number + +} +export async function fetchAllKubernetes(params: FetchKubernetesById): Promise { const convert = - (results) => results.map((variable): Model => { - return { - name: variable.description, - id: variable.model_id, - registeredDate: new Date(variable.register_date * 1000), + (results) => + results.map( + (result): Kubernetes => { + return { + id: result.kubernetes_id, + name: result.display_name, + projectId: result.project_id, + description: result.description, + configPath: result.config_path, + exposedHost: result.exposed_host, + exposedPort: result.exposed_port, + date: new Date(result.register_date * 1000) + } + } + ); + return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.id}/kubernetes`, convert) +} +export async function fetchKubernetesById(params: FetchKubernetesById): Promise { + const convert = + (result) => ( + { + ...convertKeys(result, camelize), + id: result.kubernetes_id, + name: result.display_name, + date: new Date(result.register_date * 1000) } - }) - return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/applications/${params.applicationId}/models`, convert) + ); + return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.id}/kubernetes/${params.id}`, convert) } -export interface FetchServicesParam { - kubernetes?: boolean +export class Application { + constructor( + public name: string, + public id: string, + public description: string = '', + public date: Date = null, + public projectId: number + ) { } +} +interface FetchApplicationByIdParam { id?: string - applicationId: string - kubernetesId?: string + projectId: number } -export async function fetchAllServices(params: FetchServicesParam) { +export function fetchAllApplications(params: FetchApplicationByIdParam): Promise { const convert = - (results) => results.map((variable): Service => { - return { - id: variable.service_id, - name: variable.display_name, - serviceLevel: variable.service_level, - modelId: variable.model_id, - host: variable.host, - description: variable.description, - } - }) - return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/applications/${params.applicationId}/services`, convert) + (results) => + results.map( + (result): Application => { + return { + id: result.application_id, + name: result.application_name, + description: result.description, + date: new Date(result.register_date * 1000), + projectId: result.project_id + } + } + ); + return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications`, convert) } - -export async function fetchServiceById(params: FetchServicesParam) { - const convertDetail = +export async function fetchApplicationById(params: FetchApplicationByIdParam): Promise { + const convert = (result) => ( { - id: result.service_id, - name: result.service_name, - ...convertKeys(result, camelize) + ...convertKeys(result, camelize), + name: result.application_name, + id: result.application_id, + date: new Date(result.register_date * 1000) } - ) - - if (params.kubernetes) { - return APICore.getRequest( - `${process.env.API_HOST}:${process.env.API_PORT}/api/kubernetes/${params.kubernetesId}/applications/${params.applicationId}/services/${params.id}`, - convertDetail) - } else { - return APICore.getRequest( - `${process.env.API_HOST}:${process.env.API_PORT}/api/applications/${params.applicationId}/services/${params.id}`, - convertDetail - ) - } + ); + return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.id}`, convert) } -export async function fetchModelById(params: FetchModelsParam) { - const convertDetail = +export class Model { + constructor( + public id: number, + public description: string = '', + public date: Date = null + ) { } +} +export interface FetchModelByIdParam { + id?: string + projectId: number + applicationId: string +} +export async function fetchAllModels(params: FetchModelByIdParam): Promise { + const convert = + (results) => results.map((result): Model => { + return { + description: result.description, + id: result.model_id, + date: new Date(result.register_date * 1000) + } + }); + return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/models`, convert) +} +export async function fetchModelById(params: FetchModelByIdParam): Promise { + const convert = (result) => ( { id: result.model_id, description: result.description, + date: new Date(result.register_date * 1000), ...convertKeys(result, camelize) } - ) - + ); return APICore.getRequest( - `${process.env.API_HOST}:${process.env.API_PORT}/api/applications/${params.applicationId}/models/${params.id}`, - convertDetail + `${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/models/${params.id}`, + convert ) } -export async function fetchServiceDescriptions(params: FetchServicesParam): Promise { +export class Service { + constructor( + public id: string, + public name: string, + public description: string = '', + public serviceLevel: string, + public version: string, + public modelId: number, + public insecureHost: string, + public insecurePort: number, + public date: Date = null, + public applicationId: string, + public replicasDefault?: number, + public replicasMinimum?: number, + public replicasMaximum?: number, + public autoscaleCpuThreshold?: number, + public policyMaxSurge?: number, + public policyMaxUnavailable?: number, + public policyWaitSeconds?: number, + public containerImage?: string, + public serviceGitUrl?: string, + public serviceGitBranch?: string, + public serviceBootScript?: string, + public resourceRequestCpu?: number, + public resourceRequestMemory?: string, + public resourceLimitCpu?: number, + public resourceLimitMemory?: string, + public debugMode?: boolean + ) { } +} +export interface FetchServiceParam { + projectId: number + applicationId: string +} +export async function fetchAllServices(params: FetchServiceParam): Promise { + const convert = + (results) => results.map((result): Service => { + return { + id: result.service_id, + name: result.display_name, + serviceLevel: result.service_level, + version: result.version, + modelId: result.model_id, + insecureHost: result.insecure_host, + insecurePort: result.insecure_port, + description: result.description, + date: new Date(result.register_date * 1000), + applicationId: result.application_id + } + }); + return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/services`, convert) +} +export interface FetchServiceByIdParam { + isKubernetes: boolean + isOnlyDescription: boolean + id: string + projectId: number + applicationId: string +} +export async function fetchServiceById(params: FetchServiceByIdParam): Promise { const convert = (result) => ( { id: result.service_id, - displayName: result.display_name, - description: result.description + name: result.display_name, + date: new Date(result.register_date * 1000), + ...convertKeys(result, camelize) } - ) + ); - if (params.id) { + if (params.isKubernetes && !params.isOnlyDescription) { return APICore.getRequest( - `${process.env.API_HOST}:${process.env.API_PORT}/api/applications/${params.applicationId}/services/${params.id}`, - (result) => [convert(result)] + `${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/service_deployment/${params.id}`, + convert ) } else { - // Fetch all descriptions return APICore.getRequest( - `${process.env.API_HOST}:${process.env.API_PORT}/api/applications/${params.applicationId}/services`, + `${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/services/${params.id}`, convert ) } } +export class ServiceRouteWeight { + constructor( + public displayName: string, + public serviceId: string, + public serviceWeight: number + ) { } +} +export class ServiceRoute { + constructor( + public applicationName: string, + public serviceLevel: string, + public serviceWeights: ServiceRouteWeight[] + ) { } +} +export interface FetchServiceRouteParam { + projectId: number + applicationId: string + serviceLevel: string +} +export async function fetchServiceRoute(params: FetchServiceRouteParam): Promise { + const convert = + (result) => ( + { + applicationName: result.application_name, + serviceLevel: result.service_level, + serviceWeights: result.map((weight): ServiceRouteWeight => { + return { + displayName: weight.display_name, + serviceId: weight.service_id, + serviceWeight: weight.service_weight + } + }) + } + ); + return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/service_routing?service_level=${params.serviceLevel}`, convert) +} + // PUT APIs export interface SwitchModelParam { - applicationId: string, - serviceId: string, - modelId: string, - kubernetesId?: number + projectId: number + applicationId: string + serviceId: string + modelId: string } -export async function switchModel(param: SwitchModelParam) { +export async function switchModel(params: SwitchModelParam) { const requestBody = { - model_id: param.modelId - } - const convert = (result) => result.status + model_id: params.modelId + }; + const convert = (result) => result.status; return APICore.putJsonRequest( - `${process.env.API_HOST}:${process.env.API_PORT}/api/applications/${param.applicationId}/services/${param.serviceId}`, + `${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/services/${params.serviceId}`, requestBody, convert ) } - export async function switchModels(params: SwitchModelParam[]) { const requestOptions = params.map( (param) => ( @@ -467,90 +595,114 @@ export async function switchModels(params: SwitchModelParam[]) { params: { model_id: param.modelId } } ) - ) - + ); const entryPoints = params.map( (param) => ( - param.kubernetesId ? - `${process.env.API_HOST}:${process.env.API_PORT}/api/kubernetes/${param.kubernetesId}/applications/${param.applicationId}/services/${param.serviceId}` - : `${process.env.API_HOST}:${process.env.API_PORT}/api/applications/${param.applicationId}/services/${param.serviceId}` + `${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${param.projectId}/applications/${param.applicationId}/services/${param.serviceId}` ) - ) - const convert = (result) => result.status + ); + const convert = (result) => result.status; return APICore.rawMultiRequest(entryPoints, convert, requestOptions) } export interface SynKubernetesStatusParam { - kubernetesId?: string + projectId: number applicationId?: string + serviceId?: string } export async function syncKubernetesStatus(params: SynKubernetesStatusParam): Promise { const options = { method: 'PUT', body: new FormData() - } + }; const idUrlString = - params.kubernetesId && params.applicationId - ? `${params.kubernetesId}/applications/${params.applicationId}/services` - : '' - const convert = (result) => result.status - - return APICore.rawRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/kubernetes/${idUrlString}`, convert, options) + params.applicationId && params.serviceId + ? `/applications/${params.applicationId}/service_deployment/${params.serviceId}` + : ''; + const convert = (result) => result.status; + return APICore.rawRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}${idUrlString}`, convert, options) } // DELETE APIs -export async function deleteKubernetesHost(params: any): Promise { - const convert = (result) => result.status +export interface IdParam { + projectId: number + kubernetesId?: number + applicationId?: string + serviceId?: string + modelId?: number +} +export async function deleteKubernetes(params: IdParam): Promise { + const convert = (result) => result.status; + + return APICore.deleteRequest( + `${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/kubernetes/${params.kubernetesId}`, + convert + ) +} + +export async function deleteDataServer(params: IdParam): Promise { + const convert = (result) => result.status; return APICore.deleteRequest( - `${process.env.API_HOST}:${process.env.API_PORT}/api/kubernetes/${params.kubernetesId}`, + `${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/data_servers`, convert ) } -export async function deleteKubernetesServices(params: any): Promise>> { - const convert = (result) => result.status +export async function deleteApplication(params: IdParam): Promise { + const convert = (result) => result.status; + return APICore.deleteRequest( + `${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}`, + convert + ) +} + +export async function deleteServices(params: IdParam[]): Promise>> { + const convert = (result) => result.status; const entryPoints = params.map( (param) => - `${process.env.API_HOST}:${process.env.API_PORT}/api/applications/${param.applicationId}/services/${param.serviceId}` - ) + `${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${param.projectId}/applications/${param.applicationId}/services/${param.serviceId}` + ); const requestList = params.map( (param) => ({ options: { method: 'DELETE' } }) - ) + ); return APICore.rawMultiRequest(entryPoints, convert, requestList) } -export async function deleteKubernetesModels(params: any): Promise>> { - const convert = (result) => result.status - +export async function deleteModels(params: IdParam[]): Promise>> { + const convert = (result) => result.status; const entryPoints = params.map( (param) => - `${process.env.API_HOST}:${process.env.API_PORT}/api/applications/${param.applicationId}/models/${param.modelId}` - ) + `${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${param.projectId}/applications/${param.applicationId}/models/${param.modelId}` + ); const requestList = params.map( (param) => ({ options: { method: 'DELETE' } }) - ) + ); return APICore.rawMultiRequest(entryPoints, convert, requestList) } // Login API +export class AuthToken { + constructor( + public jwt: string = '' + ) { } +} export interface LoginParam { username: string password: string } - export async function login(param: LoginParam) { const convert = (result): AuthToken => { return { jwt: result.jwt } - } + }; return APICore.postJsonRequest( `${process.env.API_HOST}:${process.env.API_PORT}/api/login`, param, @@ -564,70 +716,147 @@ export async function settings(): Promise { ) } +export class UserProjectRole { + constructor( + public projectId: number, + public role: string = '' + ) { } +} +export class UserApplicationRole { + constructor( + public applicationId: string, + public role: string = '' + ) { } +} +export class UserInfo { + constructor( + public user: { + userUid: string + userName: string + }, + public projectRoles: UserProjectRole[], + public applicationRoles: UserApplicationRole[], + ) { } +} export async function userInfo(): Promise { const convert = (result): UserInfo => { - const roles: UserRole[] = result.applications.map((e) => new UserRole(e.application_id, e.role)) + const projectRoles: UserProjectRole[] = result.projects.map( + (e) => new UserProjectRole(e.project_id, e.project_role)); + const applicationRoles: UserApplicationRole[] = result.applications.map( + (e) => new UserApplicationRole(e.application_id, e.application_role)); return new UserInfo({ userUid: result.user.auth_id, userName: result.user.user_name, - }, roles) - } + }, + projectRoles, + applicationRoles) + }; return APICore.getRequest( `${process.env.API_HOST}:${process.env.API_PORT}/api/credential`, convert ) } - export async function fetchAllUsers(): Promise { const convert = (results) => { return results.map((result) => { return new UserInfo({ userUid: result.auth_id, userName: result.user_name - }, []) + }, [], []) }) - } + }; return APICore.getRequest( - `${process.env.API_HOST}:${process.env.API_PORT}/api/applications/users`, + `${process.env.API_HOST}:${process.env.API_PORT}/api/users`, convert ) } -export async function fetchAccessControlList(param: { applicationId: string }): Promise { - const convert = (results: any): AccessControlList[] => { - return results.map((result: any) => new AccessControlList( +export class ProjectAccessControlList { + constructor( + public userUid: string, + public userName: string, + public role: string | boolean, + ) { } +} +export interface AccessControlParam { + projectId: number + applicationId?: string + method?: string + uid?: string + role?: string +} +export async function fetchProjectAccessControlList(params: AccessControlParam): Promise { + const convert = (results: any): ProjectAccessControlList[] => { + return results.map((result: any) => new ProjectAccessControlList( result.user.auth_id, result.user.user_name, - apiConvert(result.role) + apiConvertProjectRole(result.project_role) )) - } + }; return APICore.getRequest( - `${process.env.API_HOST}:${process.env.API_PORT}/api/applications/${param.applicationId}/acl`, + `${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/acl`, convert ) } - -export async function saveAccessControl(params): Promise { - const convert = (result) => result.status +export async function saveProjectAccessControl(params: AccessControlParam): Promise { + const convert = (result) => result.status; return APICore.formDataRequest( - `${process.env.API_HOST}:${process.env.API_PORT}/api/applications/${params.applicationId}/acl`, + `${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/acl`, { ...params }, convert, - params.mode === 'add' ? 'POST' : 'PATCH' + params.method === 'post' ? 'POST' : 'PATCH' + ) +} +export async function deleteProjectAccessControl(params: AccessControlParam): Promise { + const convert = (result) => result.status; + return APICore.deleteRequest( + `${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/acl/users/${params.uid}`, + convert ) } -export async function deleteAccessControl(params): Promise { - const convert = (result) => result.status - const body = new FormData() - body.append('uid', params.userUid) - const options = { - method: 'DELETE', - body - } - return APICore.rawRequest( - `${process.env.API_HOST}:${process.env.API_PORT}/api/applications/${params.applicationId}/acl`, +export class ApplicationAccessControlList { + constructor( + public userUid: string, + public userName: string, + public role: string, + ) { } +} +export async function fetchApplicationAccessControlList(params: AccessControlParam): Promise { + const convert = (results: any): ApplicationAccessControlList[] => { + return results.map((result: any) => new ApplicationAccessControlList( + result.user.auth_id, + result.user.user_name, + apiConvertApplicationRole(result.role) + )) + }; + return APICore.getRequest( + `${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/acl`, + convert + ) +} + +export async function saveApplicationAccessControl(params: AccessControlParam): Promise { + const convert = (result) => result.status; + return APICore.formDataRequest( + `${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/acl`, + { ...params }, convert, - options + params.method === 'post' ? 'POST' : 'PATCH' ) } + +export async function deleteApplicationAccessControl(params: AccessControlParam): Promise { + const convert = (result) => result.status; + return APICore.deleteRequest( + `${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/acl/users/${params.uid}`, + convert + ) +} + +export class ApiStatusResponse { + constructor( + public status: string = '', + public message: string = '' + ) { } +} From afb817d388a70556c50dec1c0be57bb40b39ce62 Mon Sep 17 00:00:00 2001 From: Hattori Keigo Date: Fri, 15 Mar 2019 08:43:42 +0900 Subject: [PATCH 04/92] Update `/actions` --- frontend/src/actions/index.tsx | 313 +++++++++++++++++++++------------ frontend/src/apis/index.tsx | 20 +-- 2 files changed, 206 insertions(+), 127 deletions(-) diff --git a/frontend/src/actions/index.tsx b/frontend/src/actions/index.tsx index 0f5e30d..c30bad4 100644 --- a/frontend/src/actions/index.tsx +++ b/frontend/src/actions/index.tsx @@ -1,21 +1,6 @@ import { Action } from 'redux' import { APIError, APIErrorType } from '@src/apis/Core' -import { - addApplication, saveService, saveModel, saveKubernetesHost, - fetchAllApplications, fetchAllModels, fetchAllServices, - fetchApplicationById, fetchKubernetesHostById, - fetchServiceById, fetchModelById, FetchServicesParam, - FetchModelsParam, fetchServiceDescriptions, - fetchAllKubernetesHosts, - uploadModel, switchModels, syncKubernetesStatus, - deleteKubernetesHost, deleteKubernetesServices, - deleteKubernetesModels, - settings, login, userInfo, fetchAllUsers, - fetchAccessControlList, saveAccessControl, deleteAccessControl, - SaveServiceParam, SaveModelParam, SwitchModelParam, - ModelResponse, KubernetesHost, LoginParam, AuthToken, UserInfo, AccessControlList -} from '@src/apis' -import { Application, Model, Service } from '@src/apis' +import * as Apis from '@src/apis' export type Actions = APIActions | NotificationActions export type APIActions

= APIRequestStartAction

| APIRequestSuccessAction | APIRequestFailueAction @@ -89,161 +74,267 @@ function asyncAPIRequestDispatcherCreator

( } } -export const uploadModelActionCreators = new APIRequestActionCreators<{name: string, description: string}, ModelResponse>('UPLOAD_MODEL') -export const uploadModelDispatcher = asyncAPIRequestDispatcherCreator<{name: string, description: string}, ModelResponse>( +export const saveProjectActionCreators = + new APIRequestActionCreators('SAVE_PROJECT') +export const saveProjectDispatcher = asyncAPIRequestDispatcherCreator( + saveProjectActionCreators, + Apis.saveProject +) + +export const saveDataServerActionCreators = + new APIRequestActionCreators('SAVE_DATASERVER') +export const saveDataServerDispatcher = asyncAPIRequestDispatcherCreator( + saveDataServerActionCreators, + Apis.saveDataServer +) + +export const saveKubernetesActionCreators = + new APIRequestActionCreators('SAVE_KUBERNETES') +export const saveKubernetesDispatcher = asyncAPIRequestDispatcherCreator( + saveKubernetesActionCreators, + Apis.saveKubernetes +) + +export const saveApplicationActionCreators = + new APIRequestActionCreators('SAVE_APPLICATION') +export const saveApplicationDispatcher = asyncAPIRequestDispatcherCreator( + saveApplicationActionCreators, + Apis.saveApplication +) + +export const updateServiceActionCreators = + new APIRequestActionCreators('UPDATE_SERVICE') +export const updateServiceDispatcher = asyncAPIRequestDispatcherCreator( + updateServiceActionCreators, + Apis.updateService +) + +export const saveServiceDeploymentActionCreators = + new APIRequestActionCreators('SAVE_SERVICE_DEPLOYMENT') +export const saveServiceDeploymentDispatcher = asyncAPIRequestDispatcherCreator( + saveServiceDeploymentActionCreators, + Apis.saveServiceDeployment +) + +export const updateServiceRoutingActionCreators = + new APIRequestActionCreators('UPDATE_SERVICE_ROUTING') +export const updateServiceRoutingDispatcher = asyncAPIRequestDispatcherCreator( + updateServiceRoutingActionCreators, + Apis.updateServiceRouting +) + +export const uploadModelActionCreators = + new APIRequestActionCreators('UPLOAD_MODEL') +export const uploadModelDispatcher = asyncAPIRequestDispatcherCreator( uploadModelActionCreators, - uploadModel + Apis.uploadModel +) + +export const updateModelActionCreators = + new APIRequestActionCreators('UPDATE_MODEL') +export const updateModelDispatcher = asyncAPIRequestDispatcherCreator( + updateModelActionCreators, + Apis.updateModel ) -export const saveKubernetesHostActionCreators = - new APIRequestActionCreators<{name: string, description: string}, boolean>('SAVE_CONNECTION') -export const saveKubernetesHostDispatcher = asyncAPIRequestDispatcherCreator( - saveKubernetesHostActionCreators, - saveKubernetesHost +export const fetchAllProjectsActionCreators = + new APIRequestActionCreators<{}, Apis.Project[]>('FETCH_ALL_PROJECTS') +export const fetchAllProjectsDispatcher = asyncAPIRequestDispatcherCreator<{}, Apis.Project[]>( + fetchAllProjectsActionCreators, + Apis.fetchAllProjects ) -export const addApplicationActionCreators = new APIRequestActionCreators<{name: string, description: string}, boolean>('ADD_CLIENT') -export const addApplicationDispatcher = asyncAPIRequestDispatcherCreator<{name: string, description: string}, boolean>( - addApplicationActionCreators, - addApplication +export const fetchProjectByIdActionCreators = + new APIRequestActionCreators('FETCH_PROJECT_BY_ID') +export const fetchProjectByIdDispatcher = asyncAPIRequestDispatcherCreator( + fetchProjectByIdActionCreators, + Apis.fetchProjectById ) -export const saveServiceActionCreators = new APIRequestActionCreators('SAVE_SERVICE') -export const saveServiceDispatcher = asyncAPIRequestDispatcherCreator( - saveServiceActionCreators, - saveService +export const fetchDataServerActionCreators = + new APIRequestActionCreators('FETCH_DATA_SERVER_BY_ID') +export const fetchDataServerDispatcher = asyncAPIRequestDispatcherCreator( + fetchDataServerActionCreators, + Apis.fetchDataServer ) -export const saveModelActionCreators = new APIRequestActionCreators('SAVE_MODEL') -export const saveModelDispatcher = asyncAPIRequestDispatcherCreator( - saveModelActionCreators, - saveModel +export const fetchAllKubernetesActionCreators = + new APIRequestActionCreators('FETCH_ALL_KUBERNETES') +export const fetchAllKubernetesDispatcher = asyncAPIRequestDispatcherCreator( + fetchAllKubernetesActionCreators, + Apis.fetchAllKubernetes ) -export const fetchAllKubernetesHostsActionCreators = new APIRequestActionCreators<{}, KubernetesHost[]>('FETCH_ALL_CONNECTIONS') -export const fetchAllKubernetesHostsDispatcher = asyncAPIRequestDispatcherCreator<{}, KubernetesHost[]>( - fetchAllKubernetesHostsActionCreators, - fetchAllKubernetesHosts +export const fetchKubernetesByIdActionCreators = + new APIRequestActionCreators('FETCH_KUBERNETES_BY_ID') +export const fetchKubernetesByIdDispatcher = asyncAPIRequestDispatcherCreator( + fetchKubernetesByIdActionCreators, + Apis.fetchKubernetesById ) -export const fetchAllApplicationsActionCreators = new APIRequestActionCreators<{}, Application[]>('FETCH_ALL_CLIENTS') -export const fetchAllApplicationsDispatcher = asyncAPIRequestDispatcherCreator<{}, Application[]>( +export const fetchAllApplicationsActionCreators = + new APIRequestActionCreators('FETCH_ALL_APPLICATIONS') +export const fetchAllApplicationsDispatcher = asyncAPIRequestDispatcherCreator( fetchAllApplicationsActionCreators, - fetchAllApplications + Apis.fetchAllApplications ) -export const fetchApplicationByIdActionCreators = new APIRequestActionCreators<{id: string}, Application>('FETCH_CLIENT_BY_ID') -export const fetchApplicationByIdDispatcher = asyncAPIRequestDispatcherCreator<{id: string}, Application>( +export const fetchApplicationByIdActionCreators = + new APIRequestActionCreators('FETCH_APPLICATION_BY_ID') +export const fetchApplicationByIdDispatcher = asyncAPIRequestDispatcherCreator( fetchApplicationByIdActionCreators, - fetchApplicationById + Apis.fetchApplicationById ) -export const fetchKubernetesHostByIdActionCreators = new APIRequestActionCreators<{id: string}, any>('FETCH_KUBERNETES_CONNECTION_BY_ID') -export const fetchKubernetesHostByIdDispatcher = asyncAPIRequestDispatcherCreator<{id: string}, any>( - fetchKubernetesHostByIdActionCreators, - fetchKubernetesHostById +export const fetchAllModelsActionCreators = + new APIRequestActionCreators('FETCH_ALL_MODELS') +export const fetchAllModelsDispatcher = asyncAPIRequestDispatcherCreator( + fetchAllModelsActionCreators, + Apis.fetchAllModels ) -export const fetchAllModelsActionCreators = new APIRequestActionCreators<{applicationId: string}, Model[]>('FETCH_ALL_MODELS') -export const fetchAllModelsDispatcher = asyncAPIRequestDispatcherCreator<{applicationId: string}, Model[]>( - fetchAllModelsActionCreators, - fetchAllModels +export const fetchModelByIdActionCreators = + new APIRequestActionCreators('FETCH_MODEL_BY_ID') +export const fetchModelByIdDispatcher = asyncAPIRequestDispatcherCreator( + fetchModelByIdActionCreators, + Apis.fetchModelById ) -export const fetchAllServicesActionCreators = new APIRequestActionCreators('FETCH_ALL_SERVICES') -export const fetchAllServicesDispatcher = asyncAPIRequestDispatcherCreator( +export const fetchAllServicesActionCreators = + new APIRequestActionCreators('FETCH_ALL_SERVICES') +export const fetchAllServicesDispatcher = asyncAPIRequestDispatcherCreator( fetchAllServicesActionCreators, - fetchAllServices + Apis.fetchAllServices ) -export const fetchServiceByIdActionCreators = new APIRequestActionCreators('FETCH_SERVICE') -export const fetchServiceByIdDispatcher = asyncAPIRequestDispatcherCreator( +export const fetchServiceByIdActionCreators = + new APIRequestActionCreators('FETCH_SERVICE_BY_ID') +export const fetchServiceByIdDispatcher = asyncAPIRequestDispatcherCreator( fetchServiceByIdActionCreators, - fetchServiceById + Apis.fetchServiceById ) -export const fetchModelByIdActionCreators = new APIRequestActionCreators('FETCH_MODEL') -export const fetchModelByIdDispatcher = asyncAPIRequestDispatcherCreator( - fetchModelByIdActionCreators, - fetchModelById +export const fetchServiceRouteActionCreators = + new APIRequestActionCreators('FETCH_SERVICE_ROUTE_BY_ID') +export const fetchServiceRouteDispatcher = asyncAPIRequestDispatcherCreator( + fetchServiceRouteActionCreators, + Apis.fetchServiceRoute ) -export const fetchServiceDescriptionsActionCreators = new APIRequestActionCreators('FETCH_SERVICE_DESCRIPTIONS') -export const fetchServiceDescriptionsDispatcher = asyncAPIRequestDispatcherCreator( - fetchServiceDescriptionsActionCreators, - fetchServiceDescriptions +export const switchModelsActionCreators = + new APIRequestActionCreators('SWITCH_MODELS') +export const switchModelsDispatcher = asyncAPIRequestDispatcherCreator( + switchModelsActionCreators, + Apis.switchModels ) -export const switchModelsActionCreators = new APIRequestActionCreators('SWITCH_MODELS') -export const switchModelsDispatcher = asyncAPIRequestDispatcherCreator( - switchModelsActionCreators, - switchModels +export const syncKubernetesStatusActionCreators = + new APIRequestActionCreators('SYNC_KUBERNETES_STATUS') +export const syncKubernetesStatusDispatcher = asyncAPIRequestDispatcherCreator( + syncKubernetesStatusActionCreators, + Apis.syncKubernetesStatus ) -export const deleteKubernetesHostActionCreators = new APIRequestActionCreators<{id: number}, boolean>('DELETE_KUBERNETES_CONNECTION') -export const deleteKubernetesHostDispatcher = asyncAPIRequestDispatcherCreator<{id: number}, boolean>( - deleteKubernetesHostActionCreators, - deleteKubernetesHost +export const deleteKubernetesActionCreators = + new APIRequestActionCreators('DELETE_KUBERNETES') +export const deleteKubernetesDispatcher = asyncAPIRequestDispatcherCreator( + deleteKubernetesActionCreators, + Apis.deleteKubernetes ) -export const deleteKubernetesServicesActionCreators = new APIRequestActionCreators('DELETE_KUBERNETES_SERVICE') -export const deleteKubernetesServicesDispatcher = asyncAPIRequestDispatcherCreator( - deleteKubernetesServicesActionCreators, - deleteKubernetesServices +export const deleteDataServerActionCreators = + new APIRequestActionCreators('DELETE_DATA_SERVER') +export const deleteDataServerDispatcher = asyncAPIRequestDispatcherCreator( + deleteDataServerActionCreators, + Apis.deleteDataServer ) -export const deleteKubernetesModelsActionCreators = new APIRequestActionCreators('DELETE_KUBERNETES_MODEL') -export const deleteKubernetesModelsDispatcher = asyncAPIRequestDispatcherCreator( - deleteKubernetesModelsActionCreators, - deleteKubernetesModels +export const deleteApplicationActionCreators = + new APIRequestActionCreators('DELETE_APPLICATION') +export const deleteApplicationDispatcher = asyncAPIRequestDispatcherCreator( + deleteApplicationActionCreators, + Apis.deleteApplication ) -export const syncKubernetesStatusActionCreators = new APIRequestActionCreators('SYNC_KUBERNETES_STATUS') -export const syncKubernetesStatusDispatcher = asyncAPIRequestDispatcherCreator( - syncKubernetesStatusActionCreators, - syncKubernetesStatus +export const deleteServicesActionCreators = + new APIRequestActionCreators('DELETE_SERVICES') +export const deleteServicesDispatcher = asyncAPIRequestDispatcherCreator( + deleteServicesActionCreators, + Apis.deleteServices ) -export const settingsActionCreators = new APIRequestActionCreators<{}, any>('SETTINGS') +export const deleteModelsActionCreators = + new APIRequestActionCreators('DELETE_MODELS') +export const deleteModelsDispatcher = asyncAPIRequestDispatcherCreator( + deleteModelsActionCreators, + Apis.deleteModels +) + +// Login +export const settingsActionCreators = + new APIRequestActionCreators<{}, any>('SETTINGS') export const settingsDispatcher = asyncAPIRequestDispatcherCreator<{}, any>( settingsActionCreators, - settings + Apis.settings ) -export const loginActionCreators = new APIRequestActionCreators('LOGIN') -export const loginDispatcher = asyncAPIRequestDispatcherCreator( +export const loginActionCreators = + new APIRequestActionCreators('LOGIN') +export const loginDispatcher = asyncAPIRequestDispatcherCreator( loginActionCreators, - login + Apis.login ) -export const userInfoActionCreators = new APIRequestActionCreators<{}, UserInfo>('USER_INFO') -export const userInfoDispatcher = asyncAPIRequestDispatcherCreator<{}, UserInfo>( +export const userInfoActionCreators = + new APIRequestActionCreators<{}, Apis.UserInfo>('USER_INFO') +export const userInfoDispatcher = asyncAPIRequestDispatcherCreator<{}, Apis.UserInfo>( userInfoActionCreators, - userInfo + Apis.userInfo ) -export const fetchAllUsersActionCreators = new APIRequestActionCreators<{}, UserInfo[]>('ALL_USERS') -export const fetchAllUsersDispatcher = asyncAPIRequestDispatcherCreator<{}, UserInfo[]>( +export const fetchAllUsersActionCreators = + new APIRequestActionCreators<{}, Apis.UserInfo[]>('ALL_USERS') +export const fetchAllUsersDispatcher = asyncAPIRequestDispatcherCreator<{}, Apis.UserInfo[]>( fetchAllUsersActionCreators, - fetchAllUsers + Apis.fetchAllUsers +) + +export const fetchProjectAccessControlListActionCreators = + new APIRequestActionCreators('FETCH_PROJECT_ACCESS_CONTROL_LIST') +export const fetchProjectAccessControlListDispatcher = asyncAPIRequestDispatcherCreator( + fetchProjectAccessControlListActionCreators, + Apis.fetchProjectAccessControlList +) + +export const saveProjectAccessControlActionCreators = new APIRequestActionCreators('SAVE_PROJECT_ACCESS_CONTROL') +export const saveProjectAccessControlDispatcher = asyncAPIRequestDispatcherCreator( + saveProjectAccessControlActionCreators, + Apis.saveProjectAccessControl +) + +export const deleteProjectAccessControlActionCreators = new APIRequestActionCreators('DELETE_PROJECT_ACCESS_CONTROL') +export const deleteProjectAccessControlDispatcher = asyncAPIRequestDispatcherCreator( + deleteProjectAccessControlActionCreators, + Apis.deleteProjectAccessControl ) -export const fetchAccessControlListActionCreators = new APIRequestActionCreators('FETCH_ACCESS_CONTROL_LIST') -export const fetchAccessControlListDispatcher = asyncAPIRequestDispatcherCreator( - fetchAccessControlListActionCreators, - fetchAccessControlList +export const fetchApplicationAccessControlListActionCreators = + new APIRequestActionCreators('FETCH_APPLICATION_ACCESS_CONTROL_LIST') +export const fetchApplicationAccessControlListDispatcher = asyncAPIRequestDispatcherCreator( + fetchApplicationAccessControlListActionCreators, + Apis.fetchApplicationAccessControlList ) -export const saveAccessControlActionCreators = new APIRequestActionCreators<{}, boolean>('SAVE_ACCESS_CONTROL') -export const saveAccessControlDispatcher = asyncAPIRequestDispatcherCreator<{}, boolean>( - saveAccessControlActionCreators, - saveAccessControl +export const saveApplicationAccessControlActionCreators = new APIRequestActionCreators('SAVE_APPLICATION_ACCESS_CONTROL') +export const saveApplicationAccessControlDispatcher = asyncAPIRequestDispatcherCreator( + saveApplicationAccessControlActionCreators, + Apis.saveApplicationAccessControl ) -export const deleteAccessControlActionCreators = new APIRequestActionCreators<{}, boolean>('DELETE_ACCESS_CONTROL') -export const deleteAccessControlDispatcher = asyncAPIRequestDispatcherCreator<{}, boolean>( - deleteAccessControlActionCreators, - deleteAccessControl +export const deleteApplicationAccessControlActionCreators = new APIRequestActionCreators('DELETE_APPLICATION_ACCESS_CONTROL') +export const deleteApplicationAccessControlDispatcher = asyncAPIRequestDispatcherCreator( + deleteApplicationAccessControlActionCreators, + Apis.deleteApplicationAccessControl ) // Notification actions diff --git a/frontend/src/apis/index.tsx b/frontend/src/apis/index.tsx index f139eb0..83f20ce 100644 --- a/frontend/src/apis/index.tsx +++ b/frontend/src/apis/index.tsx @@ -269,7 +269,7 @@ export function fetchAllProjects(): Promise { ); return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects`, convert) } -interface FetchProjectByIdParam { +export interface FetchProjectByIdParam { id: number } export async function fetchProjectById(params: FetchProjectByIdParam): Promise { @@ -301,7 +301,7 @@ export class DataServer { public date: Date = null ) { } } -interface FetchDataServerByIdParam { +export interface FetchDataServerByIdParam { id: number } export async function fetchDataServer(params: FetchDataServerByIdParam): Promise { @@ -328,7 +328,7 @@ export class Kubernetes { public date: Date = null ) { } } -interface FetchKubernetesById { +export interface FetchKubernetesById { id?: number projectId: number @@ -374,7 +374,7 @@ export class Application { public projectId: number ) { } } -interface FetchApplicationByIdParam { +export interface FetchApplicationByIdParam { id?: string projectId: number } @@ -573,18 +573,6 @@ export interface SwitchModelParam { serviceId: string modelId: string } -export async function switchModel(params: SwitchModelParam) { - const requestBody = { - model_id: params.modelId - }; - const convert = (result) => result.status; - - return APICore.putJsonRequest( - `${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/services/${params.serviceId}`, - requestBody, - convert - ) -} export async function switchModels(params: SwitchModelParam[]) { const requestOptions = params.map( (param) => ( From 58ee1b7ba4b6286d7eb1e36c9ed16c0a570d3c34 Mon Sep 17 00:00:00 2001 From: Hattori Keigo Date: Fri, 15 Mar 2019 10:20:11 +0900 Subject: [PATCH 05/92] Update `/reducers` --- frontend/src/reducers/index.tsx | 221 +++++++++++++++++--------------- 1 file changed, 121 insertions(+), 100 deletions(-) diff --git a/frontend/src/reducers/index.tsx b/frontend/src/reducers/index.tsx index 1853152..9f0ed9a 100644 --- a/frontend/src/reducers/index.tsx +++ b/frontend/src/reducers/index.tsx @@ -1,70 +1,48 @@ -import { - APIActions, - APIRequestStartAction, APIRequestFailueAction, APIRequestSuccessAction, - APIRequestActionCreators, - uploadModelActionCreators, - saveKubernetesHostActionCreators, - addApplicationActionCreators, - saveServiceActionCreators, - saveModelActionCreators, - fetchAllKubernetesHostsActionCreators, - fetchAllApplicationsActionCreators, - fetchApplicationByIdActionCreators, - fetchAllModelsActionCreators, - fetchAllServicesActionCreators, - switchModelsActionCreators, - fetchKubernetesHostByIdActionCreators, - deleteKubernetesHostActionCreators, - deleteKubernetesServicesActionCreators, - deleteKubernetesModelsActionCreators, - syncKubernetesStatusActionCreators, - fetchServiceByIdActionCreators, - fetchModelByIdActionCreators, - fetchServiceDescriptionsActionCreators, - APIRequestUnauthorized, - loginActionCreators, - userInfoActionCreators, - fetchAccessControlListActionCreators, - saveAccessControlActionCreators, - deleteAccessControlActionCreators, - settingsActionCreators, - fetchAllUsersActionCreators -} from '@src/actions' -import { APIRequest, APIRequestStatusList} from '@src/apis/Core' -import { - Application, Model, Service, SwitchModelParam, - ModelResponse, KubernetesHost, FetchServicesParam, - FetchModelsParam, LoginParam, AuthToken, UserInfo, AccessControlList -} from '@src/apis' +import {APIRequest, APIRequestStatusList} from '@src/apis/Core' +import * as Apis from '@src/apis' +import * as Actions from '@src/actions' export class AppState { constructor( // API states - public uploadModel: APIRequest = { status: APIRequestStatusList.notStarted }, - public switchModels: APIRequest = { status: APIRequestStatusList.notStarted }, - public saveKubernetesHost: APIRequest = { status: APIRequestStatusList.notStarted }, - public addApplication: APIRequest = { status: APIRequestStatusList.notStarted }, - public saveService: APIRequest = { status: APIRequestStatusList.notStarted }, - public saveModel: APIRequest = { status: APIRequestStatusList.notStarted }, - public applications: APIRequest = { status: APIRequestStatusList.notStarted }, - public applicationById: APIRequest = { status: APIRequestStatusList.notStarted }, - public services: APIRequest = { status: APIRequestStatusList.notStarted }, - public serviceDescriptions: APIRequest = { status: APIRequestStatusList.notStarted }, - public modelById: APIRequest = { status: APIRequestStatusList.notStarted }, - public serviceById: APIRequest = { status: APIRequestStatusList.notStarted }, - public models: APIRequest = { status: APIRequestStatusList.notStarted }, - public kubernetesHosts: APIRequest = { status: APIRequestStatusList.notStarted }, - public kubernetesHostById: APIRequest = { status: APIRequestStatusList.notStarted }, - public deleteKubernetesServices: APIRequest = { status: APIRequestStatusList.notStarted }, - public deleteKubernetesModels: APIRequest = { status: APIRequestStatusList.notStarted }, + public saveProject: APIRequest = { status: APIRequestStatusList.notStarted}, + public saveDataServer: APIRequest = { status: APIRequestStatusList.notStarted}, + public saveKubernetes: APIRequest = { status: APIRequestStatusList.notStarted}, + public saveApplication: APIRequest = { status: APIRequestStatusList.notStarted}, + public updateService: APIRequest = { status: APIRequestStatusList.notStarted}, + public saveServiceDeployment: APIRequest = { status: APIRequestStatusList.notStarted}, + public updateServiceRouting: APIRequest = { status: APIRequestStatusList.notStarted}, + public uploadModel: APIRequest = { status: APIRequestStatusList.notStarted}, + public updateModel: APIRequest = { status: APIRequestStatusList.notStarted}, + public fetchAllProjects: APIRequest = { status: APIRequestStatusList.notStarted}, + public fetchProjectById: APIRequest = { status: APIRequestStatusList.notStarted}, + public fetchDataServer: APIRequest = { status: APIRequestStatusList.notStarted}, + public fetchAllKubernetes: APIRequest = { status: APIRequestStatusList.notStarted}, + public fetchKubernetesById: APIRequest = { status: APIRequestStatusList.notStarted}, + public fetchAllApplications: APIRequest = { status: APIRequestStatusList.notStarted}, + public fetchApplicationById: APIRequest = { status: APIRequestStatusList.notStarted}, + public fetchAllModels: APIRequest = { status: APIRequestStatusList.notStarted}, + public fetchModelById: APIRequest = { status: APIRequestStatusList.notStarted}, + public fetchAllServices: APIRequest = { status: APIRequestStatusList.notStarted}, + public fetchServiceById: APIRequest = { status: APIRequestStatusList.notStarted}, + public fetchServiceRoute: APIRequest = { status: APIRequestStatusList.notStarted}, + public switchModels: APIRequest = { status: APIRequestStatusList.notStarted}, public syncKubernetesStatus: APIRequest = { status: APIRequestStatusList.notStarted }, + public deleteKubernetes: APIRequest = { status: APIRequestStatusList.notStarted }, + public deleteDataServer: APIRequest = { status: APIRequestStatusList.notStarted }, + public deleteApplication: APIRequest = { status: APIRequestStatusList.notStarted }, + public deleteServices: APIRequest = { status: APIRequestStatusList.notStarted }, + public deleteModels: APIRequest = { status: APIRequestStatusList.notStarted }, + public login: APIRequest = { status: APIRequestStatusList.notStarted }, public settings: APIRequest<{}> = { status: APIRequestStatusList.notStarted }, - public login: APIRequest = { status: APIRequestStatusList.notStarted }, - public userInfo: APIRequest<{}> = { status: APIRequestStatusList.notStarted }, - public accessControlList: APIRequest = { status: APIRequestStatusList.notStarted }, - public allUsers: APIRequest<{}> = { status: APIRequestStatusList.notStarted }, - public saveAccessControl: APIRequest<{}> = { status: APIRequestStatusList.notStarted }, - public deleteAccessControl: APIRequest = { status: APIRequestStatusList.notStarted }, + public userInfo: APIRequest = { status: APIRequestStatusList.notStarted }, + public fetchAllUsers: APIRequest = { status: APIRequestStatusList.notStarted }, + public fetchProjectAccessControlList: APIRequest = { status: APIRequestStatusList.notStarted }, + public saveProjectAccessControl: APIRequest = { status: APIRequestStatusList.notStarted }, + public deleteProjectAccessControl: APIRequest = { status: APIRequestStatusList.notStarted }, + public fetchApplicationAccessControlList: APIRequest = { status: APIRequestStatusList.notStarted }, + public saveApplicationAccessControl: APIRequest = { status: APIRequestStatusList.notStarted }, + public deleteApplicationAccessControl: APIRequest = { status: APIRequestStatusList.notStarted }, // Notification status public notification = { toasts: [], id: -1 }, public navigation = { login: false } @@ -74,20 +52,20 @@ export class AppState { const initialState = new AppState() function APIRequestReducerCreator

( - actionCreators: APIRequestActionCreators, + actionCreators: Actions.APIRequestActionCreators, statePropertyName: string ) { - function reducer(state: AppState = initialState, action: APIActions) { + function reducer(state: AppState = initialState, action: Actions.APIActions) { const suffix: string = actionCreators.apiType const nextState: AppState = Object.assign({}, state) - const isStarted = (item: any): item is APIRequestStartAction => + const isStarted = (item: any): item is Actions.APIRequestStartAction => item.type === `${suffix}_REQUEST_START` - const isFailed = (item: any): item is APIRequestFailueAction => + const isFailed = (item: any): item is Actions.APIRequestFailueAction => item.type === `${suffix}_FAILURE` - const isSuccess = (item: any): item is APIRequestSuccessAction => + const isSuccess = (item: any): item is Actions.APIRequestSuccessAction => item.type === `${suffix}_SUCCESS` - const isUnauthorized = (item: any): item is APIRequestUnauthorized => + const isUnauthorized = (item: any): item is Actions.APIRequestUnauthorized => item.type === `${suffix}_UNAUTHORIZED` if (isStarted(action)) { @@ -107,39 +85,82 @@ function APIRequestReducerCreator

( return reducer } -export const uploadModelReducer = APIRequestReducerCreator<{}, ModelResponse>(uploadModelActionCreators, 'uploadModel') -export const switchModelsReducer = APIRequestReducerCreator< SwitchModelParam[], boolean[] >(switchModelsActionCreators, 'switchModels') -export const addApplicationReducer = APIRequestReducerCreator(addApplicationActionCreators, 'addApplication') -export const saveServiceReducer = APIRequestReducerCreator(saveServiceActionCreators, 'saveService') -export const saveModelReducer = APIRequestReducerCreator(saveModelActionCreators, 'saveModel') -export const saveKubernetesHostReducer - = APIRequestReducerCreator(saveKubernetesHostActionCreators, 'saveKubernetesHost') -export const fetchAllKubernetesHostsReducer - = APIRequestReducerCreator<{}, KubernetesHost[]>(fetchAllKubernetesHostsActionCreators, 'kubernetesHosts') -export const fetchAllApplicationsReducer = APIRequestReducerCreator<{}, Application[]>(fetchAllApplicationsActionCreators, 'applications') -export const fetchApplicationByIdReducer = APIRequestReducerCreator<{}, Application>(fetchApplicationByIdActionCreators, 'applicationById') -export const fetchKubernetesHostByIdReducer = APIRequestReducerCreator<{}, any>(fetchKubernetesHostByIdActionCreators, 'kubernetesHostById') -export const fetchAllModelsReducer = APIRequestReducerCreator<{}, Model[]>(fetchAllModelsActionCreators, 'models') -export const fetchAllServicesReducer = APIRequestReducerCreator<{}, Service[]>(fetchAllServicesActionCreators, 'services') -export const fetchServiceByIdReducer = APIRequestReducerCreator(fetchServiceByIdActionCreators, 'serviceById') -export const fetchModelByIdReducer = APIRequestReducerCreator(fetchModelByIdActionCreators, 'modelById') -export const fetchServiceDescriptionsReducer - = APIRequestReducerCreator(fetchServiceDescriptionsActionCreators, 'serviceDescriptions') -export const deleteKubernetesHostReducer - = APIRequestReducerCreator<{id: number}, boolean>(deleteKubernetesHostActionCreators, 'deleteKubernetesHost') -export const deleteKubernetesServicesReducer - = APIRequestReducerCreator(deleteKubernetesServicesActionCreators, 'deleteKubernetesServices') -export const deleteKubernetesModelsReducer - = APIRequestReducerCreator(deleteKubernetesModelsActionCreators, 'deleteKubernetesModels') -export const syncKubernetesStatusReducer - = APIRequestReducerCreator(syncKubernetesStatusActionCreators, 'syncKubernetesStatus') -export const settingsReducer = APIRequestReducerCreator<{}, any>(settingsActionCreators, 'settings') -export const loginReducer = APIRequestReducerCreator(loginActionCreators, 'login') -export const userInfoReducer = APIRequestReducerCreator<{}, UserInfo>(userInfoActionCreators, 'userInfo') -export const fetchAllUsersStatusReducer = APIRequestReducerCreator<{}, UserInfo[]>(fetchAllUsersActionCreators, 'allUsers') -export const fetchAccessControlListReducer = APIRequestReducerCreator<{}, AccessControlList[]>(fetchAccessControlListActionCreators, 'accessControlList') -export const saveAccessControlReducer = APIRequestReducerCreator<{}, boolean>(saveAccessControlActionCreators, 'saveAccessControl') -export const deleteAccessControlReducer = APIRequestReducerCreator<{}, boolean>(deleteAccessControlActionCreators, 'deleteAccessControl') +export const saveProjectReducer = APIRequestReducerCreator( + Actions.saveProjectActionCreators, 'saveProject') +export const saveDataServerReducer = APIRequestReducerCreator( + Actions.saveDataServerActionCreators, 'saveDataServer') +export const saveKubernetesReducer = APIRequestReducerCreator( + Actions.saveKubernetesActionCreators, 'saveKubernetes') +export const saveApplicationReducer = APIRequestReducerCreator( + Actions.saveApplicationActionCreators, 'saveApplication') +export const updateServiceReducer = APIRequestReducerCreator( + Actions.updateServiceActionCreators, 'updateService') +export const saveServiceDeploymentReducer = APIRequestReducerCreator( + Actions.saveServiceDeploymentActionCreators, 'saveServiceDeployment') +export const updateServiceRoutingReducer = APIRequestReducerCreator( + Actions.updateServiceRoutingActionCreators, 'updateServiceRouting') +export const uploadModelReducer = APIRequestReducerCreator( + Actions.uploadModelActionCreators, 'uploadModel') +export const updateModelReducer = APIRequestReducerCreator( + Actions.updateModelActionCreators, 'updateModel') +export const fetchAllProjectsReducer = APIRequestReducerCreator<{}, Apis.Project[]>( + Actions.fetchAllProjectsActionCreators, 'fetchAllProjects') +export const fetchProjectByIdReducer = APIRequestReducerCreator( + Actions.fetchProjectByIdActionCreators, 'fetchProjectById') +export const fetchDataServerReducer = APIRequestReducerCreator( + Actions.fetchDataServerActionCreators, 'fetchDataServer') +export const fetchAllKubernetesReducer = APIRequestReducerCreator( + Actions.fetchAllKubernetesActionCreators, 'fetchAllKubernetes') +export const fetchKubernetesByIdReducer = APIRequestReducerCreator( + Actions.fetchKubernetesByIdActionCreators, 'fetchKubernetesById') +export const fetchAllApplicationsReducer = APIRequestReducerCreator( + Actions.fetchAllApplicationsActionCreators, 'fetchAllApplications') +export const fetchApplicationByIdReducer = APIRequestReducerCreator( + Actions.fetchApplicationByIdActionCreators, 'fetchApplicationById') +export const fetchAllModelsReducer = APIRequestReducerCreator( + Actions.fetchAllModelsActionCreators, 'fetchAllModels') +export const fetchModelByIdReducer = APIRequestReducerCreator( + Actions.fetchModelByIdActionCreators, 'fetchModelById') +export const fetchAllServicesReducer = APIRequestReducerCreator( + Actions.fetchAllServicesActionCreators, 'fetchAllServices') +export const fetchServiceByIdReducer = APIRequestReducerCreator( + Actions.fetchServiceByIdActionCreators, 'fetchServiceById') +export const fetchServiceRouteReducer = APIRequestReducerCreator( + Actions.fetchServiceRouteActionCreators, 'fetchServiceRoute') +export const switchModelsReducer = APIRequestReducerCreator( + Actions.switchModelsActionCreators, 'switchModels') +export const syncKubernetesStatusReducer = APIRequestReducerCreator( + Actions.syncKubernetesStatusActionCreators, 'syncKubernetesStatus') +export const deleteKubernetesReducer = APIRequestReducerCreator( + Actions.deleteKubernetesActionCreators, 'deleteKubernetes') +export const deleteDataServerReducer = APIRequestReducerCreator( + Actions.deleteDataServerActionCreators, 'deleteDataServer') +export const deleteApplicationReducer = APIRequestReducerCreator( + Actions.deleteApplicationActionCreators, 'deleteApplication') +export const deleteServicesReducer = APIRequestReducerCreator( + Actions.deleteServicesActionCreators, 'deleteServices') +export const deleteModelsReducer = APIRequestReducerCreator( + Actions.deleteModelsActionCreators, 'deleteModels') +export const loginReducer = APIRequestReducerCreator( + Actions.loginActionCreators, 'login') +export const settingsReducer = APIRequestReducerCreator<{}, any>( + Actions.settingsActionCreators, 'settings') +export const userInfoReducer = APIRequestReducerCreator<{}, Apis.UserInfo>( + Actions.userInfoActionCreators, 'userInfo') +export const fetchAllUsersReducer = APIRequestReducerCreator<{}, Apis.UserInfo[]>( + Actions.fetchAllUsersActionCreators, 'fetchAllUsers') +export const fetchProjectAccessControlListReducer = APIRequestReducerCreator( + Actions.fetchProjectAccessControlListActionCreators, 'fetchProjectAccessControlList') +export const saveProjectAccessControlReducer = APIRequestReducerCreator( + Actions.saveProjectAccessControlActionCreators, 'saveProjectAccessControl') +export const deleteProjectAccessControlReducer = APIRequestReducerCreator( + Actions.deleteProjectAccessControlActionCreators, 'deleteProjectAccessControl') +export const fetchApplicationAccessControlListReducer = APIRequestReducerCreator( + Actions.fetchApplicationAccessControlListActionCreators, 'fetchApplicationAccessControlList') +export const saveApplicationAccessControlReducer = APIRequestReducerCreator( + Actions.saveApplicationAccessControlActionCreators, 'saveApplicationAccessControl') +export const deleteApplicationAccessControlReducer = APIRequestReducerCreator( + Actions.deleteApplicationAccessControlActionCreators, 'deleteApplicationAccessControl') /** * Notification with toasts From 40a061cecbeb3e2de060fa128c543997be3d44b5 Mon Sep 17 00:00:00 2001 From: Hattori Keigo Date: Fri, 15 Mar 2019 10:28:04 +0900 Subject: [PATCH 06/92] Update `/app.tsx` --- frontend/src/app.tsx | 100 ++++++++++++++++++++++++++----------------- 1 file changed, 61 insertions(+), 39 deletions(-) diff --git a/frontend/src/app.tsx b/frontend/src/app.tsx index ee2ac08..330aa68 100644 --- a/frontend/src/app.tsx +++ b/frontend/src/app.tsx @@ -2,36 +2,47 @@ import * as React from 'react' import { render } from 'react-dom' import { Provider } from 'react-redux' import { createStore, combineReducers, compose, applyMiddleware } from 'redux' -import { reducer as reduxFormReducer } from 'redux-form' import thunk from 'redux-thunk' import { - notificationReducer, + saveProjectReducer, + saveDataServerReducer, + saveKubernetesReducer, + saveApplicationReducer, + updateServiceReducer, + saveServiceDeploymentReducer, + updateServiceRoutingReducer, uploadModelReducer, - switchModelsReducer, - saveKubernetesHostReducer, - addApplicationReducer, - saveServiceReducer, - saveModelReducer, - fetchAllKubernetesHostsReducer, - fetchKubernetesHostByIdReducer, + updateModelReducer, + fetchAllProjectsReducer, + fetchProjectByIdReducer, + fetchDataServerReducer, + fetchAllKubernetesReducer, + fetchKubernetesByIdReducer, fetchAllApplicationsReducer, fetchApplicationByIdReducer, fetchAllModelsReducer, + fetchModelByIdReducer, fetchAllServicesReducer, - fetchServiceDescriptionsReducer, fetchServiceByIdReducer, - fetchModelByIdReducer, - deleteKubernetesHostReducer, - deleteKubernetesServicesReducer, - deleteKubernetesModelsReducer, + fetchServiceRouteReducer, + switchModelsReducer, syncKubernetesStatusReducer, - settingsReducer, + deleteKubernetesReducer, + deleteDataServerReducer, + deleteApplicationReducer, + deleteServicesReducer, + deleteModelsReducer, loginReducer, - fetchAccessControlListReducer, - fetchAllUsersStatusReducer, - saveAccessControlReducer, - deleteAccessControlReducer, - userInfoReducer + settingsReducer, + userInfoReducer, + fetchAllUsersReducer, + fetchProjectAccessControlListReducer, + saveProjectAccessControlReducer, + deleteProjectAccessControlReducer, + fetchApplicationAccessControlListReducer, + saveApplicationAccessControlReducer, + deleteApplicationAccessControlReducer, + notificationReducer } from './reducers' import { App } from './components/App' import './assets/koromo.css' @@ -39,34 +50,45 @@ import './assets/style' const store = compose(applyMiddleware(thunk))(createStore)( combineReducers({ - notificationReducer, + saveProjectReducer, + saveDataServerReducer, + saveKubernetesReducer, + saveApplicationReducer, + updateServiceReducer, + saveServiceDeploymentReducer, + updateServiceRoutingReducer, uploadModelReducer, - switchModelsReducer, - saveKubernetesHostReducer, - addApplicationReducer, - saveServiceReducer, - saveModelReducer, - fetchAllKubernetesHostsReducer, - fetchKubernetesHostByIdReducer, + updateModelReducer, + fetchAllProjectsReducer, + fetchProjectByIdReducer, + fetchDataServerReducer, + fetchAllKubernetesReducer, + fetchKubernetesByIdReducer, fetchAllApplicationsReducer, fetchApplicationByIdReducer, fetchAllModelsReducer, + fetchModelByIdReducer, fetchAllServicesReducer, fetchServiceByIdReducer, - fetchModelByIdReducer, - fetchServiceDescriptionsReducer, - deleteKubernetesHostReducer, - deleteKubernetesServicesReducer, - deleteKubernetesModelsReducer, + fetchServiceRouteReducer, + switchModelsReducer, syncKubernetesStatusReducer, - settingsReducer, + deleteKubernetesReducer, + deleteDataServerReducer, + deleteApplicationReducer, + deleteServicesReducer, + deleteModelsReducer, loginReducer, + settingsReducer, userInfoReducer, - fetchAccessControlListReducer, - fetchAllUsersStatusReducer, - saveAccessControlReducer, - deleteAccessControlReducer, - form: reduxFormReducer + fetchAllUsersReducer, + fetchProjectAccessControlListReducer, + saveProjectAccessControlReducer, + deleteProjectAccessControlReducer, + fetchApplicationAccessControlListReducer, + saveApplicationAccessControlReducer, + deleteApplicationAccessControlReducer, + notificationReducer })) render( From 3549232f642b019f3f9355cc79d962d95a39d134 Mon Sep 17 00:00:00 2001 From: Hattori Keigo Date: Mon, 18 Mar 2019 15:09:30 +0900 Subject: [PATCH 07/92] Add yup --- frontend/package.json | 3 ++- frontend/yarn.lock | 44 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/frontend/package.json b/frontend/package.json index 6545a9a..f238d0a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -43,6 +43,7 @@ "webpack": "^4.12.1", "webpack-cli": "^3.0.8", "webpack-dev-server": "^3.1.4", - "webpack-merge": "^4.1.3" + "webpack-merge": "^4.1.3", + "yup": "^0.27.0" } } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 5f52502..b96b663 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2,6 +2,13 @@ # yarn lockfile v1 +"@babel/runtime@^7.0.0": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.3.4.tgz#73d12ba819e365fcf7fd152aed56d6df97d21c83" + integrity sha512-IvfvnMdSaLBateu0jfsYIpZTxAc2cKEXEMiezGGN75QcBcecDUKd3PgLAncT0oOgxKy8dd8hrJKj9MfzgfZd6g== + dependencies: + regenerator-runtime "^0.12.0" + "@types/history@*": version "4.6.2" resolved "https://registry.yarnpkg.com/@types/history/-/history-4.6.2.tgz#12cfaba693ba20f114ed5765467ff25fdf67ddb0" @@ -2764,6 +2771,11 @@ flush-write-stream@^1.0.0: inherits "^2.0.1" readable-stream "^2.0.4" +fn-name@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-2.0.1.tgz#5214d7537a4d06a4a301c0cc262feb84188002e7" + integrity sha1-UhTXU3pNBqSjAcDMJi/rhBiAAuc= + follow-redirects@^1.0.0: version "1.5.0" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.0.tgz#234f49cf770b7f35b40e790f636ceba0c3a0ab77" @@ -5358,6 +5370,11 @@ prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2: loose-envify "^1.3.1" object-assign "^4.1.1" +property-expr@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-1.5.1.tgz#22e8706894a0c8e28d58735804f6ba3a3673314f" + integrity sha512-CGuc0VUTGthpJXL36ydB6jnbyOf/rAHFvmVrJlH+Rg0DqqLFQGAP6hIaxD/G0OAmBJPhXDHuEJigrp0e0wFV6g== + proxy-addr@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341" @@ -5722,6 +5739,11 @@ regenerator-runtime@^0.11.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== +regenerator-runtime@^0.12.0: + version "0.12.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" + integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg== + regenerator-transform@^0.10.0: version "0.10.1" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd" @@ -6545,6 +6567,11 @@ symbol-observable@^1.0.3, symbol-observable@^1.2.0: resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== +synchronous-promise@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.6.tgz#de76e0ea2b3558c1e673942e47e714a930fa64aa" + integrity sha512-TyOuWLwkmtPL49LHCX1caIwHjRzcVd62+GF6h8W/jHOeZUFHpnd2XJDVuUlaTaLPH1nuu2M69mfHr5XbQJnf/g== + tapable@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.0.0.tgz#cbb639d9002eed9c6b5975eb20598d7936f1f9f2" @@ -6649,6 +6676,11 @@ toposort@^1.0.0: resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029" integrity sha1-LmhELZ9k7HILjMieZEOsbKqVACk= +toposort@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" + integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA= + tough-cookie@~2.3.0: version "2.3.4" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" @@ -7274,3 +7306,15 @@ yargs@^7.0.0: which-module "^1.0.0" y18n "^3.2.1" yargs-parser "^5.0.0" + +yup@^0.27.0: + version "0.27.0" + resolved "https://registry.yarnpkg.com/yup/-/yup-0.27.0.tgz#f8cb198c8e7dd2124beddc2457571329096b06e7" + integrity sha512-v1yFnE4+u9za42gG/b/081E7uNW9mUj3qtkmelLbW5YPROZzSH/KUUyJu9Wt8vxFJcT9otL/eZopS0YK1L5yPQ== + dependencies: + "@babel/runtime" "^7.0.0" + fn-name "~2.0.1" + lodash "^4.17.11" + property-expr "^1.5.0" + synchronous-promise "^2.0.6" + toposort "^2.0.2" From de0047a540ae15a991cc1d107f31e317b9350603 Mon Sep 17 00:00:00 2001 From: Hattori Keigo Date: Mon, 18 Mar 2019 15:10:22 +0900 Subject: [PATCH 08/92] Fix variable `id` to an appropriate variable name --- frontend/src/apis/index.tsx | 82 ++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/frontend/src/apis/index.tsx b/frontend/src/apis/index.tsx index 83f20ce..d230d03 100644 --- a/frontend/src/apis/index.tsx +++ b/frontend/src/apis/index.tsx @@ -17,7 +17,7 @@ const convertKeys = (params, func) => Object.keys(params) // POST or PATCH export interface ProjectParam { - id?: number, + projectId?: number, displayName: string description: string registeredDate?: Date @@ -34,7 +34,7 @@ export async function saveProject(params: ProjectParam) { if (params.method === 'post') { return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects`, requestBody, convert, 'POST') } else if (params.method === 'patch') { - return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.id}`, requestBody, convert, 'PATCH') + return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}`, requestBody, convert, 'PATCH') } throw new RangeError(`You specified wrong save method ${params.method}`) @@ -67,7 +67,7 @@ export async function saveDataServer(params: DataServerParam) { } export interface KubernetesParam { - id?: number, + kubernetesId?: number, projectId: number description: string displayName: string @@ -90,14 +90,14 @@ export async function saveKubernetes(params: KubernetesParam) { if (params.method === 'post') { return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/kubernetes`, requestBody, convert, 'POST') } else if (params.method === 'patch') { - return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/kubernetes/${params.id}`, requestBody, convert, 'PATCH') + return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/kubernetes/${params.kubernetesId}`, requestBody, convert, 'PATCH') } throw new RangeError(`You specified wrong save method ${params.method}`) } export interface ApplicationParam { - id?: string, + applicationId?: string, projectId: number description: string applicationName: string @@ -115,14 +115,14 @@ export async function saveApplication(params: ApplicationParam) { if (params.method === 'post') { return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications`, requestBody, convert, 'POST') } else if (params.method === 'patch') { - return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.id}`, requestBody, convert, 'PATCH') + return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}`, requestBody, convert, 'PATCH') } throw new RangeError(`You specified wrong save method ${params.method}`) } export interface UpdateServiceParam { - id: string + serviceId: string projectId: number applicationId: number description: string @@ -137,7 +137,7 @@ export async function updateService(params: UpdateServiceParam): Promise response.status; - return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/services/${params.id}`, requestBody, convert, 'PATCH') + return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/services/${params.serviceId}`, requestBody, convert, 'PATCH') } export interface SingleServiceParam { @@ -155,7 +155,7 @@ export interface SingleServiceParam { method: string } export interface DeploymentParam { - id?: string + serviceId?: string replicasDefault?: number replicasMinimum?: number replicasMaximum?: number @@ -186,7 +186,7 @@ export async function saveServiceDeployment(params: ServiceDeploymentParam): Pro if (params.method === 'post') { return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/service_deployment`, requestBody, convert, 'POST') } else if (params.method === 'patch') { - return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/service_deployment/${params.id}`, requestBody, convert, 'PATCH') + return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/service_deployment/${params.serviceId}`, requestBody, convert, 'PATCH') } } else { return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/single_service_registration`, requestBody, convert, 'POST') @@ -230,7 +230,7 @@ export async function uploadModel(params: UploadModelParam) { } export interface UpdateModelParam { - id?: number + modelId?: number projectId: number applicationId: string description: string @@ -242,14 +242,14 @@ export async function updateModel(params: UpdateModelParam): Promise { const convert = (response) => response.status; - return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/models/${params.id}`, requestBody, convert, 'PATCH') + return APICore.formDataRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/models/${params.modelId}`, requestBody, convert, 'PATCH') } // GET APIs export class Project { constructor( public name: string, - public id: string, + public projectId: string, public description: string = '', public date: Date = null ) { } @@ -261,7 +261,7 @@ export function fetchAllProjects(): Promise { (result): Project => { return { ...result, - id: result.project_id, + projectId: result.project_id, name: result.display_name, date: new Date(result.register_date * 1000) } @@ -270,7 +270,7 @@ export function fetchAllProjects(): Promise { return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects`, convert) } export interface FetchProjectByIdParam { - id: number + projectId: number } export async function fetchProjectById(params: FetchProjectByIdParam): Promise { const convert = @@ -278,11 +278,11 @@ export async function fetchProjectById(params: FetchProjectByIdParam): Promise

{ const convert = @@ -313,13 +313,13 @@ export async function fetchDataServer(params: FetchDataServerByIdParam): Promise date: new Date(result.register_date * 1000) } ); - return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.id}/data_servers`, convert) + return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/data_servers`, convert) } export class Kubernetes { constructor( public name: string, - public id: number, + public kubernetesId: number, public projectId: number, public description: string = '', public configPath: string, @@ -329,7 +329,7 @@ export class Kubernetes { ) { } } export interface FetchKubernetesById { - id?: number + kubernetesId?: number projectId: number } @@ -339,7 +339,7 @@ export async function fetchAllKubernetes(params: FetchKubernetesById): Promise { return { - id: result.kubernetes_id, + kubernetesId: result.kubernetes_id, name: result.display_name, projectId: result.project_id, description: result.description, @@ -350,32 +350,32 @@ export async function fetchAllKubernetes(params: FetchKubernetesById): Promise { const convert = (result) => ( { ...convertKeys(result, camelize), - id: result.kubernetes_id, + kubernetesId: result.kubernetes_id, name: result.display_name, date: new Date(result.register_date * 1000) } ); - return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.id}/kubernetes/${params.id}`, convert) + return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/kubernetes/${params.kubernetesId}`, convert) } export class Application { constructor( public name: string, - public id: string, + public applicationId: string, public description: string = '', public date: Date = null, public projectId: number ) { } } export interface FetchApplicationByIdParam { - id?: string + applicationId?: string projectId: number } export function fetchAllApplications(params: FetchApplicationByIdParam): Promise { @@ -384,7 +384,7 @@ export function fetchAllApplications(params: FetchApplicationByIdParam): Promise results.map( (result): Application => { return { - id: result.application_id, + applicationId: result.application_id, name: result.application_name, description: result.description, date: new Date(result.register_date * 1000), @@ -400,22 +400,22 @@ export async function fetchApplicationById(params: FetchApplicationByIdParam): P { ...convertKeys(result, camelize), name: result.application_name, - id: result.application_id, + applicationId: result.application_id, date: new Date(result.register_date * 1000) } ); - return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.id}`, convert) + return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}`, convert) } export class Model { constructor( - public id: number, + public modelId: number, public description: string = '', public date: Date = null ) { } } export interface FetchModelByIdParam { - id?: string + modelId?: string projectId: number applicationId: string } @@ -424,7 +424,7 @@ export async function fetchAllModels(params: FetchModelByIdParam): Promise results.map((result): Model => { return { description: result.description, - id: result.model_id, + modelId: result.model_id, date: new Date(result.register_date * 1000) } }); @@ -434,21 +434,21 @@ export async function fetchModelById(params: FetchModelByIdParam): Promise ( { - id: result.model_id, + modelId: result.model_id, description: result.description, date: new Date(result.register_date * 1000), ...convertKeys(result, camelize) } ); return APICore.getRequest( - `${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/models/${params.id}`, + `${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}/models/${params.modelId}`, convert ) } export class Service { constructor( - public id: string, + public serviceId: string, public name: string, public description: string = '', public serviceLevel: string, @@ -484,7 +484,7 @@ export async function fetchAllServices(params: FetchServiceParam): Promise results.map((result): Service => { return { - id: result.service_id, + serviceId: result.service_id, name: result.display_name, serviceLevel: result.service_level, version: result.version, @@ -501,7 +501,7 @@ export async function fetchAllServices(params: FetchServiceParam): Promise ( { - id: result.service_id, + serviceId: result.service_id, name: result.display_name, date: new Date(result.register_date * 1000), ...convertKeys(result, camelize) @@ -518,12 +518,12 @@ export async function fetchServiceById(params: FetchServiceByIdParam): Promise Date: Mon, 18 Mar 2019 15:11:14 +0900 Subject: [PATCH 09/92] Fix Admin --- .../App/Admin/AddUserApplicationRoleModal.tsx | 205 ++++++++++++++++++ .../src/components/App/Admin/AddUserModal.tsx | 182 ---------------- .../App/Admin/AddUserProjectRoleModal.tsx | 205 ++++++++++++++++++ .../Admin/EditUserApplicationRoleModal.tsx | 142 ++++++++++++ .../App/Admin/EditUserProjectRoleModal.tsx | 140 ++++++++++++ .../App/Admin/EditUserRoleFields.tsx | 47 ---- .../App/Admin/EditUserRoleModal.tsx | 122 ----------- .../APIRequestResultsRenderer/index.tsx | 19 +- 8 files changed, 705 insertions(+), 357 deletions(-) create mode 100644 frontend/src/components/App/Admin/AddUserApplicationRoleModal.tsx delete mode 100644 frontend/src/components/App/Admin/AddUserModal.tsx create mode 100644 frontend/src/components/App/Admin/AddUserProjectRoleModal.tsx create mode 100644 frontend/src/components/App/Admin/EditUserApplicationRoleModal.tsx create mode 100644 frontend/src/components/App/Admin/EditUserProjectRoleModal.tsx delete mode 100644 frontend/src/components/App/Admin/EditUserRoleFields.tsx delete mode 100644 frontend/src/components/App/Admin/EditUserRoleModal.tsx diff --git a/frontend/src/components/App/Admin/AddUserApplicationRoleModal.tsx b/frontend/src/components/App/Admin/AddUserApplicationRoleModal.tsx new file mode 100644 index 0000000..fbad147 --- /dev/null +++ b/frontend/src/components/App/Admin/AddUserApplicationRoleModal.tsx @@ -0,0 +1,205 @@ +import * as React from 'react' +import { connect } from 'react-redux' +import { withRouter, RouteComponentProps } from 'react-router' +import { Formik, Form, ErrorMessage, Field } from 'formik' +import * as Yup from "yup"; +import { Modal, ModalHeader, ModalBody, ModalFooter, Button } from 'reactstrap' + +import { UserInfo, ApplicationAccessControlList, AccessControlParam } from '@src/apis' +import { APIRequest, isAPISucceeded, isAPIFailed } from '@src/apis/Core' +import { + addNotification, + AddNotificationAction, + saveApplicationAccessControlDispatcher, + fetchAllUsersDispatcher, +} from '@src/actions' +import { APIRequestResultsRenderer } from '@components/Common/APIRequestResultsRenderer' +import { applicationRole } from './constants' + +interface CustomProps { + isModalOpen: boolean + acl: ApplicationAccessControlList[] + toggle: () => void + reload: () => void +} + +interface StateProps { + fetchAllUsersStatus: APIRequest + saveApplicationAccessControlStatus: APIRequest +} + +interface DispatchProps { + addNotification: (params) => AddNotificationAction + fetchAllUsers: () => Promise + saveApplicationAccessControl: (params: AccessControlParam) => Promise +} + +type AddUserApplicationRoleModalProps = CustomProps & StateProps & DispatchProps & RouteComponentProps<{projectId: number, applicationId: string}> + +interface AddUserApplicationRoleModalState { + submitting: boolean +} + +const UserRoleSchema = Yup.object().shape({ + uid: Yup.string() + .required('Required'), + role: Yup.string() + .oneof([applicationRole.admin.toString(), applicationRole.editor.toString(), applicationRole.viewer.toString()]) + .required('Required'), +}); + +class AddUserApplicationRoleModal extends React.Component { + private alreadyAdded: Set + constructor(props) { + super(props) + this.onCancel = this.onCancel.bind(this) + this.onSubmit = this.onSubmit.bind(this) + this.renderForm = this.renderForm.bind(this) + this.state = { + submitting: false + } + this.alreadyAdded = new Set() + props.acl.forEach((e: ApplicationAccessControlList) => { + this.alreadyAdded.add(e.userUid) + }) + } + componentDidUpdate(prevProps, prevState) { + const { isModalOpen } = prevProps + const { fetchAllUsers, saveApplicationAccessControlStatus, toggle, reload } = this.props + const { submitting } = this.state + + fetchAllUsers() + if (isModalOpen && submitting) { + const succeeded: boolean = isAPISucceeded(saveApplicationAccessControlStatus) + const failed: any = isAPIFailed(saveApplicationAccessControlStatus) && saveApplicationAccessControlStatus.error + if (succeeded) { + this.props.addNotification({ color: 'success', message: 'Successfully added user' }) + toggle() + reload() + } + if (failed) { + const message: string = failed.message || 'Something went wrong. Try again later' + this.props.addNotification({ color: 'error', message }) + } + } + } + render() { + const { fetchAllUsersStatus, isModalOpen } = this.props + return ( + + + + ) + } + private renderForm(results) { + return ( + + {({ errors, touched }) => ( +

+ + + Add User + + + {this.renderUsers(results.fetchAllUsersStatus)} + {errors.uid && touched.uid ? ( +
{errors.uid}
+ ) : null} + + {this.renderRoles()} + {errors.role && touched.role ? ( +
{errors.role}
+ ) : null} + +
+ + + {' '} + + + + )} + + ) + } + private renderUsers(userInfos: UserInfo[]) { + const users = userInfos.map((v: UserInfo) => { + return ( + + ) + }) + return ( + + {users} + + ) + } + private renderRoles() { + const roles = Object.values(applicationRole).map((roleName: string) => { + return ( + + ) + }) + return ( + + {roles} + + ) + } + private onCancel() { + const { toggle } = this.props + toggle() + } + private onSubmit(params) { + const { saveApplicationAccessControl, match } = this.props + const projectId = match.params.projectId + const applicationId = match.params.applicationId + this.setState({ submitting: true }) + return saveApplicationAccessControl({ + projectId, + applicationId, + ...params, + method: 'post' + }) + } +} + +const mapStateToProps = (state: any) => ( + { + fetchAllUsersStatus: state.fetchAllUsersReducer.fetchAllUsers, + saveApplicationAccessControlStatus: state.saveApplicationAccessControlReducer.saveApplicationAccessControl + } +) + +const mapDispatchToProps = (dispatch): DispatchProps => { + return { + addNotification: (params) => dispatch(addNotification(params)), + fetchAllUsers: () => fetchAllUsersDispatcher(dispatch), + saveApplicationAccessControl: (params) => saveApplicationAccessControlDispatcher(dispatch, params) + } +} + +export default withRouter( + connect & CustomProps>( + mapStateToProps, mapDispatchToProps + )(AddUserApplicationRoleModal) +) diff --git a/frontend/src/components/App/Admin/AddUserModal.tsx b/frontend/src/components/App/Admin/AddUserModal.tsx deleted file mode 100644 index a466dca..0000000 --- a/frontend/src/components/App/Admin/AddUserModal.tsx +++ /dev/null @@ -1,182 +0,0 @@ -import * as React from 'react' -import { connect } from 'react-redux' -import { withRouter, RouteComponentProps } from 'react-router' -import { Dispatch } from 'redux' -import { Field, InjectedFormProps, reduxForm } from 'redux-form' -import { Modal, ModalHeader, ModalBody, ModalFooter, Button } from 'reactstrap' - -import { UserInfo, AccessControlList } from '@src/apis' -import { APIRequest, isAPISucceeded, isAPIFailed } from '@src/apis/Core' -import { saveAccessControlDispatcher, addNotification, AddNotificationAction, fetchAllUsersDispatcher } from '@src/actions' -import { SingleFormField } from '@components/Common/Field/SingleFormField' -import { required } from '@components/Common/Field/Validateors' -import { APIRequestResultsRenderer } from '@components/Common/APIRequestResultsRenderer' -import { role } from './constants' - -interface Props extends RouteComponentProps<{ applicationId: string }> { - isModalOpen: boolean - acl: AccessControlList[] - toggle: () => void - reload: () => void -} - -interface StateProps { - fetchAllUsersStatus: APIRequest - saveAccessControlStatus: APIRequest -} - -interface DispatchProps { - addNotification: (params) => AddNotificationAction - fetchAllUsers: () => Promise - saveAccessControl: (params) => Promise -} - -type AddUserModalProps = Props & StateProps & DispatchProps & InjectedFormProps - -class AddUserModal extends React.Component { - private alreadyAdded: Set - constructor(props) { - super(props) - this.onCancel = this.onCancel.bind(this) - this.onSubmit = this.onSubmit.bind(this) - this.renderForm = this.renderForm.bind(this) - this.state = { - submitting: false - } - this.alreadyAdded = new Set() - props.acl.forEach((e: AccessControlList) => { - this.alreadyAdded.add(e.userUid) - }) - } - componentWillReceiveProps(nextProps: AddUserModalProps) { - const { isModalOpen } = this.props - const { saveAccessControlStatus, reset, toggle, reload, submitting } = nextProps - if (isModalOpen && submitting) { - const succeeded: boolean = isAPISucceeded(saveAccessControlStatus) - const failed: any = isAPIFailed(saveAccessControlStatus) && saveAccessControlStatus.error - if (succeeded) { - nextProps.addNotification({ color: 'success', message: 'Successfully added user' }) - reset() - toggle() - reload() - } - if (failed) { - const message: string = failed.message || 'Something went wrong. Try again later' - nextProps.addNotification({ color: 'error', message }) - } - } - } - componentWillMount() { - const { fetchAllUsers } = this.props - fetchAllUsers() - } - render() { - const { fetchAllUsersStatus, isModalOpen } = this.props - return ( - - - - ) - } - private renderForm(results) { - const { handleSubmit } = this.props - return ( -
- - - Add User - - {this.renderBodyForm(results.fetchAllUsersStatus)} - {this.renderFooterButtons()} -
- ) - } - private renderBodyForm(userInfos: UserInfo[]) { - const roles = Object.values(role).map((roleName: string) => { - return { label: roleName, value: roleName } - }) - const users = userInfos.map((v: UserInfo) => { - return { - label: v.user.userUid, - value: v.user.userUid, - disabled: this.alreadyAdded.has(v.user.userUid) - } - }) - return ( - - - - - ) - } - private renderFooterButtons() { - const { submitting } = this.props - return ( - - - {' '} - - - ) - } - private onCancel() { - const { toggle } = this.props - toggle() - } - private onSubmit(params) { - const { saveAccessControl, match } = this.props - const applicationId = match.params.applicationId - this.setState({ submitting: true }) - return saveAccessControl({ - applicationId, - ...params.save.user, - mode: 'add' - }) - } -} - -export default withRouter( - connect( - (state: any): StateProps => { - return { - ...state.form, - fetchAllUsersStatus: state.fetchAllUsersStatusReducer.allUsers, - saveAccessControlStatus: state.saveAccessControlReducer.saveAccessControl - } - }, - (dispatch: Dispatch): DispatchProps => { - return { - addNotification: (params) => dispatch(addNotification(params)), - fetchAllUsers: () => fetchAllUsersDispatcher(dispatch), - saveAccessControl: (params) => saveAccessControlDispatcher(dispatch, params) - } - } - )(reduxForm<{}, Props>({ - form: 'saveUserForm' - })(AddUserModal)) -) diff --git a/frontend/src/components/App/Admin/AddUserProjectRoleModal.tsx b/frontend/src/components/App/Admin/AddUserProjectRoleModal.tsx new file mode 100644 index 0000000..e3f16ef --- /dev/null +++ b/frontend/src/components/App/Admin/AddUserProjectRoleModal.tsx @@ -0,0 +1,205 @@ +import * as React from 'react' +import { connect } from 'react-redux' +import { withRouter, RouteComponentProps } from 'react-router' +import { Formik, Form, ErrorMessage, Field } from 'formik' +import * as Yup from "yup"; +import { Modal, ModalHeader, ModalBody, ModalFooter, Button } from 'reactstrap' + +import {UserInfo, ProjectAccessControlList, AccessControlParam} from '@src/apis' +import { APIRequest, isAPISucceeded, isAPIFailed } from '@src/apis/Core' +import { + addNotification, + AddNotificationAction, + saveProjectAccessControlDispatcher, + fetchAllUsersDispatcher, +} from '@src/actions' +import { APIRequestResultsRenderer } from '@components/Common/APIRequestResultsRenderer' +import { projectRole } from './constants' + +interface Props extends RouteComponentProps<{projectId: number, applicationId?: string}> { + isModalOpen: boolean + acl: ProjectAccessControlList[] + toggle: () => void + reload: () => void +} + +interface StateProps { + fetchAllUsersStatus: APIRequest + saveProjectAccessControlStatus: APIRequest +} + +interface DispatchProps { + addNotification: (params) => AddNotificationAction + fetchAllUsers: () => Promise + saveProjectAccessControl: (params: AccessControlParam) => Promise +} + +type AddUserProjectRoleModalProps = Props & StateProps & DispatchProps + +interface AddUserProjectRoleModalState { + submitting: boolean +} + +const UserRoleSchema = Yup.object().shape({ + uid: Yup.string() + .required('Required'), + role: Yup.string() + .oneof([projectRole.admin.toString(), projectRole.member.toString()]) + .required('Required'), +}); + +class AddUserProjectRoleModal extends React.Component { + private alreadyAdded: Set + constructor(props) { + super(props) + this.onCancel = this.onCancel.bind(this) + this.onSubmit = this.onSubmit.bind(this) + this.renderForm = this.renderForm.bind(this) + this.state = { + submitting: false + } + this.alreadyAdded = new Set() + props.acl.forEach((e: ProjectAccessControlList) => { + this.alreadyAdded.add(e.userUid) + }) + } + componentDidUpdate(prevProps, prevState) { + const { isModalOpen } = prevProps + const { fetchAllUsers, saveProjectAccessControlStatus, toggle, reload } = this.props + const { submitting } = this.state + + fetchAllUsers() + if (isModalOpen && submitting) { + const succeeded: boolean = isAPISucceeded(saveProjectAccessControlStatus) + const failed: any = isAPIFailed(saveProjectAccessControlStatus) && saveProjectAccessControlStatus.error + if (succeeded) { + this.props.addNotification({ color: 'success', message: 'Successfully added user' }) + toggle() + reload() + } + if (failed) { + const message: string = failed.message || 'Something went wrong. Try again later' + this.props.addNotification({ color: 'error', message }) + } + } + } + render() { + const { fetchAllUsersStatus, isModalOpen } = this.props + return ( + + + + ) + } + private renderForm(results) { + return ( + + + {({ errors, touched }) => ( +
+ + + Add User + + + {this.renderUsers(results.fetchAllUsersStatus)} + {errors.uid && touched.uid ? ( +
{errors.uid}
+ ) : null} + + {this.renderRoles()} + {errors.role && touched.role ? ( +
{errors.role}
+ ) : null} + +
+ + + {' '} + + +
+ )} +
+
+ ) + } + private renderUsers(userInfos: UserInfo[]) { + const users = userInfos.map((v: UserInfo) => { + return ( + + ) + }) + return ( + + {users} + + ) + } + private renderRoles() { + const roles = Object.values(projectRole).map((roleName: string) => { + return ( + + ) + }) + return ( + + {roles} + + ) + } + private onCancel() { + const { toggle } = this.props + toggle() + } + private onSubmit(params) { + const { saveProjectAccessControl, match } = this.props + const projectId = match.params.projectId + this.setState({ submitting: true }) + return saveProjectAccessControl({ + projectId, + ...params, + method: 'post' + }) + } +} + +const mapStateToProps = (state: any) => ( + { + fetchAllUsersStatus: state.fetchAllUsersReducer.fetchAllUsers, + saveProjectAccessControlStatus: state.saveProjectAccessControlReducer.saveProjectAccessControl + } +) + +const mapDispatchToProps = (dispatch): DispatchProps => { + return { + addNotification: (params) => dispatch(addNotification(params)), + fetchAllUsers: () => fetchAllUsersDispatcher(dispatch), + saveProjectAccessControl: (params) => saveProjectAccessControlDispatcher(dispatch, params) + } +} + +export default withRouter( + connect>( + mapStateToProps, mapDispatchToProps + )(AddUserProjectRoleModal) +) diff --git a/frontend/src/components/App/Admin/EditUserApplicationRoleModal.tsx b/frontend/src/components/App/Admin/EditUserApplicationRoleModal.tsx new file mode 100644 index 0000000..c9e125a --- /dev/null +++ b/frontend/src/components/App/Admin/EditUserApplicationRoleModal.tsx @@ -0,0 +1,142 @@ +import * as React from 'react' +import { connect } from 'react-redux' +import { Formik, Form, ErrorMessage, Field } from 'formik' +import * as Yup from "yup"; +import { Modal, ModalHeader, ModalBody, ModalFooter, Button } from 'reactstrap' + +import { ApplicationAccessControlList } from '@src/apis' +import { APIRequest, isAPIFailed, isAPISucceeded } from '@src/apis/Core' +import { applicationRole } from "@components/App/Admin/constants"; + +interface EditUserApplicationRoleModalState { + submitting: boolean +} + +const UserRoleSchema = Yup.object().shape({ + role: Yup.string() + .oneof([applicationRole.admin.toString(), applicationRole.editor.toString(), applicationRole.viewer.toString()]) + .required('Required'), +}); + +class EditUserRoleModalImpl extends React.Component { + constructor(props: EditUserRoleModalProps) { + super(props) + this.onCancel = this.onCancel.bind(this) + this.onSubmit = this.onSubmit.bind(this) + } + componentDidUpdate(prevProps, prevState) { + const { isModalOpen } = prevProps + const { saveApplicationAccessControlStatus, toggle, reload } = this.props + const { submitting } = this.state + + if (isModalOpen && submitting) { + const succeeded: boolean = isAPISucceeded(saveApplicationAccessControlStatus) + const failed: any = isAPIFailed(saveApplicationAccessControlStatus) && saveApplicationAccessControlStatus.error + if (succeeded) { + this.props.addNotification({ color: 'success', message: 'Successfully updated user' }) + toggle() + reload() + } + if (failed) { + this.props.addNotification({ color: 'error', message: 'Something went wrong. Try again later' }) + } + } + } + render() { + const { isModalOpen, target } = this.props + if (!target) { + return null + } + return ( + + + {({ errors, touched }) => ( +
+ + + Edit User {target.userUid} + + + {this.renderRoles(target)} + {errors.role && touched.role ? ( +
{errors.role}
+ ) : null} + +
+ + + {' '} + + +
+ )} +
+
+ ) + } + private renderRoles(target) { + if (!target) { + return null + } + const roles = Object.values(applicationRole).map((roleName: string) => { + return ( + + ) + }) + return ( + + {roles} + + ) + } + private onCancel() { + const { toggle } = this.props + toggle() + } + private onSubmit(params) { + const { saveApplicationAccessControl, projectId, applicationId, target } = this.props + this.setState({ submitting: true }) + return saveApplicationAccessControl({ + projectId, + applicationId, + uid: target.userUid, + role: params.role, + method: 'patch' + }) + } +} + +interface CustomProps { + projectId: number + applicationId: string + isModalOpen: boolean + toggle: () => void + reload: () => void + target?: ApplicationAccessControlList + saveApplicationAccessControl: (params) => void + saveApplicationAccessControlStatus: APIRequest + addNotification: (params) => void +} + +type EditUserRoleModalProps = CustomProps + +export const EditUserApplicationRoleModal = + connect( + (state: any, extraProps: CustomProps) => ({ + ...extraProps, + ...state.form + }) + )(EditUserRoleModalImpl) diff --git a/frontend/src/components/App/Admin/EditUserProjectRoleModal.tsx b/frontend/src/components/App/Admin/EditUserProjectRoleModal.tsx new file mode 100644 index 0000000..2531e52 --- /dev/null +++ b/frontend/src/components/App/Admin/EditUserProjectRoleModal.tsx @@ -0,0 +1,140 @@ +import * as React from 'react' +import { connect } from 'react-redux' +import { Formik, Form, ErrorMessage, Field } from 'formik' +import * as Yup from "yup"; +import { Modal, ModalHeader, ModalBody, ModalFooter, Button } from 'reactstrap' + +import { ProjectAccessControlList } from '@src/apis' +import { APIRequest, isAPIFailed, isAPISucceeded } from '@src/apis/Core' +import { projectRole } from "@components/App/Admin/constants"; + +interface EditUserProjectRoleModalState { + submitting: boolean +} + +const UserRoleSchema = Yup.object().shape({ + role: Yup.string() + .oneof([projectRole.admin.toString(), projectRole.member.toString()]) + .required('Required'), +}); + +class EditUserRoleModalImpl extends React.Component { + constructor(props: EditUserRoleModalProps) { + super(props) + this.onCancel = this.onCancel.bind(this) + this.onSubmit = this.onSubmit.bind(this) + } + componentDidUpdate(prevProps, prevState) { + const { isModalOpen } = prevProps + const { saveProjectAccessControlStatus, toggle, reload } = this.props + const { submitting } = this.state + + if (isModalOpen && submitting) { + const succeeded: boolean = isAPISucceeded(saveProjectAccessControlStatus) + const failed: any = isAPIFailed(saveProjectAccessControlStatus) && saveProjectAccessControlStatus.error + if (succeeded) { + this.props.addNotification({ color: 'success', message: 'Successfully updated user' }) + toggle() + reload() + } + if (failed) { + this.props.addNotification({ color: 'error', message: 'Something went wrong. Try again later' }) + } + } + } + render() { + const { isModalOpen, target } = this.props + if (!target) { + return null + } + return ( + + + {({ errors, touched }) => ( +
+ + + Edit User {target.userUid} + + + {this.renderRoles(target)} + {errors.role && touched.role ? ( +
{errors.role}
+ ) : null} + +
+ + + {' '} + + +
+ )} +
+
+ ) + } + private renderRoles(target) { + if (!target) { + return null + } + const roles = Object.values(projectRole).map((roleName: string) => { + return ( + + ) + }) + return ( + + {roles} + + ) + } + private onCancel() { + const { toggle } = this.props + toggle() + } + private onSubmit(params) { + const { saveProjectAccessControl, projectId, target } = this.props + this.setState({ submitting: true }) + return saveProjectAccessControl({ + projectId, + uid: target.userUid, + role: params.role, + method: 'patch' + }) + } +} + +interface CustomProps { + projectId: number + isModalOpen: boolean + toggle: () => void + reload: () => void + target?: ProjectAccessControlList + saveProjectAccessControl: (params) => void + saveProjectAccessControlStatus: APIRequest + addNotification: (params) => void +} + +type EditUserRoleModalProps = CustomProps + +export const EditUserProjectRoleModal = + connect( + (state: any, extraProps: CustomProps) => ({ + ...extraProps, + ...state.form + }) + )(EditUserRoleModalImpl) diff --git a/frontend/src/components/App/Admin/EditUserRoleFields.tsx b/frontend/src/components/App/Admin/EditUserRoleFields.tsx deleted file mode 100644 index b4dbe76..0000000 --- a/frontend/src/components/App/Admin/EditUserRoleFields.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import * as React from 'react' -import { connect } from 'react-redux' -import { Field } from 'redux-form' - -import { AccessControlList } from '@src/apis' -import { SingleFormField } from '@components/Common/Field/SingleFormField' -import { required } from '@components/Common/Field/Validateors' -import { role } from './constants' - -class EditUserRoleFieldsImpl extends React.Component { - constructor(props: EditUserRoleFieldsProps) { - super(props) - } - render() { - const { target } = this.props - if (!target) { - return null - } - const roles = Object.values(role).map((roleName: string) => { - return { label: roleName, value: roleName } - }) - return ( - - ) - } -} - -interface CustomProps { - target?: AccessControlList -} - -type EditUserRoleFieldsProps = CustomProps - -export const EditUserRoleFields = - connect( - (state: any, extraProps: CustomProps) => ({ - ...extraProps, - }) - )(EditUserRoleFieldsImpl) diff --git a/frontend/src/components/App/Admin/EditUserRoleModal.tsx b/frontend/src/components/App/Admin/EditUserRoleModal.tsx deleted file mode 100644 index 759ed5e..0000000 --- a/frontend/src/components/App/Admin/EditUserRoleModal.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import * as React from 'react' -import {reduxForm, InjectedFormProps} from 'redux-form' -import { connect } from 'react-redux' -import { Modal, ModalHeader, ModalBody, ModalFooter, Button, Form } from 'reactstrap' - -import { AccessControlList } from '@src/apis' -import { APIRequest, isAPIFailed, isAPISucceeded } from '@src/apis/Core' -import { EditUserRoleFields } from './EditUserRoleFields' - -class EditUserRoleModalImpl extends React.Component { - constructor(props: EditUserRoleModalProps) { - super(props) - this.onCancel = this.onCancel.bind(this) - this.onSubmit = this.onSubmit.bind(this) - } - componentWillReceiveProps(nextProps: EditUserRoleModalProps) { - const { isModalOpen } = this.props - const { saveAccessControlStatus, reset, toggle, reload, submitting } = nextProps - if (isModalOpen && submitting) { - const succeeded: boolean = isAPISucceeded(saveAccessControlStatus) - const failed: boolean = isAPIFailed(saveAccessControlStatus) - if (succeeded) { - nextProps.addNotification({ color: 'success', message: 'Successfully updated user' }) - reset() - toggle() - reload() - } - if (failed) { - nextProps.addNotification({ color: 'error', message: 'Something went wrong. Try again later' }) - } - } - } - render() { - const { isModalOpen, handleSubmit, target, submitting } = this.props - if (!target) { - return null - } - return ( - -
- - - Edit User {target.userUid} - - - - - - - {' '} - - -
-
- ) - } - private onCancel() { - const { toggle } = this.props - toggle() - } - private onSubmit(params) { - const { saveAccessControl, applicationId, target } = this.props - this.setState({ submitting: true }) - return saveAccessControl({ - applicationId, - ...params.edit.user, - uid: target.userUid, - mode: 'edit' - }) - } -} - -interface CustomProps { - applicationId: string - isModalOpen: boolean - toggle: () => void - reload: () => void - target?: AccessControlList - saveAccessControl: (params) => void - saveAccessControlStatus: APIRequest - addNotification: (params) => void -} - -interface StateProps { - initialValues -} - -type EditUserRoleModalProps = - CustomProps - & StateProps - & InjectedFormProps<{}, CustomProps> - -const generateInitialValues = (props: CustomProps) => ( - { - edit: { - user: { - ...props.target - } - } - } -) - -export const EditUserRoleModal = - connect( - (state: any, extraProps: CustomProps): StateProps => ({ - ...extraProps, - ...state.form, - initialValues: generateInitialValues(extraProps) - }) - )(reduxForm<{}, CustomProps>({ - form: 'EditUserRoleForm', - touchOnChange: true, - enableReinitialize: true - })(EditUserRoleModalImpl)) diff --git a/frontend/src/components/Common/APIRequestResultsRenderer/index.tsx b/frontend/src/components/Common/APIRequestResultsRenderer/index.tsx index 90b5180..3942281 100644 --- a/frontend/src/components/Common/APIRequestResultsRenderer/index.tsx +++ b/frontend/src/components/Common/APIRequestResultsRenderer/index.tsx @@ -1,8 +1,8 @@ import * as React from 'react' import { withRouter, RouteComponentProps } from 'react-router-dom' import { isAPISucceeded, APIRequestStatusList } from '@src/apis/Core' -import { UserRole } from "@src/apis"; -import { role } from "@components/App/Admin/constants"; +import { UserProjectRole, UserApplicationRole } from "@src/apis"; +import { projectRole, applicationRole } from "@components/App/Admin/constants"; /** * Render component with API requests @@ -14,6 +14,7 @@ interface Props { APIStatus: {} render: (result: any, canEdit: boolean) => JSX.Element renderFailed?: () => JSX.Element + projectId?: number applicationId?: string } @@ -30,15 +31,21 @@ class APIRequestResults extends React.Component, } render() { - const { render, renderFailed, applicationId } = this.props + const { render, renderFailed, projectId, applicationId } = this.props const currentStatus: RequestResultsPair = this.checkAllRequestResults() if (currentStatus[0] === APIRequestStatusList.success) { const fetchedResults = currentStatus[1] - if (fetchedResults.userInfoStatus && applicationId) { - const canEdit = fetchedResults.userInfoStatus.roles.some((userRole: UserRole) => { + if (fetchedResults.userInfoStatus && projectId && applicationId) { + const canEdit = fetchedResults.userInfoStatus.applicationRoles.some((userRole: UserApplicationRole) => { return String(userRole.applicationId) === applicationId && - (userRole.role === role.editor || userRole.role === role.owner) + (userRole.role === applicationRole.editor || userRole.role === applicationRole.admin) + }) + return render(fetchedResults, canEdit) + } else if (fetchedResults.userInfoStatus && projectId) { + const canEdit = fetchedResults.userInfoStatus.projectRoles.some((userRole: UserProjectRole) => { + return Number(userRole.projectId) === projectId && + (userRole.role === projectRole.admin) }) return render(fetchedResults, canEdit) } else { From 543234519b34247b273b44d2074ec3bc9c7e19f5 Mon Sep 17 00:00:00 2001 From: Hattori Keigo Date: Mon, 8 Apr 2019 11:36:41 +0900 Subject: [PATCH 10/92] Fix minor --- frontend/src/actions/index.tsx | 18 ++++---- frontend/src/apis/index.tsx | 76 ++++++++++++++++----------------- frontend/src/app.tsx | 4 +- frontend/src/reducers/index.tsx | 10 ++--- 4 files changed, 54 insertions(+), 54 deletions(-) diff --git a/frontend/src/actions/index.tsx b/frontend/src/actions/index.tsx index c30bad4..f941d32 100644 --- a/frontend/src/actions/index.tsx +++ b/frontend/src/actions/index.tsx @@ -159,15 +159,15 @@ export const fetchDataServerDispatcher = asyncAPIRequestDispatcherCreator('FETCH_ALL_KUBERNETES') -export const fetchAllKubernetesDispatcher = asyncAPIRequestDispatcherCreator( + new APIRequestActionCreators('FETCH_ALL_KUBERNETES') +export const fetchAllKubernetesDispatcher = asyncAPIRequestDispatcherCreator( fetchAllKubernetesActionCreators, Apis.fetchAllKubernetes ) export const fetchKubernetesByIdActionCreators = - new APIRequestActionCreators('FETCH_KUBERNETES_BY_ID') -export const fetchKubernetesByIdDispatcher = asyncAPIRequestDispatcherCreator( + new APIRequestActionCreators('FETCH_KUBERNETES_BY_ID') +export const fetchKubernetesByIdDispatcher = asyncAPIRequestDispatcherCreator( fetchKubernetesByIdActionCreators, Apis.fetchKubernetesById ) @@ -228,11 +228,11 @@ export const switchModelsDispatcher = asyncAPIRequestDispatcherCreator('SYNC_KUBERNETES_STATUS') -export const syncKubernetesStatusDispatcher = asyncAPIRequestDispatcherCreator( - syncKubernetesStatusActionCreators, - Apis.syncKubernetesStatus +export const syncKubernetesActionCreators = + new APIRequestActionCreators('SYNC_KUBERNETES_STATUS') +export const syncKubernetesDispatcher = asyncAPIRequestDispatcherCreator( + syncKubernetesActionCreators, + Apis.syncKubernetes ) export const deleteKubernetesActionCreators = diff --git a/frontend/src/apis/index.tsx b/frontend/src/apis/index.tsx index d230d03..5d01970 100644 --- a/frontend/src/apis/index.tsx +++ b/frontend/src/apis/index.tsx @@ -1,5 +1,5 @@ import * as APICore from './Core' -import { apiConvertDataServerMode, apiConvertProjectRole, apiConvertApplicationRole } from '@components/App/Admin/constants' +import { apiConvertDataServerMode, apiConvertProjectRole, apiConvertApplicationRole } from '@components/Common/Enum' const snakelize = (value: string): string => (value.split(/(?=[A-Z])/).join('_').toLowerCase()); const camelize = (value: string): string => { @@ -20,7 +20,7 @@ export interface ProjectParam { projectId?: number, displayName: string description: string - registeredDate?: Date + registerDate?: Date method: string } export async function saveProject(params: ProjectParam) { @@ -52,7 +52,7 @@ export interface DataServerParam { awsAccessKey: string awsSecretKey: string awsBucketName: string - registeredDate?: Date + registerDate?: Date method: string } export async function saveDataServer(params: DataServerParam) { @@ -73,16 +73,16 @@ export interface KubernetesParam { displayName: string exposedHost: string exposedPort: number - registeredDate?: Date - configFile: File | string + registerDate?: Date + configPath?: File | string method: string } export async function saveKubernetes(params: KubernetesParam) { const requestBody = { ...convertKeys(params, snakelize), - file: params.configFile, + file: params.configPath, method: {} = {}, - configFile: {} = {} + configPath: {} = {} }; const convert = (response) => response.status; @@ -101,7 +101,7 @@ export interface ApplicationParam { projectId: number description: string applicationName: string - registeredDate?: Date + registerDate?: Date method: string } export async function saveApplication(params: ApplicationParam) { @@ -151,7 +151,7 @@ export interface SingleServiceParam { serviceModelAssignment: number serviceInsecureHost: string serviceInsecurePort: number - registeredDate?: Date + registerDate?: Date method: string } export interface DeploymentParam { @@ -217,7 +217,7 @@ export interface UploadModelParam { projectId: number applicationId: string description: string - file: File + filepath: File } export async function uploadModel(params: UploadModelParam) { const requestBody = { @@ -251,7 +251,7 @@ export class Project { public name: string, public projectId: string, public description: string = '', - public date: Date = null + public registerDate: Date = null ) { } } export function fetchAllProjects(): Promise { @@ -263,7 +263,7 @@ export function fetchAllProjects(): Promise { ...result, projectId: result.project_id, name: result.display_name, - date: new Date(result.register_date * 1000) + registerDate: new Date(result.register_date * 1000) } } ); @@ -279,7 +279,7 @@ export async function fetchProjectById(params: FetchProjectByIdParam): Promise

( { ...convertKeys(result, camelize), - mode: apiConvertDataServerMode(result.data_server_mode), - date: new Date(result.register_date * 1000) + dataServerMode: apiConvertDataServerMode(result.data_server_mode), + registerDate: new Date(result.register_date * 1000) } ); return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/data_servers`, convert) @@ -318,48 +318,48 @@ export async function fetchDataServer(params: FetchDataServerByIdParam): Promise export class Kubernetes { constructor( - public name: string, + public displayName: string, public kubernetesId: number, public projectId: number, public description: string = '', public configPath: string, public exposedHost: string, public exposedPort: number, - public date: Date = null + public registerDate: Date = null ) { } } -export interface FetchKubernetesById { +export interface FetchKubernetesByIdParam { kubernetesId?: number projectId: number } -export async function fetchAllKubernetes(params: FetchKubernetesById): Promise { +export async function fetchAllKubernetes(params: FetchKubernetesByIdParam): Promise { const convert = (results) => results.map( (result): Kubernetes => { return { kubernetesId: result.kubernetes_id, - name: result.display_name, + displayName: result.display_name, projectId: result.project_id, description: result.description, configPath: result.config_path, exposedHost: result.exposed_host, exposedPort: result.exposed_port, - date: new Date(result.register_date * 1000) + registerDate: new Date(result.register_date * 1000) } } ); return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/kubernetes`, convert) } -export async function fetchKubernetesById(params: FetchKubernetesById): Promise { +export async function fetchKubernetesById(params: FetchKubernetesByIdParam): Promise { const convert = (result) => ( { ...convertKeys(result, camelize), kubernetesId: result.kubernetes_id, name: result.display_name, - date: new Date(result.register_date * 1000) + registerDate: new Date(result.register_date * 1000) } ); return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/kubernetes/${params.kubernetesId}`, convert) @@ -370,7 +370,7 @@ export class Application { public name: string, public applicationId: string, public description: string = '', - public date: Date = null, + public registerDate: Date = null, public projectId: number ) { } } @@ -387,7 +387,7 @@ export function fetchAllApplications(params: FetchApplicationByIdParam): Promise applicationId: result.application_id, name: result.application_name, description: result.description, - date: new Date(result.register_date * 1000), + registerDate: new Date(result.register_date * 1000), projectId: result.project_id } } @@ -401,7 +401,7 @@ export async function fetchApplicationById(params: FetchApplicationByIdParam): P ...convertKeys(result, camelize), name: result.application_name, applicationId: result.application_id, - date: new Date(result.register_date * 1000) + registerDate: new Date(result.register_date * 1000) } ); return APICore.getRequest(`${process.env.API_HOST}:${process.env.API_PORT}/api/projects/${params.projectId}/applications/${params.applicationId}`, convert) @@ -411,11 +411,11 @@ export class Model { constructor( public modelId: number, public description: string = '', - public date: Date = null + public registerDate: Date = null ) { } } export interface FetchModelByIdParam { - modelId?: string + modelId?: number projectId: number applicationId: string } @@ -425,7 +425,7 @@ export async function fetchAllModels(params: FetchModelByIdParam): Promise { +export async function syncKubernetes(params: SyncKubernetesParam): Promise { const options = { method: 'PUT', body: new FormData() diff --git a/frontend/src/app.tsx b/frontend/src/app.tsx index 330aa68..c873f85 100644 --- a/frontend/src/app.tsx +++ b/frontend/src/app.tsx @@ -26,7 +26,7 @@ import { fetchServiceByIdReducer, fetchServiceRouteReducer, switchModelsReducer, - syncKubernetesStatusReducer, + syncKubernetesReducer, deleteKubernetesReducer, deleteDataServerReducer, deleteApplicationReducer, @@ -72,7 +72,7 @@ const store = compose(applyMiddleware(thunk))(createStore)( fetchServiceByIdReducer, fetchServiceRouteReducer, switchModelsReducer, - syncKubernetesStatusReducer, + syncKubernetesReducer, deleteKubernetesReducer, deleteDataServerReducer, deleteApplicationReducer, diff --git a/frontend/src/reducers/index.tsx b/frontend/src/reducers/index.tsx index 9f0ed9a..14b83f7 100644 --- a/frontend/src/reducers/index.tsx +++ b/frontend/src/reducers/index.tsx @@ -27,7 +27,7 @@ export class AppState { public fetchServiceById: APIRequest = { status: APIRequestStatusList.notStarted}, public fetchServiceRoute: APIRequest = { status: APIRequestStatusList.notStarted}, public switchModels: APIRequest = { status: APIRequestStatusList.notStarted}, - public syncKubernetesStatus: APIRequest = { status: APIRequestStatusList.notStarted }, + public syncKubernetes: APIRequest = { status: APIRequestStatusList.notStarted }, public deleteKubernetes: APIRequest = { status: APIRequestStatusList.notStarted }, public deleteDataServer: APIRequest = { status: APIRequestStatusList.notStarted }, public deleteApplication: APIRequest = { status: APIRequestStatusList.notStarted }, @@ -109,9 +109,9 @@ export const fetchProjectByIdReducer = APIRequestReducerCreator( Actions.fetchDataServerActionCreators, 'fetchDataServer') -export const fetchAllKubernetesReducer = APIRequestReducerCreator( +export const fetchAllKubernetesReducer = APIRequestReducerCreator( Actions.fetchAllKubernetesActionCreators, 'fetchAllKubernetes') -export const fetchKubernetesByIdReducer = APIRequestReducerCreator( +export const fetchKubernetesByIdReducer = APIRequestReducerCreator( Actions.fetchKubernetesByIdActionCreators, 'fetchKubernetesById') export const fetchAllApplicationsReducer = APIRequestReducerCreator( Actions.fetchAllApplicationsActionCreators, 'fetchAllApplications') @@ -129,8 +129,8 @@ export const fetchServiceRouteReducer = APIRequestReducerCreator( Actions.switchModelsActionCreators, 'switchModels') -export const syncKubernetesStatusReducer = APIRequestReducerCreator( - Actions.syncKubernetesStatusActionCreators, 'syncKubernetesStatus') +export const syncKubernetesReducer = APIRequestReducerCreator( + Actions.syncKubernetesActionCreators, 'syncKubernetes') export const deleteKubernetesReducer = APIRequestReducerCreator( Actions.deleteKubernetesActionCreators, 'deleteKubernetes') export const deleteDataServerReducer = APIRequestReducerCreator( From 5f7bf68da741497f8bc5e8b9180b178c48ce4374 Mon Sep 17 00:00:00 2001 From: Hattori Keigo Date: Mon, 8 Apr 2019 11:37:21 +0900 Subject: [PATCH 11/92] Add Field template --- .../src/components/Common/Field/Checkbox.tsx | 55 ++ .../Common/Field/FileUploadInputField.tsx | 73 --- .../src/components/Common/Field/Radio.tsx | 47 ++ .../Common/Field/SingleFormField.tsx | 78 --- .../components/Common/Field/Validateors.tsx | 19 - .../src/components/Common/Field/index.tsx | 2 + .../DeploymentSettingFormFields/index.tsx | 504 ------------------ 7 files changed, 104 insertions(+), 674 deletions(-) create mode 100644 frontend/src/components/Common/Field/Checkbox.tsx delete mode 100644 frontend/src/components/Common/Field/FileUploadInputField.tsx create mode 100644 frontend/src/components/Common/Field/Radio.tsx delete mode 100644 frontend/src/components/Common/Field/SingleFormField.tsx delete mode 100644 frontend/src/components/Common/Field/Validateors.tsx create mode 100644 frontend/src/components/Common/Field/index.tsx delete mode 100644 frontend/src/components/misc/Forms/DeploymentSettingFormFields/index.tsx diff --git a/frontend/src/components/Common/Field/Checkbox.tsx b/frontend/src/components/Common/Field/Checkbox.tsx new file mode 100644 index 0000000..a9778cb --- /dev/null +++ b/frontend/src/components/Common/Field/Checkbox.tsx @@ -0,0 +1,55 @@ +import * as React from 'react' +import { connect } from 'react-redux' +import { Field } from "formik"; + + +class CheckboxImpl extends React.Component { + constructor(props, context) { + super(props, context) + } + + render() { + return ( + + {({ field, form }) => ( + + )} + + ) + } +} + +type CheckboxProps = CustomProps + +interface CheckboxState {} + +interface CustomProps { + name: string + value + label: string +} + +export const Checkbox = connect( + (state: any, props: CustomProps) => ({ + ...state.form, + ...props, + }) +)(CheckboxImpl) \ No newline at end of file diff --git a/frontend/src/components/Common/Field/FileUploadInputField.tsx b/frontend/src/components/Common/Field/FileUploadInputField.tsx deleted file mode 100644 index 0d23988..0000000 --- a/frontend/src/components/Common/Field/FileUploadInputField.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import * as React from 'react' -import { FormFeedback, FormText, FormGroup } from 'reactstrap' -import { WrappedFieldProps } from 'redux-form' - -const adaptFileEventToValue = (delegate) => (e) => { - delegate(e.target.files[0]) -} - -interface CustomFormFieldProps { - id: string - fileName: string - placeholder: string - required?: boolean - formText?: string | JSX.Element -} - -export type FileUploadInputFieldProps = CustomFormFieldProps & WrappedFieldProps - -/* https://github.com/erikras/redux-form/issues/3686 */ -export const FileUploadInputField = ({ - input: { - value: omitValue, - onChange, - onBlur, - ...inputProps - }, - meta: { touched, error, warning, submitting }, - fileName, - id, - label, - placeholder, - required, - formText, - ...props -}: FileUploadInputFieldProps) => { - const errorMessage = - error && {error} - const warningMessage = - warning && {warning} - const validMessage = OK - const isValid = (!error) && (!warning) - const formValidClass = touched ? (isValid ? 'is-valid' : 'is-invalid') : '' - const margin = 'mb-3' - const requiredClass = required ? 'required' : '' - const formTextElement = - formText - ? ({formText}) - : null - - return ( - - -

- onChange(e)} - onBlur={adaptFileEventToValue(onBlur)} - type='file' - id={id} - className={`custom-file-input ${formValidClass}`} - disabled={submitting} - {...inputProps} - {...props} - /> - -
- {formTextElement} - {touched && - (errorMessage || warningMessage || validMessage)} - - ) -} diff --git a/frontend/src/components/Common/Field/Radio.tsx b/frontend/src/components/Common/Field/Radio.tsx new file mode 100644 index 0000000..967fcf7 --- /dev/null +++ b/frontend/src/components/Common/Field/Radio.tsx @@ -0,0 +1,47 @@ +import * as React from 'react' +import { connect } from 'react-redux' +import { Field } from "formik"; + + +class RadioImpl extends React.Component { + constructor(props, context) { + super(props, context) + } + + render() { + return ( + + {({ field, form }) => ( + + )} + + ) + } +} + +type RadioProps = CustomProps + +interface RadioState {} + +interface CustomProps { + name: string + value + label: string +} + +export const Radio = connect( + (state: any, props: CustomProps) => ({ + ...state.form, + ...props, + }) +)(RadioImpl) \ No newline at end of file diff --git a/frontend/src/components/Common/Field/SingleFormField.tsx b/frontend/src/components/Common/Field/SingleFormField.tsx deleted file mode 100644 index 6b345cd..0000000 --- a/frontend/src/components/Common/Field/SingleFormField.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import * as React from 'react' -import { FormGroup, Label, Input, FormFeedback, FormText } from 'reactstrap' -import { WrappedFieldProps } from 'redux-form' - -/** - * General single form field component with validation message - * - * - */ -interface CustomFormFieldProps { - placeholder?: string, - id: string, - type: string, - className: string, - groupClassName?: string, - disable?: boolean, - options?: Array<{label: string, value: string, disabled: boolean}> - required?: boolean - formText?: string | JSX.Element -} - -export type FormFieldProps = CustomFormFieldProps & WrappedFieldProps - -export const SingleFormField = ({ - input, - label, - placeholder, - id, - type, - className, - groupClassName, - disable, - options = [] as Array<{label: string, value: string, disabled: boolean}>, - meta: { touched, error, warning, submitting }, - required, - formText -}: FormFieldProps) => { - const errorMessage = - error && {error} - const warningMessage = - warning && {warning} - const validMessage = OK - const isValid = (!error) && (!warning) - const margin = 'mb-3' - const renderOptionElements = () => ([{label: '', value: '', disabled: false}].concat(options)).map((v) => { - return ( - - ) - }) - const requiredClass = required ? 'required' : '' - const formTextElement = - formText - ? ({formText}) - : null - - return ( - - - - {options.length > 0 ? renderOptionElements() : null} - - {formTextElement} - {touched && - (errorMessage || warningMessage || validMessage)} - - ) -} diff --git a/frontend/src/components/Common/Field/Validateors.tsx b/frontend/src/components/Common/Field/Validateors.tsx deleted file mode 100644 index 716b314..0000000 --- a/frontend/src/components/Common/Field/Validateors.tsx +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Validators for form fields (Used in Redux Form) - */ - -/** - * Check whether `value` is filled (e.g. text field) - * @param value Value to be required - */ -export const required = (value) => ( - value !== undefined && value !== null && value !== '' - ? undefined : 'Required' -) -export const nameFormat = (value) => - (value && !/^[\w-]+$/.test(value) - ? 'Must be write in [0-9a-zA-Z_-]+' : undefined) -export const applicationNameFormat = nameFormat -const maxLength = max => value => - value && value.length > max ? `Must be ${max} characters or less` : undefined -export const maxLength20 = maxLength(20) diff --git a/frontend/src/components/Common/Field/index.tsx b/frontend/src/components/Common/Field/index.tsx new file mode 100644 index 0000000..b99f937 --- /dev/null +++ b/frontend/src/components/Common/Field/index.tsx @@ -0,0 +1,2 @@ +export { Checkbox } from './Checkbox' +export { Radio } from './Radio' diff --git a/frontend/src/components/misc/Forms/DeploymentSettingFormFields/index.tsx b/frontend/src/components/misc/Forms/DeploymentSettingFormFields/index.tsx deleted file mode 100644 index 9a1d3d5..0000000 --- a/frontend/src/components/misc/Forms/DeploymentSettingFormFields/index.tsx +++ /dev/null @@ -1,504 +0,0 @@ -import * as React from 'react' -import { connect } from 'react-redux' -import { Field } from 'redux-form' -import { CardTitle, UncontrolledTooltip } from 'reactstrap' - -import { KubernetesHost, Model } from '@src/apis' -import { SingleFormField } from '@common/Field/SingleFormField' -import { required, applicationNameFormat, maxLength20 } from '@common/Field/Validateors' - -declare var REACT_APP_CONFIG: any - -/** - * Form fields to set up application/service - */ -class DeploymentSettingFormFields extends React.Component { - /** - * Render form fields to setup deployment - * (Common to save applications or services) - * - */ - render() { - const { applicationType, mode } = this.props - const kubernetesFields = - applicationType === 'kubernetes' - ? this.renderScalingConfigFields() - : null - - return ( - - {this.renderBasicConfigFields()} - {kubernetesFields} - {applicationType && mode === 'add' ? this.renderMemoFields() : null} - - ) - } - - renderBasicConfigFields(): JSX.Element { - const { - onChangeApplicationType, - applicationType, - resource, - formNamePrefix - } = this.props - - const applicationTypes = [ - { label: 'Kubernetes', value: 'kubernetes' }, - { label: 'Non Kubernetes', value: 'simple' } - ] - const applicationTypeSelectBox = ( - - ) - - const nameField = ( - - ) - - const nonKubernetesHostField = - - - const hosts = - this.props.kubernetesHosts.map( - (host: KubernetesHost) => ({ label: host.displayName, value: host.id.toString() }) - ) - - const kubernetesHostField = - - - const basicConfigFields = this.renderBasicKubernetesConfigFields() - - return ( - - Basic - {applicationTypeSelectBox} - {applicationType === 'kubernetes' ? kubernetesHostField : null} - {applicationType === 'kubernetes' && resource === 'application' ? nameField : null} - {applicationType === 'simple' ? nonKubernetesHostField : null} - {applicationType === 'kubernetes' ? basicConfigFields : null} - - ) - } - - renderBasicKubernetesConfigFields(): JSX.Element { - const { environmentsToName } = REACT_APP_CONFIG - const { formNamePrefix, resource, mode } = this.props - - const serviceLevels = - Object.keys(environmentsToName).map( - (env) => ({ label: environmentsToName[env], value: env })) - - const serviceModelAssignments = - this.props.models.map( - (model: Model) => ({ label: model.name, value: model.id.toString() })) - - const serviceLevelSelectBox = ( - - ) - - const servicePortField = ( - - ) - - const serviceContainerImageField = ( - - ) - - const serviceGitUrlField = ( - - ) - - const serviceGitBranchField = ( - - ) - - const serviceBootScriptField = ( - - ) - - const modelAssignmentSelectBox = ( - - ) - - return ( - - {serviceLevelSelectBox} - {servicePortField} - {serviceContainerImageField} - {serviceGitUrlField} - {serviceGitBranchField} - {serviceBootScriptField} - {resource === 'service' && mode === 'add' ? modelAssignmentSelectBox : null} - {this.renderResourceConfigFields()} - - ) - } - - renderScalingConfigFields() { - const { formNamePrefix } = this.props - - const replicasDefaultField = ( - - ) - - const replicasMinimumField = ( - - ) - - const replicasMaximumField = ( - - ) - - const autoscaleCpuThresholdField = ( - - ) - - const policyMaxSurgeField = ( - - ) - - const policyMaxUnavailableField = ( - - ) - - const policyWaitSecondsField = ( - - ) - - return ( - - {this.renderFormCardTitleWithIcon( - 'Policy', - 'policy', - 'Deploy and monitoring policies for Kubernetes' - )} -

Replica

-
- {replicasDefaultField} - {replicasMinimumField} - {replicasMaximumField} -
-

Autoscale Threshold

- {autoscaleCpuThresholdField} -

Rolling Update

-
- {policyMaxSurgeField} - {policyMaxUnavailableField} - {policyWaitSecondsField} -
-
- ) - } - - renderResourceConfigFields() { - const { formNamePrefix, resource } = this.props - - const resourceRequestCpuField = ( - - ) - - const resourceLimitCpuField = ( - - ) - - const resourceRequestMemoryField = ( - - ) - - const resourceLimitMemoryField = ( - - ) - - return ( - -
- {resourceRequestCpuField} - {resourceLimitCpuField} -
-
- {resourceRequestMemoryField} - {resourceLimitMemoryField} -
-
- ) - } - - renderMemoFields() { - const { - formNamePrefix, - applicationType, - resource - } = this.props - - const memoFieldName = - applicationType === 'kubernetes' - ? 'commitMessage' - : 'description' - - const descriptionField = ( - - ) - - return ( - - Memo - {descriptionField} - - ) - } - - renderFormCardTitleWithIcon(titleText, iconId, tooltipText) { - return ( - - - {titleText} - - - - {tooltipText} - - - - ) - } -} - -interface CustomProps { - applicationType - formNamePrefix: string - kubernetesHosts: KubernetesHost[] - resource: string - onChangeApplicationType? - mode - models: Model[] -} - -type FormProps = - CustomProps - -export const kubernetesDeploymentDefultSettings = { - replicasDefault: 1, - replicasMaximum: 1, - replicasMinimum: 1, - autoscaleCpuThreshold: 80, - policyMaxSurge: 1, - policyMaxUnavailable: 0, - policyWaitSeconds: 300, - resourceRequestCpu: 1, - resourceLimitCpu: 1, - resourceRequestMemory: '512Mi', - resourceLimitMemory: '512Mi', - containerImage: 'rekcurd/rekcurd:python-latest', - serviceGitUrl: 'https://github.com/rekcurd/rekcurd-example.git', - serviceGitBranch: 'master', - serviceBootScript: 'start.sh' -} - -export default -connect( - (state: any, extraProps: CustomProps) => ({ - ...extraProps, - }) -)(DeploymentSettingFormFields) \ No newline at end of file From 7b00be92e1bc4962003f80c293e002a7aba9d2ac Mon Sep 17 00:00:00 2001 From: Hattori Keigo Date: Mon, 8 Apr 2019 11:38:42 +0900 Subject: [PATCH 12/92] Renew `Admin` --- .../src/components/App/Admin/constants.ts | 48 --- frontend/src/components/App/Admin/index.tsx | 272 ---------------- .../AddUserApplicationRoleModal.tsx | 13 +- .../EditUserApplicationRoleModal.tsx | 2 +- .../components/App/ApplicationAdmin/index.tsx | 302 ++++++++++++++++++ .../AddUserProjectRoleModal.tsx | 22 +- .../EditUserProjectRoleModal.tsx | 2 +- .../src/components/App/ProjectAdmin/index.tsx | 298 +++++++++++++++++ .../src/components/App/Projects/index.tsx | 145 +++++++++ 9 files changed, 770 insertions(+), 334 deletions(-) delete mode 100644 frontend/src/components/App/Admin/constants.ts delete mode 100644 frontend/src/components/App/Admin/index.tsx rename frontend/src/components/App/{Admin => ApplicationAdmin}/AddUserApplicationRoleModal.tsx (95%) rename frontend/src/components/App/{Admin => ApplicationAdmin}/EditUserApplicationRoleModal.tsx (98%) create mode 100644 frontend/src/components/App/ApplicationAdmin/index.tsx rename frontend/src/components/App/{Admin => ProjectAdmin}/AddUserProjectRoleModal.tsx (91%) rename frontend/src/components/App/{Admin => ProjectAdmin}/EditUserProjectRoleModal.tsx (98%) create mode 100644 frontend/src/components/App/ProjectAdmin/index.tsx create mode 100644 frontend/src/components/App/Projects/index.tsx diff --git a/frontend/src/components/App/Admin/constants.ts b/frontend/src/components/App/Admin/constants.ts deleted file mode 100644 index 90bf0c7..0000000 --- a/frontend/src/components/App/Admin/constants.ts +++ /dev/null @@ -1,48 +0,0 @@ -export enum dataServerMode { - local = 'local', - ceph_s3 = 'ceph_s3', - aws_s3 = 'aws_s3' -} - -export enum projectRole { - member = 'member', - admin = 'admin' -} - -export enum applicationRole { - viewer = 'viewer', - editor = 'editor', - admin = 'admin' -} - -export const apiConvertDataServerMode = (param) => { - if (param === 'DataServerModeEnum.local') { - return dataServerMode.local.toString() - } else if (param === 'DataServerModeEnum.ceph_s3') { - return dataServerMode.ceph_s3.toString() - } else if (param === 'DataServerModeEnum.aws_s3') { - return dataServerMode.aws_s3.toString() - } else { - return false - } -}; - -export const apiConvertProjectRole = (param) => { - if (param === 'ProjectRole.member') { - return projectRole.member.toString() - } else if (param === 'ProjectRole.admin') { - return projectRole.admin.toString() - } else { - return false - } -}; - -export const apiConvertApplicationRole = (param) => { - if (param === 'ApplicationRole.admin') { - return applicationRole.admin.toString() - } else if (param === 'ApplicationRole.editor') { - return applicationRole.editor.toString() - } else { - return applicationRole.viewer.toString() - } -}; diff --git a/frontend/src/components/App/Admin/index.tsx b/frontend/src/components/App/Admin/index.tsx deleted file mode 100644 index 79431d1..0000000 --- a/frontend/src/components/App/Admin/index.tsx +++ /dev/null @@ -1,272 +0,0 @@ -import * as React from 'react' -import { connect } from 'react-redux' -import { Dispatch } from 'redux' -import { withRouter, RouteComponentProps } from 'react-router' -import { Table, Row, Col, Button, Modal, ModalHeader, ModalBody } from 'reactstrap' - -import { AccessControlList, Application, UserInfo } from '@src/apis' -import { APIRequest, isAPISucceeded } from '@src/apis/Core' -import { - saveAccessControlDispatcher, - addNotification, - AddNotificationAction, - fetchAccessControlListDispatcher, - fetchApplicationByIdDispatcher, - deleteAccessControlDispatcher, -} from '@src/actions' - -import { APIRequestResultsRenderer } from '@common/APIRequestResultsRenderer' - -import AddUserModal from './AddUserModal' -import { EditUserRoleModal } from './EditUserRoleModal' - -interface StateProps { - saveAccessControlStatus: APIRequest - accessControlList: APIRequest - application: APIRequest - userInfoStatus: APIRequest - deleteAccessControlStatus: APIRequest -} - -interface DispatchProps { - saveAccessControl: (params) => Promise - addNotification: (params) => AddNotificationAction - fetchAccessControlList: (applicationId: string) => Promise - fetchApplicationById: (id: string) => Promise - deleteAccessControl: (applicaitonId: string, userUid: string) => Promise -} - -type AdminProps = StateProps & DispatchProps & RouteComponentProps<{applicationId: string}> - -interface AdminState { - isAddUserModalOpen: boolean - isEditUserModalOpen: boolean - isRemoveUserModalOpen: boolean - removeTarget?: string - editTarget?: AccessControlList - submitted: boolean -} - -class Admin extends React.Component { - constructor(props: AdminProps) { - super(props) - this.renderAccessControlList = this.renderAccessControlList.bind(this) - this.toggleAddUserModalOpen = this.toggleAddUserModalOpen.bind(this) - this.toggleEditUserModalOpen = this.toggleEditUserModalOpen.bind(this) - this.reload = this.reload.bind(this) - this.state = { - isAddUserModalOpen: false, - isEditUserModalOpen: false, - isRemoveUserModalOpen: false, - submitted: false - } - } - componentWillReceiveProps(nextProps: AdminProps) { - const { deleteAccessControlStatus, fetchAccessControlList, match } = nextProps - const { submitted } = this.state - if (submitted) { - if (isAPISucceeded(deleteAccessControlStatus)) { - nextProps.addNotification({ color: 'success', message: 'Successfully removed user' }) - fetchAccessControlList(match.params.applicationId) - } else { - nextProps.addNotification({ color: 'error', message: 'Something went wrong. Try again later' }) - } - this.setState({ submitted: false }) - } - } - componentWillMount() { - const { fetchAccessControlList, fetchApplicationById, match } = this.props - fetchAccessControlList(match.params.applicationId) - fetchApplicationById(match.params.applicationId) - } - render() { - const { accessControlList, application, userInfoStatus } = this.props - return ( - - ) - } - renderAccessControlList(results) { - const application: Application = results.application - const acl: AccessControlList[] = results.accessControlList - const userInfo: UserInfo = results.userInfoStatus - return this.renderContent(application.name, acl, userInfo) - } - renderContent(applicationName: string, acl: AccessControlList[], userInfo: UserInfo) { - const { match, saveAccessControl } = this.props - const { isAddUserModalOpen, isEditUserModalOpen, editTarget } = this.state - const tableBody = acl.map((e: AccessControlList, i: number) => { - const isMyself: boolean = e.userUid === userInfo.user.userUid - const removeButton = ( - - ) - return ( - - -
- {e.userUid} -
- - {e.userName} - {e.role.replace(/Role./, '')} - {isMyself ? null : removeButton} - - ) - }) - return ( -
- - -

- - {applicationName} -

- - - - -
- - - {this.renderConfirmRemoveUserModal()} -

- - Access Control List -

-
- - - - - - - {tableBody} -
IDNameRole
-
- ) - } - private renderConfirmRemoveUserModal(): JSX.Element { - const { deleteAccessControl, match } = this.props - const { isRemoveUserModalOpen, removeTarget } = this.state - const cancel = this.toggleRemoveUserModalOpen.bind(this) - const executeDeletion = () => { - deleteAccessControl(match.params.applicationId, removeTarget) - this.setState({ submitted: true }) - this.toggleRemoveUserModalOpen() - } - return ( - - Remove User - - Are you sure to remove {removeTarget}? - -
- - -
-
- ) - } - private toggleAddUserModalOpen() { - const { isAddUserModalOpen } = this.state - this.setState({ - isAddUserModalOpen: !isAddUserModalOpen, - }) - } - private toggleEditUserModalOpen() { - const { isEditUserModalOpen } = this.state - this.setState({ - isEditUserModalOpen: !isEditUserModalOpen, - }) - } - private toggleRemoveUserModalOpen() { - const { isRemoveUserModalOpen } = this.state - this.setState({ - isRemoveUserModalOpen: !isRemoveUserModalOpen, - }) - } - private onClickRemoveButton(acl: AccessControlList) { - return () => { - this.setState({ removeTarget: acl.userUid }) - this.toggleRemoveUserModalOpen() - } - } - private onClickEditUser(acl: AccessControlList) { - return () => { - this.toggleEditUserModalOpen() - this.setState({ editTarget: acl }) - } - } - private reload() { - const { fetchAccessControlList, match } = this.props - fetchAccessControlList(match.params.applicationId) - } -} - -export default withRouter( - connect( - (state: any): StateProps => { - return { - saveAccessControlStatus: state.saveAccessControlReducer.saveAccessControl, - accessControlList: state.fetchAccessControlListReducer.accessControlList, - application: state.fetchApplicationByIdReducer.applicationById, - userInfoStatus: state.userInfoReducer.userInfo, - deleteAccessControlStatus: state.deleteAccessControlReducer.deleteAccessControl - } - }, - (dispatch: Dispatch): DispatchProps => { - return { - saveAccessControl: (params) => saveAccessControlDispatcher(dispatch, params), - addNotification: (params) => dispatch(addNotification(params)), - fetchAccessControlList: (applicationId: string) => fetchAccessControlListDispatcher(dispatch, { applicationId }), - fetchApplicationById: (id: string) => fetchApplicationByIdDispatcher(dispatch, { id }), - deleteAccessControl: (applicationId: string, userUid: string) => deleteAccessControlDispatcher(dispatch, { applicationId, userUid }) - } - } - )(Admin) -) diff --git a/frontend/src/components/App/Admin/AddUserApplicationRoleModal.tsx b/frontend/src/components/App/ApplicationAdmin/AddUserApplicationRoleModal.tsx similarity index 95% rename from frontend/src/components/App/Admin/AddUserApplicationRoleModal.tsx rename to frontend/src/components/App/ApplicationAdmin/AddUserApplicationRoleModal.tsx index fbad147..c3d20a4 100644 --- a/frontend/src/components/App/Admin/AddUserApplicationRoleModal.tsx +++ b/frontend/src/components/App/ApplicationAdmin/AddUserApplicationRoleModal.tsx @@ -14,7 +14,7 @@ import { fetchAllUsersDispatcher, } from '@src/actions' import { APIRequestResultsRenderer } from '@components/Common/APIRequestResultsRenderer' -import { applicationRole } from './constants' +import { applicationRole } from '@components/Common/Enum' interface CustomProps { isModalOpen: boolean @@ -63,12 +63,16 @@ class AddUserApplicationRoleModal extends React.Component(saveApplicationAccessControlStatus) const failed: any = isAPIFailed(saveApplicationAccessControlStatus) && saveApplicationAccessControlStatus.error @@ -83,6 +87,7 @@ class AddUserApplicationRoleModal extends React.Component { return { addNotification: (params) => dispatch(addNotification(params)), fetchAllUsers: () => fetchAllUsersDispatcher(dispatch), - saveApplicationAccessControl: (params) => saveApplicationAccessControlDispatcher(dispatch, params) + saveApplicationAccessControl: (params: AccessControlParam) => saveApplicationAccessControlDispatcher(dispatch, params) } } diff --git a/frontend/src/components/App/Admin/EditUserApplicationRoleModal.tsx b/frontend/src/components/App/ApplicationAdmin/EditUserApplicationRoleModal.tsx similarity index 98% rename from frontend/src/components/App/Admin/EditUserApplicationRoleModal.tsx rename to frontend/src/components/App/ApplicationAdmin/EditUserApplicationRoleModal.tsx index c9e125a..13e81b2 100644 --- a/frontend/src/components/App/Admin/EditUserApplicationRoleModal.tsx +++ b/frontend/src/components/App/ApplicationAdmin/EditUserApplicationRoleModal.tsx @@ -6,7 +6,7 @@ import { Modal, ModalHeader, ModalBody, ModalFooter, Button } from 'reactstrap' import { ApplicationAccessControlList } from '@src/apis' import { APIRequest, isAPIFailed, isAPISucceeded } from '@src/apis/Core' -import { applicationRole } from "@components/App/Admin/constants"; +import { applicationRole } from "@components/Common/Enum"; interface EditUserApplicationRoleModalState { submitting: boolean diff --git a/frontend/src/components/App/ApplicationAdmin/index.tsx b/frontend/src/components/App/ApplicationAdmin/index.tsx new file mode 100644 index 0000000..b898f00 --- /dev/null +++ b/frontend/src/components/App/ApplicationAdmin/index.tsx @@ -0,0 +1,302 @@ +import * as React from 'react' +import { connect } from 'react-redux' +import { Dispatch } from 'redux' +import { withRouter, RouteComponentProps } from 'react-router' +import { Table, Row, Col, Button, Modal, ModalHeader, ModalBody } from 'reactstrap' + +import { + ApplicationAccessControlList, Application, UserInfo, AccessControlParam, FetchApplicationByIdParam +} from '@src/apis' +import { APIRequest, isAPISucceeded } from '@src/apis/Core' +import { + addNotification, + AddNotificationAction, + saveApplicationAccessControlDispatcher, + fetchApplicationAccessControlListDispatcher, + fetchApplicationByIdDispatcher, + deleteApplicationAccessControlDispatcher, + userInfoDispatcher, +} from '@src/actions' + +import { APIRequestResultsRenderer } from '@common/APIRequestResultsRenderer' + +import AddUserApplicationRoleModal from './AddUserApplicationRoleModal' +import { EditUserApplicationRoleModal } from './EditUserApplicationRoleModal' + +interface StateProps { + saveApplicationAccessControlStatus: APIRequest + fetchApplicationAccessControlListStatus: APIRequest + fetchApplicationByIdStatus: APIRequest + userInfoStatus: APIRequest + deleteApplicationAccessControlStatus: APIRequest +} + +interface DispatchProps { + addNotification: (params) => AddNotificationAction + saveApplicationAccessControl: (params: AccessControlParam) => Promise + fetchApplicationAccessControlList: (params: AccessControlParam) => Promise + fetchApplicationById: (params: FetchApplicationByIdParam) => Promise + userInfo: () => Promise + deleteApplicationAccessControl: (params: AccessControlParam) => Promise +} + +type ApplicationAdminProps = StateProps & DispatchProps & RouteComponentProps<{projectId: number, applicationId: string}> + +interface ApplicationAdminState { + isAddUserApplicationRoleModalOpen: boolean + isEditUserApplicationRoleModalOpen: boolean + isRemoveUserApplicationRoleModalOpen: boolean + removeUserApplicationRoleTarget?: string + editUserApplicationRoleTarget?: ApplicationAccessControlList + submitted: boolean +} + +class ApplicationAdmin extends React.Component { + constructor(props: ApplicationAdminProps) { + super(props) + this.renderApplicationAccessControlList = this.renderApplicationAccessControlList.bind(this) + this.toggleAddUserApplicationRoleModalOpen = this.toggleAddUserApplicationRoleModalOpen.bind(this) + this.toggleEditUserApplicationRoleModalOpen = this.toggleEditUserApplicationRoleModalOpen.bind(this) + this.reload = this.reload.bind(this) + this.state = { + isAddUserApplicationRoleModalOpen: false, + isEditUserApplicationRoleModalOpen: false, + isRemoveUserApplicationRoleModalOpen: false, + submitted: false + } + } + + componentDidMount() { + const params = { + projectId: this.props.match.params.projectId, + applicationId: this.props.match.params.applicationId + } + this.props.fetchApplicationAccessControlList(params) + this.props.fetchApplicationById(params) + } + + static getDerivedStateFromProps(nextProps: ApplicationAdminProps, prevState: ApplicationAdminState){ + const { submitted } = prevState + const params = { + projectId: nextProps.match.params.projectId, + applicationId: nextProps.match.params.applicationId + } + + if (submitted) { + if (isAPISucceeded(nextProps.deleteApplicationAccessControlStatus)) { + nextProps.addNotification({color: 'success', message: 'Successfully removed user'}) + nextProps.fetchApplicationAccessControlList(params) + } else { + nextProps.addNotification({ color: 'error', message: 'Something went wrong. Try again later' }) + } + return { submitted: false } + } + } + + render() { + const { + fetchApplicationByIdStatus, + fetchApplicationAccessControlListStatus, + userInfoStatus } = this.props + return ( + + ) + } + renderApplicationAccessControlList(results) { + const application: Application = results.fetchApplicationByIdStatus + const applicationAcl: ApplicationAccessControlList[] = results.fetchApplicationAccessControlListStatus + const userInfo: UserInfo = results.userInfoStatus + return this.renderContent(application, applicationAcl, userInfo) + } + renderContent(application: Application, applicationAcl: ApplicationAccessControlList[], + userInfo: UserInfo) { + const { match, saveApplicationAccessControl } = this.props + const { + isAddUserApplicationRoleModalOpen, isEditUserApplicationRoleModalOpen, editUserApplicationRoleTarget + } = this.state + const tableBody = applicationAcl.map((e: ApplicationAccessControlList, i: number) => { + const isMyself: boolean = e.userUid === userInfo.user.userUid + const removeButton = ( + + ) + return ( + + +
+ {e.userUid} +
+ + {e.userName} + {typeof e.role === "boolean" ? null : e.role.replace(/ApplicationRole./, '')} + {isMyself ? null : removeButton} + + ) + }) + return ( +
+ + +

+ + {application.name} +

+ + + + +
+ + + {this.renderConfirmRemoveUserModal()} +

+ + Access Control List +

+
+ + + + + + + {tableBody} +
IDNameRole
+
+ ) + } + + private renderConfirmRemoveUserModal(): JSX.Element { + const { deleteApplicationAccessControl, match } = this.props + const { isRemoveUserApplicationRoleModalOpen, removeUserApplicationRoleTarget } = this.state + const cancel = this.toggleRemoveUserModalOpen.bind(this) + const executeDeletion = () => { + deleteApplicationAccessControl({ + projectId: match.params.projectId, + applicationId: match.params.applicationId, + uid: removeUserApplicationRoleTarget + }) + this.setState({ submitted: true }) + this.toggleRemoveUserModalOpen() + } + return ( + + Remove User + + Are you sure to remove {removeUserApplicationRoleTarget}? + +
+ + +
+
+ ) + } + private toggleAddUserApplicationRoleModalOpen() { + const { isAddUserApplicationRoleModalOpen } = this.state + this.setState({ + isAddUserApplicationRoleModalOpen: !isAddUserApplicationRoleModalOpen, + }) + } + private toggleEditUserApplicationRoleModalOpen() { + const { isEditUserApplicationRoleModalOpen } = this.state + this.setState({ + isEditUserApplicationRoleModalOpen: !isEditUserApplicationRoleModalOpen, + }) + } + private toggleRemoveUserModalOpen() { + const { isRemoveUserApplicationRoleModalOpen } = this.state + this.setState({ + isRemoveUserApplicationRoleModalOpen: !isRemoveUserApplicationRoleModalOpen, + }) + } + private onClickRemoveButton(acl: ApplicationAccessControlList) { + return () => { + this.setState({ removeUserApplicationRoleTarget: acl.userUid }) + this.toggleRemoveUserModalOpen() + } + } + private onClickEditUser(acl: ApplicationAccessControlList) { + return () => { + this.toggleEditUserApplicationRoleModalOpen() + this.setState({ editUserApplicationRoleTarget: acl }) + } + } + private reload() { + const { fetchApplicationAccessControlList, match } = this.props + fetchApplicationAccessControlList({projectId: match.params.projectId, applicationId: match.params.applicationId}) + } +} + +export default withRouter( + connect( + (state: any): StateProps => { + return { + saveApplicationAccessControlStatus: state.saveApplicationAccessControlReducer.saveApplicationAccessControl, + fetchApplicationAccessControlListStatus: state.fetchApplicationAccessControlListReducer.fetchApplicationAccessControlList, + fetchApplicationByIdStatus: state.fetchApplicationByIdReducer.fetchApplicationById, + userInfoStatus: state.userInfoReducer.userInfo, + deleteApplicationAccessControlStatus: state.deleteApplicationAccessControlReducer.deleteApplicationAccessControl + } + }, + (dispatch: Dispatch): DispatchProps => { + return { + addNotification: (params) => dispatch(addNotification(params)), + saveApplicationAccessControl: (params: AccessControlParam) => saveApplicationAccessControlDispatcher(dispatch, params), + fetchApplicationAccessControlList: (params: AccessControlParam) => fetchApplicationAccessControlListDispatcher(dispatch, params), + fetchApplicationById: (params: FetchApplicationByIdParam) => fetchApplicationByIdDispatcher(dispatch, params), + userInfo: () => userInfoDispatcher(dispatch), + deleteApplicationAccessControl: (params: AccessControlParam) => deleteApplicationAccessControlDispatcher(dispatch, params) + } + } + )(ApplicationAdmin) +) diff --git a/frontend/src/components/App/Admin/AddUserProjectRoleModal.tsx b/frontend/src/components/App/ProjectAdmin/AddUserProjectRoleModal.tsx similarity index 91% rename from frontend/src/components/App/Admin/AddUserProjectRoleModal.tsx rename to frontend/src/components/App/ProjectAdmin/AddUserProjectRoleModal.tsx index e3f16ef..26a19f7 100644 --- a/frontend/src/components/App/Admin/AddUserProjectRoleModal.tsx +++ b/frontend/src/components/App/ProjectAdmin/AddUserProjectRoleModal.tsx @@ -5,7 +5,7 @@ import { Formik, Form, ErrorMessage, Field } from 'formik' import * as Yup from "yup"; import { Modal, ModalHeader, ModalBody, ModalFooter, Button } from 'reactstrap' -import {UserInfo, ProjectAccessControlList, AccessControlParam} from '@src/apis' +import { UserInfo, ProjectAccessControlList, AccessControlParam } from '@src/apis' import { APIRequest, isAPISucceeded, isAPIFailed } from '@src/apis/Core' import { addNotification, @@ -14,9 +14,10 @@ import { fetchAllUsersDispatcher, } from '@src/actions' import { APIRequestResultsRenderer } from '@components/Common/APIRequestResultsRenderer' -import { projectRole } from './constants' +import { projectRole } from '@components/Common/Enum' -interface Props extends RouteComponentProps<{projectId: number, applicationId?: string}> { + +interface CustomProps { isModalOpen: boolean acl: ProjectAccessControlList[] toggle: () => void @@ -34,7 +35,7 @@ interface DispatchProps { saveProjectAccessControl: (params: AccessControlParam) => Promise } -type AddUserProjectRoleModalProps = Props & StateProps & DispatchProps +type AddUserProjectRoleModalProps = CustomProps & StateProps & DispatchProps & RouteComponentProps<{projectId: number}> interface AddUserProjectRoleModalState { submitting: boolean @@ -63,12 +64,16 @@ class AddUserProjectRoleModal extends React.Component(saveProjectAccessControlStatus) const failed: any = isAPIFailed(saveProjectAccessControlStatus) && saveProjectAccessControlStatus.error @@ -83,6 +88,7 @@ class AddUserProjectRoleModal extends React.Component { return { addNotification: (params) => dispatch(addNotification(params)), fetchAllUsers: () => fetchAllUsersDispatcher(dispatch), - saveProjectAccessControl: (params) => saveProjectAccessControlDispatcher(dispatch, params) + saveProjectAccessControl: (params: AccessControlParam) => saveProjectAccessControlDispatcher(dispatch, params) } } export default withRouter( - connect>( + connect & CustomProps>( mapStateToProps, mapDispatchToProps )(AddUserProjectRoleModal) ) diff --git a/frontend/src/components/App/Admin/EditUserProjectRoleModal.tsx b/frontend/src/components/App/ProjectAdmin/EditUserProjectRoleModal.tsx similarity index 98% rename from frontend/src/components/App/Admin/EditUserProjectRoleModal.tsx rename to frontend/src/components/App/ProjectAdmin/EditUserProjectRoleModal.tsx index 2531e52..5d54111 100644 --- a/frontend/src/components/App/Admin/EditUserProjectRoleModal.tsx +++ b/frontend/src/components/App/ProjectAdmin/EditUserProjectRoleModal.tsx @@ -6,7 +6,7 @@ import { Modal, ModalHeader, ModalBody, ModalFooter, Button } from 'reactstrap' import { ProjectAccessControlList } from '@src/apis' import { APIRequest, isAPIFailed, isAPISucceeded } from '@src/apis/Core' -import { projectRole } from "@components/App/Admin/constants"; +import { projectRole } from "@components/Common/Enum"; interface EditUserProjectRoleModalState { submitting: boolean diff --git a/frontend/src/components/App/ProjectAdmin/index.tsx b/frontend/src/components/App/ProjectAdmin/index.tsx new file mode 100644 index 0000000..3080386 --- /dev/null +++ b/frontend/src/components/App/ProjectAdmin/index.tsx @@ -0,0 +1,298 @@ +import * as React from 'react' +import { connect } from 'react-redux' +import { Dispatch } from 'redux' +import { withRouter, RouteComponentProps } from 'react-router' +import { Table, Row, Col, Button, Modal, ModalHeader, ModalBody } from 'reactstrap' + +import { + ProjectAccessControlList, Project, UserInfo, AccessControlParam, FetchProjectByIdParam +} from '@src/apis' +import { APIRequest, isAPISucceeded } from '@src/apis/Core' +import { + addNotification, + AddNotificationAction, + saveProjectAccessControlDispatcher, + fetchProjectAccessControlListDispatcher, + fetchProjectByIdDispatcher, + deleteProjectAccessControlDispatcher, + userInfoDispatcher, +} from '@src/actions' + +import { APIRequestResultsRenderer } from '@common/APIRequestResultsRenderer' + +import AddUserProjectRoleModal from './AddUserProjectRoleModal' +import { EditUserProjectRoleModal } from './EditUserProjectRoleModal' + +interface StateProps { + saveProjectAccessControlStatus: APIRequest + fetchProjectAccessControlListStatus: APIRequest + fetchProjectByIdStatus: APIRequest + userInfoStatus: APIRequest + deleteProjectAccessControlStatus: APIRequest +} + +interface DispatchProps { + addNotification: (params) => AddNotificationAction + saveProjectAccessControl: (params: AccessControlParam) => Promise + fetchProjectAccessControlList: (params: AccessControlParam) => Promise + fetchProjectById: (params: FetchProjectByIdParam) => Promise + userInfo: () => Promise + deleteProjectAccessControl: (params: AccessControlParam) => Promise +} + +type ProjectAdminProps = StateProps & DispatchProps & RouteComponentProps<{projectId: number}> + +interface ProjectAdminState { + isAddUserProjectRoleModalOpen: boolean + isEditUserProjectRoleModalOpen: boolean + isRemoveUserProjectRoleModalOpen: boolean + removeUserProjectRoleTarget?: string + editUserProjectRoleTarget?: ProjectAccessControlList + submitted: boolean +} + +class ProjectAdmin extends React.Component { + constructor(props: ProjectAdminProps) { + super(props) + this.renderProjectAccessControlList = this.renderProjectAccessControlList.bind(this) + this.toggleAddUserProjectRoleModalOpen = this.toggleAddUserProjectRoleModalOpen.bind(this) + this.toggleEditUserProjectRoleModalOpen = this.toggleEditUserProjectRoleModalOpen.bind(this) + this.reload = this.reload.bind(this) + this.state = { + isAddUserProjectRoleModalOpen: false, + isEditUserProjectRoleModalOpen: false, + isRemoveUserProjectRoleModalOpen: false, + submitted: false + } + } + + componentDidMount() { + const params = { + projectId: this.props.match.params.projectId, + } + this.props.fetchProjectAccessControlList(params) + this.props.fetchProjectById(params) + } + + static getDerivedStateFromProps(nextProps: ProjectAdminProps, prevState: ProjectAdminState){ + const { submitted } = prevState + const params = { + projectId: nextProps.match.params.projectId + } + + if (submitted) { + if (isAPISucceeded(nextProps.deleteProjectAccessControlStatus)) { + nextProps.addNotification({color: 'success', message: 'Successfully removed user'}) + nextProps.fetchProjectAccessControlList(params) + } else { + nextProps.addNotification({ color: 'error', message: 'Something went wrong. Try again later' }) + } + return { submitted: false } + } + } + + render() { + const { + fetchProjectByIdStatus, + fetchProjectAccessControlListStatus, + userInfoStatus } = this.props + return ( + + ) + } + renderProjectAccessControlList(results) { + const project: Project = results.fetchProjectByIdStatus + const projectAcl: ProjectAccessControlList[] = results.fetchProjectAccessControlListStatus + const userInfo: UserInfo = results.userInfoStatus + return this.renderContent(project, projectAcl, userInfo) + } + renderContent(project: Project, projectAcl: ProjectAccessControlList[], + userInfo: UserInfo) { + const { match, saveProjectAccessControl } = this.props + const { + isAddUserProjectRoleModalOpen, isEditUserProjectRoleModalOpen, editUserProjectRoleTarget + } = this.state + const tableBody = projectAcl.map((e: ProjectAccessControlList, i: number) => { + const isMyself: boolean = e.userUid === userInfo.user.userUid + const removeButton = ( + + ) + return ( + + +
+ {e.userUid} +
+ + {e.userName} + {typeof e.role === "boolean" ? null : e.role.replace(/ProjectRole./, '')} + {isMyself ? null : removeButton} + + ) + }) + return ( +
+ + +

+ + {project.name} +

+ + + + +
+ + + {this.renderConfirmRemoveUserModal()} +

+ + Access Control List +

+
+ + + + + + + {tableBody} +
IDNameRole
+
+ ) + } + + private renderConfirmRemoveUserModal(): JSX.Element { + const { deleteProjectAccessControl, match } = this.props + const { isRemoveUserProjectRoleModalOpen, removeUserProjectRoleTarget } = this.state + const cancel = this.toggleRemoveUserModalOpen.bind(this) + const executeDeletion = () => { + deleteProjectAccessControl({ + projectId: match.params.projectId, + uid: removeUserProjectRoleTarget + }) + this.setState({ submitted: true }) + this.toggleRemoveUserModalOpen() + } + return ( + + Remove User + + Are you sure to remove {removeUserProjectRoleTarget}? + +
+ + +
+
+ ) + } + private toggleAddUserProjectRoleModalOpen() { + const { isAddUserProjectRoleModalOpen } = this.state + this.setState({ + isAddUserProjectRoleModalOpen: !isAddUserProjectRoleModalOpen, + }) + } + private toggleEditUserProjectRoleModalOpen() { + const { isEditUserProjectRoleModalOpen } = this.state + this.setState({ + isEditUserProjectRoleModalOpen: !isEditUserProjectRoleModalOpen, + }) + } + private toggleRemoveUserModalOpen() { + const { isRemoveUserProjectRoleModalOpen } = this.state + this.setState({ + isRemoveUserProjectRoleModalOpen: !isRemoveUserProjectRoleModalOpen, + }) + } + private onClickRemoveButton(acl: ProjectAccessControlList) { + return () => { + this.setState({ removeUserProjectRoleTarget: acl.userUid }) + this.toggleRemoveUserModalOpen() + } + } + private onClickEditUser(acl: ProjectAccessControlList) { + return () => { + this.toggleEditUserProjectRoleModalOpen() + this.setState({ editUserProjectRoleTarget: acl }) + } + } + private reload() { + const { fetchProjectAccessControlList, match } = this.props + fetchProjectAccessControlList({projectId: match.params.projectId}) + } +} + +export default withRouter( + connect( + (state: any): StateProps => { + return { + saveProjectAccessControlStatus: state.saveProjectAccessControlReducer.saveProjectAccessControl, + fetchProjectAccessControlListStatus: state.fetchProjectAccessControlListReducer.fetchProjectAccessControlList, + fetchProjectByIdStatus: state.fetchProjectByIdReducer.fetchProjectById, + userInfoStatus: state.userInfoReducer.userInfo, + deleteProjectAccessControlStatus: state.deleteProjectAccessControlReducer.deleteProjectAccessControl + } + }, + (dispatch: Dispatch): DispatchProps => { + return { + addNotification: (params) => dispatch(addNotification(params)), + saveProjectAccessControl: (params: AccessControlParam) => saveProjectAccessControlDispatcher(dispatch, params), + fetchProjectAccessControlList: (params: AccessControlParam) => fetchProjectAccessControlListDispatcher(dispatch, params), + fetchProjectById: (params: FetchProjectByIdParam) => fetchProjectByIdDispatcher(dispatch, params), + userInfo: () => userInfoDispatcher(dispatch), + deleteProjectAccessControl: (params: AccessControlParam) => deleteProjectAccessControlDispatcher(dispatch, params) + } + } + )(ProjectAdmin) +) diff --git a/frontend/src/components/App/Projects/index.tsx b/frontend/src/components/App/Projects/index.tsx new file mode 100644 index 0000000..3c19a7e --- /dev/null +++ b/frontend/src/components/App/Projects/index.tsx @@ -0,0 +1,145 @@ +import * as React from 'react' +import { connect } from 'react-redux' +import { RouterProps, RouteComponentProps } from 'react-router' +import { withRouter, Link } from 'react-router-dom' +import { Button } from 'reactstrap' + +import { APIRequest } from '@src/apis/Core' +import { Project } from '@src/apis' +import { + fetchAllProjectsDispatcher, + addNotification + } from '@src/actions' +import { APIRequestResultsRenderer } from '@common/APIRequestResultsRenderer' + +interface ProjectsState {} + +/** + * Show list of all projects + * + * Home page to move detaied page for each project + */ +class ProjectList extends React.Component { + constructor(props, context) { + super(props, context) + + this.renderProjects = this.renderProjects.bind(this) + } + + componentDidMount() { + this.props.fetchProjects() + } + + render() { + const status = this.props.projects + + return ( + + ) + + } + + renderProjects(result) { + const projects: Project[] = result.projects + const { push } = this.props.history + + const title = ( +
+

+ + Projects +

+
+ +
+
+ ) + + return ( +
+
+ {title} + {this.renderProjectListTable(projects)} +
+
+ ) + } + + /** + * Render table to show project + * each cell has link to move detailed project page + * + * @param projects {Project[]} List of projects + */ + renderProjectListTable(projects: Project[]) { + const projectListTableBody = ( + projects.map( + (value: Project) => ( + + + + {value.name} + + + + {value.description} + + + {value.registerDate.toUTCString()} + + + ) + ) + ) + + return ( + + + + + + + + {projectListTableBody} + +
NameDescriptionDate
+ ) + } +} + +export interface StateProps { + projects: APIRequest +} + +const mapStateToProps = (state) => { + return { + ...state.fetchAllProjectsReducer + } +} + +export interface DispatchProps { + fetchProjects: () => Promise, + addNotification +} + +const mapDispatchToProps = (dispatch): DispatchProps => { + return { + fetchProjects: () => fetchAllProjectsDispatcher(dispatch), + addNotification: (params) => dispatch(addNotification(params)) + } +} + +export default withRouter( + connect>( + mapStateToProps, mapDispatchToProps + )(ProjectList) +) From fd75f57fc90aef584ab17807fb765c6e2b096851 Mon Sep 17 00:00:00 2001 From: Hattori Keigo Date: Mon, 8 Apr 2019 11:39:56 +0900 Subject: [PATCH 13/92] Renew `Login` --- frontend/src/components/App/Login/fields.tsx | 50 ------------- frontend/src/components/App/Login/form.tsx | 79 ++++++++++++-------- frontend/src/components/App/Login/index.tsx | 12 ++- 3 files changed, 54 insertions(+), 87 deletions(-) delete mode 100644 frontend/src/components/App/Login/fields.tsx diff --git a/frontend/src/components/App/Login/fields.tsx b/frontend/src/components/App/Login/fields.tsx deleted file mode 100644 index f1ca077..0000000 --- a/frontend/src/components/App/Login/fields.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import * as React from 'react' -import { connect } from 'react-redux' -import { Field } from 'redux-form' -import { SingleFormField } from '@common/Field/SingleFormField' -import { required } from '@common/Field/Validateors' - -class LoginFormFields extends React.Component { - render() { - const { formNamePrefix } = this.props - const usernameField = ( - - ) - const passwordField = ( - - ) - return ( - - {usernameField} - {passwordField} - - ) - } -} - -interface CustomProps { - formNamePrefix: string -} - -type FormProps = CustomProps - -export default - connect( - (state: any, extraProps: CustomProps) => ({ - ...extraProps, - }) - )(LoginFormFields) diff --git a/frontend/src/components/App/Login/form.tsx b/frontend/src/components/App/Login/form.tsx index c84b7f2..55d96e0 100644 --- a/frontend/src/components/App/Login/form.tsx +++ b/frontend/src/components/App/Login/form.tsx @@ -1,55 +1,74 @@ import * as React from 'react' import { connect } from 'react-redux' -import { reduxForm, InjectedFormProps } from 'redux-form' -import { Button, Card, CardBody, Form } from 'reactstrap' -import LoginFormFields from './fields' +import { Button, Card, CardBody } from 'reactstrap' +import { Formik, Form, ErrorMessage, Field } from 'formik' +import * as Yup from "yup"; + + +const LoginSchema = Yup.object().shape({ + username: Yup.string() + .required('Required') + .max(512), + password: Yup.string() + .required('Required'), +}); class LoginFormImpl extends React.Component { render() { - const { handleSubmit, onSubmit } = this.props - const loginForm = this.renderLoginForm() + const { onSubmit } = this.props return (

Login

-
- {loginForm} -
+ + {({ errors, touched }) => ( +
+ + + + {errors.username && touched.username ? ( +
{errors.username}
+ ) : null} + + + {errors.password && touched.password ? ( +
{errors.password}
+ ) : null} + +
+
+ + + + + +
+ )} +
) } - - renderLoginForm() { - return ( - - - - - - - - - ) - } } interface LoginFormCustomProps { onSubmit } -type LoginFormProps = LoginFormCustomProps & InjectedFormProps<{}, LoginFormCustomProps> +type LoginFormProps = LoginFormCustomProps export const LoginForm = connect( (state: any, extraProps: any) => ({ }) - )(reduxForm<{}, LoginFormCustomProps>({ - form: 'loginForm', - })(LoginFormImpl)) + )(LoginFormImpl) diff --git a/frontend/src/components/App/Login/index.tsx b/frontend/src/components/App/Login/index.tsx index 713428a..644d8ed 100644 --- a/frontend/src/components/App/Login/index.tsx +++ b/frontend/src/components/App/Login/index.tsx @@ -2,14 +2,12 @@ import * as React from 'react' import { connect } from 'react-redux' import { withRouter, RouterProps } from 'react-router' import { Row, Col } from 'reactstrap' -import { InjectedFormProps } from 'redux-form' import { loginDispatcher, userInfoDispatcher, addNotification } from '@src/actions' -import { FormCustomProps } from '@components/App/SaveApplication/ApplicationDeploymentForm' import { LoginForm } from './form' -import { isAPISucceeded, APIRequest, APIStatusSuccess, APIRequestStatusList, JWT_TOKEN_KEY, isAPIFailed, isAPIUnauthorized } from '@src/apis/Core' -import { AuthToken } from '@src/apis' +import { isAPISucceeded, APIRequest, APIStatusSuccess, JWT_TOKEN_KEY, isAPIFailed, isAPIUnauthorized } from '@src/apis/Core' +import { AuthToken, LoginParam } from '@src/apis' class Login extends React.Component { constructor(props, context) { @@ -68,7 +66,7 @@ interface StateProps { loginStatus: APIRequest } -type LoginProps = StateProps & DispatchProps & RouterProps & InjectedFormProps<{}, FormCustomProps> +type LoginProps = StateProps & DispatchProps & RouterProps interface LoginState { submitting: boolean @@ -82,14 +80,14 @@ const mapStateToProps = (state) => { interface DispatchProps { fetchUserInfo: () => Promise - submitLogin: (params) => Promise + submitLogin: (params: LoginParam) => Promise addNotification: (params) => Promise } const mapDispatchToProps = (dispatch): DispatchProps => { return { fetchUserInfo: () => userInfoDispatcher(dispatch), - submitLogin: (params) => loginDispatcher(dispatch, params), + submitLogin: (params: LoginParam) => loginDispatcher(dispatch, params), addNotification: (params) => dispatch(addNotification(params)) } } From 7c2dfd858715558d7ed9646575a4a6510bc25567 Mon Sep 17 00:00:00 2001 From: Hattori Keigo Date: Mon, 8 Apr 2019 11:40:41 +0900 Subject: [PATCH 14/92] Renew `Kubernetes` --- .../App/Kubernetes/Host/HostForm.tsx | 433 +++++------------- .../components/App/Kubernetes/Host/index.tsx | 94 ++-- .../components/App/Kubernetes/Hosts/index.tsx | 183 ++++---- .../App/Kubernetes/SideMenu/index.tsx | 38 ++ .../src/components/App/Kubernetes/index.tsx | 7 +- 5 files changed, 285 insertions(+), 470 deletions(-) create mode 100644 frontend/src/components/App/Kubernetes/SideMenu/index.tsx diff --git a/frontend/src/components/App/Kubernetes/Host/HostForm.tsx b/frontend/src/components/App/Kubernetes/Host/HostForm.tsx index bb54f3e..1fe73f0 100644 --- a/frontend/src/components/App/Kubernetes/Host/HostForm.tsx +++ b/frontend/src/components/App/Kubernetes/Host/HostForm.tsx @@ -1,346 +1,143 @@ import * as React from 'react' import { connect } from 'react-redux' -import { reduxForm, Field, InjectedFormProps } from 'redux-form' -import { Card, CardBody, Form, Button, CardTitle, UncontrolledTooltip } from 'reactstrap' - -import { FileUploadInputField } from '@common/Field/FileUploadInputField' -import { SingleFormField } from '@common/Field/SingleFormField' -import { required } from '@common/Field/Validateors' - -class HostFormImpl extends React.Component { +import { Card, CardBody, Button, CardTitle, UncontrolledTooltip } from 'reactstrap' + +import * as Yup from "yup"; +import { ErrorMessage, Field, Form, Formik } from "formik"; + + +const AddKubernetesSchema = Yup.object().shape({ + displayName: Yup.string() + .required('Required') + .max(128), + description: Yup.string(), + exposedHost: Yup.string() + .required('Required') + .max(512), + exposedPort: Yup.number() + .required('Required') + .positive() + .integer(), + configPath: Yup.mixed() + .required('Required') +}); + +const EditKubernetesSchema = Yup.object().shape({ + displayName: Yup.string() + .max(128), + description: Yup.string(), + exposedHost: Yup.string() + .max(512), + exposedPort: Yup.number() + .positive() + .integer(), + configPath: Yup.mixed() +}); + +class HostFormImpl extends React.Component { constructor(props, context) { super(props, context) - - this.onChange = this.onChange.bind(this) - this.state = { - fileName: null - } - } - - /** - * Show specified file name on the field - * @param event - */ - onChange(event) { - if (event.target.files[0]) { - this.setState({ fileName: event.target.files[0].name }) - } } render() { - const { onSubmit, handleSubmit, mode } = this.props + const { onSubmit, onCancel, method } = this.props + const initialValues = { + ...this.props.initialValues, + configPath: null, + } return (

- {mode === 'add' ? 'Add' : 'Edit'} Kubernetes Host + {method === 'post' ? 'Add' : 'Edit'} Kubernetes Host

-
- {this.renderFormBody()} - {this.renderDbConfigFields()} - {this.renderModelConfigFields()} - {this.renderButtons()} -
-
- ) - } - - renderFormBody() { - const { fileName } = this.state - const { mode } = this.props - - const nameForm = ( - - ) - - const uploadConfigForm = ( - - ) - - const descriptionForm = ( - - ) - - const dnsForm = ( - - ) - - return ( - - - Basic - - {nameForm} - {uploadConfigForm} - {dnsForm} - {descriptionForm} - - ) - } - - /** - * Render database config fields - * (Only for kubernetes) - */ - renderDbConfigFields(): JSX.Element { - const { - mode - } = this.props - - const dbMySQLHostField = ( - - ) - - const dbMySQLPortField = ( - - ) - - const dbMySQLDBNameField = ( - - ) - - const dbMySQLUserField = ( - - ) - - const dbMySQLPasswordField = ( - - ) - - const mysqlHostFields = ( - -
Input following configurations of your MySQL server
-
- {dbMySQLHostField} - {dbMySQLPortField} -
-
- {dbMySQLDBNameField} - {dbMySQLUserField} - {dbMySQLPasswordField} -
-
- ) - - return ( - - - {this.renderFormCardTitleWithIcon( - 'Database', - 'database', - 'Specify configs of database that is required to manage workers' + + {({ errors, touched, setFieldValue, isSubmitting }) => ( +
+ + + + {errors.displayName && touched.displayName ? ( +
{errors.displayName}
+ ) : null} + + { + setFieldValue("configPath", event.currentTarget.files[0]); + }} /> + {errors.configPath && touched.configPath ? ( +
{errors.configPath}
+ ) : null} + + + {errors.description && touched.description ? ( +
{errors.description}
+ ) : null} + + + {errors.exposedHost && touched.exposedHost ? ( +
{errors.exposedHost}
+ ) : null} + + + {errors.exposedPort && touched.exposedPort ? ( +
{errors.exposedPort}
+ ) : null} + +
+
+ + + + + {' '} + + + +
)} - {mysqlHostFields} -
-
- ) - } - - renderFormCardTitleWithIcon(titleText, iconId, tooltipText) { - return ( - - - {titleText} - - - - {tooltipText} - - - - ) - } - - renderModelConfigFields() { - const { mode } = this.props - - const hostModelDirField = ( - - ) - - const podModelDirField = ( - - ) - - return ( - - - Model Storage - {hostModelDirField} - {podModelDirField} - - - ) - } - - /** - * Render control buttons - * - * Put on footer of this modal - */ - renderButtons() { - const { onCancel, submitting, mode } = this.props - - if (submitting) { - return ( -
-
- Submitting... -
- ) - } - - return ( - - - - {' '} - - - + +
) } } -type HostFormProps = - CustomProps - & StateProps - & InjectedFormProps<{}, CustomProps> +type HostFormProps = CustomProps const defaultInitialValues = { - hostModelDir: '/mnt/rekcurd-model', - podModelDir: '/mnt/rekcurd-model' -} - -interface StateProps { - fileName: string + displayName: '', + description: '', + exposedHost: 'localhost', + exposedPort: 31380 } export interface CustomProps { onCancel onSubmit - mode: string - initialValues?: any + method: string + initialValues?: { + displayName: string + description: string + exposedHost: string + exposedPort: number + } } -const mapStateToProps = (state: any, extraProps: CustomProps) => ({ - ...state.form, - initialValues: { - [extraProps.mode]: { - kubernetes: { - ...defaultInitialValues, - } +export const HostForm = connect( + (state: any, extraProps: CustomProps) => ({ + ...state.form, + initialValues: { + ...defaultInitialValues, + ...extraProps.initialValues }, - ...extraProps.initialValues - } -}) - -export const HostForm = connect(mapStateToProps)( - reduxForm<{}, CustomProps>( - { - form: 'kubernetesHostForm', - touchOnChange: true - } - )(HostFormImpl) -) + }) +)(HostFormImpl) diff --git a/frontend/src/components/App/Kubernetes/Host/index.tsx b/frontend/src/components/App/Kubernetes/Host/index.tsx index f78de32..923d70a 100644 --- a/frontend/src/components/App/Kubernetes/Host/index.tsx +++ b/frontend/src/components/App/Kubernetes/Host/index.tsx @@ -2,19 +2,18 @@ import * as React from 'react' import { connect } from 'react-redux' import { RouterProps } from 'react-router' import { withRouter, RouteComponentProps } from 'react-router-dom' -import { InjectedFormProps } from 'redux-form' import { APIRequest, isAPISucceeded, isAPIFailed } from '@src/apis/Core' -import { KubernetesHost, SaveKubernetesHostParam } from '@src/apis' -import { saveKubernetesHostDispatcher, fetchKubernetesHostByIdDispatcher, addNotification } from '@src/actions' -import { HostForm, CustomProps as FormCustomProps } from './HostForm' +import { Kubernetes, KubernetesParam, FetchKubernetesByIdParam } from '@src/apis' +import { saveKubernetesDispatcher, fetchKubernetesByIdDispatcher, addNotification } from '@src/actions' +import { HostForm } from './HostForm' import { APIRequestResultsRenderer } from '@common/APIRequestResultsRenderer' /** * Page for adding/editing Kubernetes host * */ -class Host extends React.Component { +class Host extends React.Component { constructor(props, context) { super(props, context) @@ -33,15 +32,18 @@ class Host extends React.Component(saveKubernetesHostStatus) && saveKubernetesHostStatus.result - const failed: boolean = (isAPISucceeded(saveKubernetesHostStatus) && !saveKubernetesHostStatus.result) || - isAPIFailed(saveKubernetesHostStatus) + const succeeded: boolean = isAPISucceeded(saveKubernetesStatus) && saveKubernetesStatus.result + const failed: boolean = (isAPISucceeded(saveKubernetesStatus) && !saveKubernetesStatus.result) || + isAPIFailed(saveKubernetesStatus) if (succeeded) { - this.setState({notified: true}) - push('/settings/kubernetes/hosts') + push(`/projects/${nextProps.match.params.projectId}/kubernetes`) nextProps.addNotification({ color: 'success', message: 'Successfully saved host' }) + return {notified: true} } else if (failed) { - this.setState({notified: true}) nextProps.addNotification({ color: 'error', message: 'Something went wrong. Try again later' }) + return {notified: true} } } } render() { - const { mode } = this.props - if (mode === 'edit') { + const { method } = this.props + if (method === 'patch') { return ( ) @@ -96,7 +100,7 @@ class Host extends React.Component ) } @@ -107,8 +111,8 @@ class Host extends React.Component ) } @@ -117,43 +121,47 @@ class Host extends React.Component - & RouterProps & RouteComponentProps<{ kubernetesId?: string }> + & RouterProps & RouteComponentProps<{ projectId: number, kubernetesId?: number }> + +interface HostState { + submitting: boolean, + notified: boolean +} interface StateProps { - saveKubernetesHostStatus: APIRequest - fetchKubernetesHostByIdStatus: APIRequest + saveKubernetesStatus: APIRequest + fetchKubernetesByIdStatus: APIRequest } interface CustomProps { - mode: string + method: string } const mapStateToProps = (state: any, extraProps: CustomProps) => ( { - saveKubernetesHostStatus: state.saveKubernetesHostReducer.saveKubernetesHost, - fetchKubernetesHostByIdStatus: state.fetchKubernetesHostByIdReducer.kubernetesHostById, + saveKubernetesStatus: state.saveKubernetesReducer.saveKubernetes, + fetchKubernetesByIdStatus: state.fetchKubernetesByIdReducer.fetchKubernetesById, ...state.form, ...extraProps } ) export interface DispatchProps { - saveKubernetesHost: (params: SaveKubernetesHostParam) => Promise - fetchKubernetesHostById: (params) => Promise + saveKubernetes: (params: KubernetesParam) => Promise + fetchKubernetesById: (params: FetchKubernetesByIdParam) => Promise addNotification: (params) => any } const mapDispatchToProps = (dispatch): DispatchProps => { return { - saveKubernetesHost: (params: SaveKubernetesHostParam) => saveKubernetesHostDispatcher(dispatch, params), - fetchKubernetesHostById: (params) => fetchKubernetesHostByIdDispatcher(dispatch, params), + saveKubernetes: (params: KubernetesParam) => saveKubernetesDispatcher(dispatch, params), + fetchKubernetesById: (params: FetchKubernetesByIdParam) => fetchKubernetesByIdDispatcher(dispatch, params), addNotification: (params) => dispatch(addNotification(params)) } } export default withRouter( - connect & CustomProps>( + connect & CustomProps>( mapStateToProps, mapDispatchToProps )(Host) ) diff --git a/frontend/src/components/App/Kubernetes/Hosts/index.tsx b/frontend/src/components/App/Kubernetes/Hosts/index.tsx index ec9764b..babe336 100644 --- a/frontend/src/components/App/Kubernetes/Hosts/index.tsx +++ b/frontend/src/components/App/Kubernetes/Hosts/index.tsx @@ -1,16 +1,14 @@ import * as React from 'react' import { connect } from 'react-redux' -import { RouterProps } from 'react-router' import { withRouter, RouteComponentProps, Link } from 'react-router-dom' import { Row, Col, Table, Button, Tooltip, Modal, ModalBody, ModalHeader } from 'reactstrap' import { APIRequestResultsRenderer } from '@common/APIRequestResultsRenderer' import { APIRequest, isAPIFailed, isAPISucceeded } from '@src/apis/Core' -import { KubernetesHost } from '@src/apis' +import { Kubernetes, FetchKubernetesByIdParam, IdParam } from '@src/apis' import { - fetchAllKubernetesHostsDispatcher, - deleteKubernetesHostDispatcher, - syncKubernetesStatusDispatcher, + fetchAllKubernetesDispatcher, + deleteKubernetesDispatcher, addNotification, } from '@src/actions' @@ -20,25 +18,46 @@ import { * - List up existing Kubernetes hosts * - Move to a page for adding/editing hosts (`Host`) */ -class Hosts extends React.Component { +class Hosts extends React.Component { constructor(props, context) { super(props, context) this.state = { isDeleteModalOpen: false, tooltipOpen: {}, deletionSubmitted: false, - syncSubmitted: false, deletionNotified: false, - syncNotified: false, - deletionTargetHost: { id: null, displayName: null } + deletionTarget: { id: null, displayName: null } } - this.renderKubernetesHosts = this.renderKubernetesHosts.bind(this) + this.renderKubernetes = this.renderKubernetes.bind(this) this.changeDeletionTarget = this.changeDeletionTarget.bind(this) this.toggleTooltip = this.toggleTooltip.bind(this) this.toggleDeleteModal = this.toggleDeleteModal.bind(this) } + componentDidMount() { + this.props.fetchKubernetes({projectId: this.props.match.params.projectId}) + } + + static getDerivedStateFromProps(nextProps: KubernetesProps, prevState: KubernetesState){ + const { deleteKubernetesStatus } = nextProps + const { deletionSubmitted, deletionNotified } = prevState + + if (deletionSubmitted && !deletionNotified) { + const succeeded: boolean = isAPISucceeded(deleteKubernetesStatus) && deleteKubernetesStatus.result + const failed: boolean = (isAPISucceeded(deleteKubernetesStatus) && !deleteKubernetesStatus.result) || isAPIFailed(deleteKubernetesStatus) + + if (succeeded) { + nextProps.fetchKubernetes({projectId: nextProps.match.params.projectId}) + nextProps.addNotification({ color: 'success', message: 'Successfully deleted host' }) + return {deletionSubmitted: false, deletionNotified: true} + } else if (failed) { + nextProps.addNotification({ color: 'error', message: 'Something went wrong. Try again later' }) + return {deletionSubmitted: false, deletionNotified: true} + } + } + } + toggleDeleteModal() { this.setState({ isDeleteModalOpen: !this.state.isDeleteModalOpen @@ -47,17 +66,17 @@ class Hosts extends React.Component(status) && status.result - const failed: boolean = (isAPISucceeded(status) && !status.result) || - isAPIFailed(status) - - if (succeeded) { - this.setState({[submitted]: false, [notified]: true}) - push('/settings/kubernetes/hosts') - this.props.fetchKubernetesHosts() - this.props.addNotification({ color: 'success', message: notificationText }) - } else if (failed) { - this.setState({[submitted]: false, [notified]: true}) - this.props.addNotification({ color: 'error', message: 'Something went wrong. Try again later' }) - } - } - } - - componentWillMount() { - this.props.fetchKubernetesHosts() - } - render() { return ( ) } - renderKubernetesHosts(status) { - const kubernetesHosts: KubernetesHost[] = status.hosts - const { deletionSubmitted, syncSubmitted } = this.state - const submitted = deletionSubmitted || syncSubmitted + renderKubernetes(status) { + const kubernetesHosts: Kubernetes[] = status.fetchAllKubernetes + const { deletionSubmitted } = this.state + const submitted = deletionSubmitted const { push } = this.props.history - const submitSync = () => { - this.setState({syncSubmitted: true, syncNotified: false}) - this.props.syncAllKubernetesStatus({}) - } + const { projectId } = this.props.match.params const title = (

- Hosts + Kubernetes

- - {` `} -
@@ -172,17 +138,17 @@ class Hosts extends React.Component ( - + (value: Kubernetes) => ( + {value.displayName} @@ -192,20 +158,20 @@ class Hosts extends React.Component - {value.dnsName} + {value.exposedHost}:{value.exposedPort} - {value.registeredDate.toUTCString()} + {value.registerDate.toUTCString()} { this.changeDeletionTarget(value.id, value.displayName); this.toggleDeleteModal() }} + id={`k8shost-delete-${value.kubernetesId}`} + onClick={() => { this.changeDeletionTarget(value.kubernetesId, value.displayName); this.toggleDeleteModal() }} > - - Delete host + + Delete this host @@ -217,7 +183,7 @@ class Hosts extends React.Component - NameDescriptionDNSDate + NameDescriptionExposed Host:PortRegistered Date @@ -229,8 +195,8 @@ class Hosts extends React.Component { this.toggleDeleteModal() @@ -244,7 +210,7 @@ class Hosts extends React.Component
- @@ -257,37 +223,42 @@ class Hosts extends React.Component ) } +} + +type KubernetesProps = StateProps & DispatchProps & RouteComponentProps<{projectId: number}> +interface KubernetesState { + isDeleteModalOpen: boolean, + tooltipOpen: {}, + deletionSubmitted: boolean, + deletionNotified: boolean, + deletionTarget: { id: string, displayName: string } } interface StateProps { - fetchAllKubernetesHostsStatus: APIRequest - deleteKubernetesHostStatus: APIRequest - syncAllKubernetesStatusStatus: APIRequest + fetchAllKubernetesStatus: APIRequest + deleteKubernetesStatus: APIRequest } const mapStateToProps = (state): StateProps => { return { - fetchAllKubernetesHostsStatus: state.fetchAllKubernetesHostsReducer.kubernetesHosts, - deleteKubernetesHostStatus: state.deleteKubernetesHostReducer.deleteKubernetesHost, - syncAllKubernetesStatusStatus: state.syncKubernetesStatusReducer.syncKubernetesStatus + fetchAllKubernetesStatus: state.fetchAllKubernetesReducer.fetchAllKubernetes, + deleteKubernetesStatus: state.deleteKubernetesReducer.deleteKubernetes, } } interface DispatchProps { - fetchKubernetesHosts: () => Promise - deleteKubernetesHost: (params) => Promise - syncAllKubernetesStatus: (params) => Promise + fetchKubernetes: (params: FetchKubernetesByIdParam) => Promise + deleteKubernetes: (params: IdParam) => Promise addNotification } const mapDispatchToProps = (dispatch): DispatchProps => { return { - fetchKubernetesHosts: () => fetchAllKubernetesHostsDispatcher(dispatch), - deleteKubernetesHost: (params) => deleteKubernetesHostDispatcher(dispatch, params), - syncAllKubernetesStatus: () => syncKubernetesStatusDispatcher(dispatch, {}), + fetchKubernetes: (params: FetchKubernetesByIdParam) => fetchAllKubernetesDispatcher(dispatch, params), + deleteKubernetes: (params: IdParam) => deleteKubernetesDispatcher(dispatch, params), addNotification: (params) => dispatch(addNotification(params)) } } -export default withRouter(connect>(mapStateToProps, mapDispatchToProps)(Hosts)) +export default withRouter(connect>(mapStateToProps, mapDispatchToProps)(Hosts)) diff --git a/frontend/src/components/App/Kubernetes/SideMenu/index.tsx b/frontend/src/components/App/Kubernetes/SideMenu/index.tsx new file mode 100644 index 0000000..82a20df --- /dev/null +++ b/frontend/src/components/App/Kubernetes/SideMenu/index.tsx @@ -0,0 +1,38 @@ +import * as React from 'react' +import { SideMenu, SideMenuParam } from '@common/SideMenu' +import { Col, Row } from 'reactstrap' + +/** + * Settings page + * + * @param props + */ +class Settings extends React.Component<{}> { + render() { + const sideMenuParam: SideMenuParam = { + title:
Kubernetes
, + id: 'kubernetes-side-menu', + contents: [ + { + title:
{` `}List
, + items: [{text: 'TBD', path: '/settings/kubernetes/hosts/', icon: 'plug'}] + } + ] + } + + return ( + + { /* Side menu */} + + + + { /* Main content */} + + {this.props.children} + + + ) + } +} + +export default Settings diff --git a/frontend/src/components/App/Kubernetes/index.tsx b/frontend/src/components/App/Kubernetes/index.tsx index 01b88f8..a641482 100644 --- a/frontend/src/components/App/Kubernetes/index.tsx +++ b/frontend/src/components/App/Kubernetes/index.tsx @@ -1,6 +1,10 @@ import * as React from 'react' import { withRouter } from 'react-router' +export { default as SideMenu } from './SideMenu' +export { default as Hosts } from './Hosts' +export { default as Host } from './Host' + class KubernetesImpl extends React.Component<{}, {}> { constructor(props, context) { super(props, context) @@ -11,7 +15,4 @@ class KubernetesImpl extends React.Component<{}, {}> { } } -export { default as Host } from './Host' -export { default as Hosts } from './Hosts' - export const Kubernetes = withRouter(KubernetesImpl) From d7c32f3ce47f1e702b81935be9684183085a493a Mon Sep 17 00:00:00 2001 From: Hattori Keigo Date: Mon, 8 Apr 2019 11:41:38 +0900 Subject: [PATCH 15/92] Add `SaveProject` --- .../App/SaveProject/ProjectDeploymentForm.tsx | 111 ++++++++++++++++ .../src/components/App/SaveProject/index.tsx | 118 ++++++++++++++++++ 2 files changed, 229 insertions(+) create mode 100644 frontend/src/components/App/SaveProject/ProjectDeploymentForm.tsx create mode 100644 frontend/src/components/App/SaveProject/index.tsx diff --git a/frontend/src/components/App/SaveProject/ProjectDeploymentForm.tsx b/frontend/src/components/App/SaveProject/ProjectDeploymentForm.tsx new file mode 100644 index 0000000..52e48c0 --- /dev/null +++ b/frontend/src/components/App/SaveProject/ProjectDeploymentForm.tsx @@ -0,0 +1,111 @@ +import * as React from 'react' +import { connect } from 'react-redux' +import { Card, CardBody, Button } from 'reactstrap' +import { Formik, Form, ErrorMessage, Field } from 'formik' +import * as Yup from "yup"; + + +const ProjectSchema = Yup.object().shape({ + display_name: Yup.string() + .required('Required') + .max(128), + description: Yup.string(), +}); + +class ProjectDeploymentFormImpl extends React.Component { + /** + * Render form bodies to add project + * + */ + render() { + const { onSubmit } = this.props + + return ( + +

+ + Add Project +

+ + + {({ errors, touched }) => ( +
+ + + + {errors.display_name && touched.display_name ? ( +
{errors.display_name}
+ ) : null} + + + {errors.description && touched.description ? ( +
{errors.description}
+ ) : null} + +
+
+ {this.renderButtons()} +
+ )} +
+
+ ) + } + + /** + * Render control buttons + * + * Put on footer of this modal + */ + renderButtons() { + const { onSubmit, onCancel, submitting } = this.props + if (submitting) { + return ( + + +
+ Submitting... + + + ) + } + + return ( + + + + {' '} + + + + ) + } +} + +export interface FormCustomProps { + submitting + onCancel + onSubmit +} + +type AddProjectFormProps = FormCustomProps + +export const ProjectDeloymentForm = + connect( + (state: any, extraProps: FormCustomProps) => ({ + ...extraProps, + ...state.form + }) + )(ProjectDeploymentFormImpl) diff --git a/frontend/src/components/App/SaveProject/index.tsx b/frontend/src/components/App/SaveProject/index.tsx new file mode 100644 index 0000000..d6567a0 --- /dev/null +++ b/frontend/src/components/App/SaveProject/index.tsx @@ -0,0 +1,118 @@ +import * as React from 'react' +import { connect } from 'react-redux' +import { RouterProps } from 'react-router' +import { withRouter, RouteComponentProps } from 'react-router-dom' +import { Row, Col, Alert } from 'reactstrap' + +import { ProjectParam } from '@src/apis' +import { APIRequest, isAPISucceeded, isAPIFailed } from '@src/apis/Core' +import { saveProjectDispatcher, addNotification } from '@src/actions' +import { FormCustomProps, ProjectDeloymentForm } from './ProjectDeploymentForm' + +/** + * Page for adding project + * + */ +class AddProject extends React.Component { + constructor(props, context) { + super(props, context) + + this.onSubmit = this.onSubmit.bind(this) + this.onCancel = this.onCancel.bind(this) + this.state = { + submitting: false, + notified: false, + } + } + + static getDerivedStateFromProps(nextProps: AddProjectProps, prevState: AddProjectState){ + const { saveProjectStatus } = nextProps + const { submitting, notified } = prevState + const { push } = nextProps.history + + // Close modal when API successfully finished + if (submitting && !notified) { + const succeeded: boolean = isAPISucceeded(saveProjectStatus) && saveProjectStatus.result + const failed: boolean = (isAPISucceeded(saveProjectStatus) && !saveProjectStatus.result) || + isAPIFailed(saveProjectStatus) + if (succeeded) { + nextProps.addNotification({ color: 'success', message: 'Successfully added project' }) + push('/projects/') + return { notified: true } + } else if (failed) { + nextProps.addNotification({ color: 'error', message: 'Something went wrong. Try again later' }) + return { notified: true } + } + } + } + + /** + * Handle cancel button + * + * Reset form and move to project list page + */ + onCancel() { + const { push } = this.props.history + push('/projects') + } + + onSubmit(parameters) { + const { saveProject } = this.props + this.setState({ submitting: true, notified: false }) + + return saveProject( + { + ...parameters, + } + ) + } + + render() { + return ( + + + + + + ) + } +} + +type AddProjectProps = StateProps & DispatchProps & RouterProps +interface AddProjectState { + submitting: boolean + notified: boolean +} + +interface StateProps { + saveProjectStatus: APIRequest +} +const mapStateToProps = (state: any, extraProps: FormCustomProps) => ( + { + saveProjectStatus: state.saveProjectReducer.saveProject, + ...state.form, + ...extraProps + } +) + +export interface DispatchProps { + saveProject: (params: ProjectParam) => Promise + addNotification: (params) => Promise +} + +const mapDispatchToProps = (dispatch): DispatchProps => { + return { + saveProject: (params: ProjectParam) => saveProjectDispatcher(dispatch, params), + addNotification: (params) => dispatch(addNotification(params)) + } +} + +export default withRouter( + connect & FormCustomProps>( + mapStateToProps, mapDispatchToProps + )(AddProject) +) From 57f3056614068acaa3235bfb78105403b19694ad Mon Sep 17 00:00:00 2001 From: Hattori Keigo Date: Mon, 8 Apr 2019 11:41:59 +0900 Subject: [PATCH 16/92] Add `DataServer` --- .../App/DataServer/DataServerForm.tsx | 224 ++++++++++++++++++ .../src/components/App/DataServer/index.tsx | 168 +++++++++++++ 2 files changed, 392 insertions(+) create mode 100644 frontend/src/components/App/DataServer/DataServerForm.tsx create mode 100644 frontend/src/components/App/DataServer/index.tsx diff --git a/frontend/src/components/App/DataServer/DataServerForm.tsx b/frontend/src/components/App/DataServer/DataServerForm.tsx new file mode 100644 index 0000000..ea0166e --- /dev/null +++ b/frontend/src/components/App/DataServer/DataServerForm.tsx @@ -0,0 +1,224 @@ +import * as React from 'react' +import { connect } from 'react-redux' +import { Card, CardBody, Button, CardTitle, UncontrolledTooltip } from 'reactstrap' +import { dataServerMode } from '@components/Common/Enum' + +import * as Yup from "yup"; +import { ErrorMessage, Field, Form, Formik } from "formik"; + + +const DataServerSchema = Yup.object().shape({ + dataServerMode: Yup.string() + .oneof([dataServerMode.local.toString(), dataServerMode.ceph_s3.toString(), dataServerMode.aws_s3.toString()]) + .required('Required'), + cephAccessKey: Yup.string() + .max(128), + cephSecretKey: Yup.string() + .max(128), + cephHost: Yup.string() + .max(512), + cephPort: Yup.number() + .positive() + .integer(), + cephIsSecure: Yup.bool(), + cephBucketName: Yup.string() + .max(128), + awsAccessKey: Yup.string() + .max(128), + awsSecretKey: Yup.string() + .max(128), + awsBucketName: Yup.string() + .max(128) +}); + +class DataServerFormImpl extends React.Component { + constructor(props, context) { + super(props, context) + this.state = { + dataServerMode: this.props.initialValues.dataServerMode + } + + this.onChange = this.onChange.bind(this) + } + + onChange(event) { + if (this.state.dataServerMode !== event.target) { + this.setState({dataServerMode: event.target}) + } + } + + private renderModes() { + const modes = Object.values(dataServerMode).map((modeName: string) => { + return ( + + ) + }) + return ( + + {modes} + + ) + } + + render() { + const { onSubmit, onCancel, method } = this.props + const initialValues = { + ...this.props.initialValues + } + let fields = null + if (this.state.dataServerMode === dataServerMode.ceph_s3.toString()) { + fields = ( + + + + + + + + + + + ) + } else if (this.state.dataServerMode === dataServerMode.aws_s3.toString()) { + fields = ( + + + + + + + + ) + } else { + fields = null + } + + return ( +
+

+ + {method === 'post' ? 'Add' : 'Edit'} DataServer +

+ + {({ errors, touched, isSubmitting }) => ( +
+ + + {this.renderModes()} + {errors.dataServerMode && touched.dataServerMode ? ( +
{errors.dataServerMode}
+ ) : null} + +
+
+ + {fields} + {errors.cephAccessKey && touched.cephAccessKey ? ( +
{errors.cephAccessKey}
+ ) : null} + + {errors.cephSecretKey && touched.cephSecretKey ? ( +
{errors.cephSecretKey}
+ ) : null} + + {errors.cephHost && touched.cephHost ? ( +
{errors.cephHost}
+ ) : null} + + {errors.cephPort && touched.cephPort ? ( +
{errors.cephPort}
+ ) : null} + + {errors.cephIsSecure && touched.cephIsSecure ? ( +
{errors.cephIsSecure}
+ ) : null} + + {errors.cephBucketName && touched.cephBucketName ? ( +
{errors.cephBucketName}
+ ) : null} + + {errors.awsAccessKey && touched.awsAccessKey ? ( +
{errors.awsAccessKey}
+ ) : null} + + {errors.awsSecretKey && touched.awsSecretKey ? ( +
{errors.awsSecretKey}
+ ) : null} + + {errors.awsBucketName && touched.awsBucketName ? ( +
{errors.awsBucketName}
+ ) : null} + + + + + + {' '} + + + + + )} +
+
+ ) + } +} + +type DataServerFormProps = CustomProps + +interface DataServerState { + dataServerMode: string +} + +const defaultInitialValues = { + dataServerMode: dataServerMode.local.toString(), + cephAccessKey: null, + cephSecretKey: null, + cephHost: null, + cephPort: null, + cephIsSecure: null, + cephBucketName: null, + awsAccessKey: null, + awsSecretKey: null, + awsBucketName: null, +} + +export interface CustomProps { + onCancel + onSubmit + method: string + initialValues?: { + dataServerMode: string + cephAccessKey: string + cephSecretKey: string + cephHost: string + cephPort: number + cephIsSecure: boolean + cephBucketName: string + awsAccessKey: string + awsSecretKey: string + awsBucketName: string + } +} + +export const DataServerForm = connect( + (state: any, extraProps: CustomProps) => ({ + ...state.form, + initialValues: { + ...defaultInitialValues, + ...extraProps.initialValues + }, + }) +)(DataServerFormImpl) diff --git a/frontend/src/components/App/DataServer/index.tsx b/frontend/src/components/App/DataServer/index.tsx new file mode 100644 index 0000000..f921415 --- /dev/null +++ b/frontend/src/components/App/DataServer/index.tsx @@ -0,0 +1,168 @@ +import * as React from 'react' +import { connect } from 'react-redux' +import { RouterProps } from 'react-router' +import { withRouter, RouteComponentProps } from 'react-router-dom' + +import { APIRequest, isAPISucceeded, isAPIFailed } from '@src/apis/Core' +import { DataServer, DataServerParam, FetchDataServerByIdParam } from '@src/apis' +import { saveDataServerDispatcher, fetchDataServerDispatcher, addNotification } from '@src/actions' +import { DataServerForm } from './DataServerForm' +import { APIRequestResultsRenderer } from '@common/APIRequestResultsRenderer' + +/** + * Page for adding/editing DataServer + * + */ +class DataServerComponent extends React.Component { + constructor(props, context) { + super(props, context) + + this.onSubmit = this.onSubmit.bind(this) + this.onCancel = this.onCancel.bind(this) + this.renderEditForm = this.renderEditForm.bind(this) + this.state = { + method: null, + submitting: false, + notified: false + } + } + + /** + * Handle submit + * + * @param params + */ + onSubmit(params) { + const { saveDataServer } = this.props + const formParams: DataServer = params + + this.setState({submitting: true, notified: false}) + return saveDataServer({ + projectId: this.props.match.params.projectId, + ...formParams, method: this.state.method + }) + } + + /** + * Handle cancel button + * + * Reset form and move to hosts list page + */ + onCancel() { + const { push } = this.props.history + push(`/projects/${this.props.match.params.projectId}/data_servers`) + } + + componentDidMount() { + this.props.fetchDataServer({ + projectId: this.props.match.params.projectId + }) + } + + static getDerivedStateFromProps(nextProps: DataServerProps, prevState: DataServerState){ + const { saveDataServerStatus, fetchDataServerStatus } = nextProps + const { submitting, notified } = prevState + + // Handling submitted API results + if (submitting && !notified) { + const succeeded: boolean = isAPISucceeded(saveDataServerStatus) && saveDataServerStatus.result + const failed: boolean = (isAPISucceeded(saveDataServerStatus) && !saveDataServerStatus.result) || + isAPIFailed(saveDataServerStatus) + if (succeeded) { + nextProps.fetchDataServer({ + projectId: nextProps.match.params.projectId + }) + nextProps.addNotification({ color: 'success', message: 'Successfully saved host' }) + return {submitting: false, notified: true} + } else if (failed) { + nextProps.addNotification({ color: 'error', message: 'Something went wrong. Try again later' }) + return {submitting: false, notified: true} + } + } else { + const failed: boolean = (isAPISucceeded(fetchDataServerStatus) && !fetchDataServerStatus.result) || isAPIFailed(fetchDataServerStatus) + if (failed) { + return {method: 'post'} + } else { + return {method: 'patch'} + } + } + } + + render() { + const { method } = this.state + if (method === 'patch') { + return ( + + ) + } + return ( + + ) + } + + renderEditForm(result) { + const properties = { ...result.data_servers } + return ( + + ) + } +} + +type DataServerProps = + StateProps & DispatchProps + & CustomProps + & RouterProps & RouteComponentProps<{ projectId: number }> + +interface DataServerState { + method: string, + submitting: boolean, + notified: boolean +} + +interface StateProps { + saveDataServerStatus: APIRequest + fetchDataServerStatus: APIRequest +} + +interface CustomProps {} + +const mapStateToProps = (state: any, extraProps: CustomProps) => ( + { + saveDataServerStatus: state.saveDataServerReducer.saveDataServer, + fetchDataServerStatus: state.fetchDataServerReducer.fetchDataServer, + ...state.form, + ...extraProps + } +) + +export interface DispatchProps { + saveDataServer: (params: DataServerParam) => Promise + fetchDataServer: (params: FetchDataServerByIdParam) => Promise + addNotification: (params) => any +} + +const mapDispatchToProps = (dispatch): DispatchProps => { + return { + saveDataServer: (params: DataServerParam) => saveDataServerDispatcher(dispatch, params), + fetchDataServer: (params: FetchDataServerByIdParam) => fetchDataServerDispatcher(dispatch, params), + addNotification: (params) => dispatch(addNotification(params)) + } +} + +export default withRouter( + connect & CustomProps>( + mapStateToProps, mapDispatchToProps + )(DataServerComponent) +) From c8e7c34773e043ac02b84ecf768b4c76a985d046 Mon Sep 17 00:00:00 2001 From: Hattori Keigo Date: Mon, 8 Apr 2019 11:42:40 +0900 Subject: [PATCH 17/92] Remove `Deploy` Add `Dashboard` --- .../App/Dashboard/DashboardStatusForm.tsx | 323 ++++++++++ .../src/components/App/Dashboard/index.tsx | 564 ++++++++++++++++++ .../App/Deploy/DeployStatusForm.tsx | 230 ------- .../App/Deploy/DeployStatusTable.tsx | 263 -------- frontend/src/components/App/Deploy/index.tsx | 513 ---------------- 5 files changed, 887 insertions(+), 1006 deletions(-) create mode 100644 frontend/src/components/App/Dashboard/DashboardStatusForm.tsx create mode 100644 frontend/src/components/App/Dashboard/index.tsx delete mode 100644 frontend/src/components/App/Deploy/DeployStatusForm.tsx delete mode 100644 frontend/src/components/App/Deploy/DeployStatusTable.tsx delete mode 100644 frontend/src/components/App/Deploy/index.tsx diff --git a/frontend/src/components/App/Dashboard/DashboardStatusForm.tsx b/frontend/src/components/App/Dashboard/DashboardStatusForm.tsx new file mode 100644 index 0000000..95d1ea2 --- /dev/null +++ b/frontend/src/components/App/Dashboard/DashboardStatusForm.tsx @@ -0,0 +1,323 @@ +import * as React from 'react' +import { connect } from 'react-redux' +import { Link } from 'react-router-dom'; +import { Button, Table, Row } from 'reactstrap' +import { Formik, Form } from 'formik' + +import { APIRequest } from '@src/apis/Core' +import { Service, Model } from '@src/apis' +import { ControlMode } from './index' +import { Checkbox, Radio } from '@common/Field' + + +class DashboardStatusForm extends React.Component { + constructor(props, context) { + super(props, context) + + this.handleDiscardChanges = this.handleDiscardChanges.bind(this) + this.handleModeChanges = this.handleModeChanges.bind(this) + } + + render() { + const { + onSubmit + } = this.props + + return ( + + {({ isInitialValid, isSubmitting }) => ( +
+ {this.handleModeChanges(isInitialValid)} +
+ {this.renderSwitchModelsButton()} +
+ + {this.renderTableHead()} + {this.renderTableBody()} +
+
+ {this.renderSubmitButtons(isInitialValid, isSubmitting)} +
+ )} +
+ ) + } + + renderSwitchModelsButton = () => { + const { mode, changeMode, canEdit } = this.props + + if (!canEdit) { + return null + } + switch (mode) { + case ControlMode.EDIT_DEPLOY_STATUS: + case ControlMode.SELECT_TARGETS: + return ( + + ) + default: + return ( +
+ +
+ ) + } + } + + /** + * Render head row of the table + * + * Render Service names and label (`Models` and `Services`) + * Each Service is rendered with a deploy check box on viewing/deleting mode + * @param services Services to be shown (Currently show all, but should be filtered) + */ + renderTableHead = () => { + const { mode, projectId, applicationId, canEdit, services } = this.props + + // Button to delete Service (for deleting k8s services) + const deleteCheckButton = (serviceName: string, serviceId: string) => { + return ( + + { canEdit ? + + : null } + + {serviceName} + + + ) + } + + const renderButton = (serviceName, serviceId) => { + const renderMap = { + [ControlMode.VIEW_DEPLOY_STATUS] : deleteCheckButton(serviceName, serviceId), + [ControlMode.SELECT_TARGETS] : deleteCheckButton(serviceName, serviceId), + [ControlMode.EDIT_DEPLOY_STATUS]: {serviceName} + } + return renderMap[mode] + } + + return ( + + + + Models \ Services + + {/* Render services */} + {services.map( + (service: Service) => ( + + {renderButton(service.name, service.serviceId)} + + ) + )} + + + ) + } + + /** + * Render body of the table + */ + renderTableBody = () => { + const { models, projectId, applicationId, mode, canEdit } = this.props + + const deleteCheckButton = (modelName: string, modelId: string) => { + const checkBox = ( + + ) + return ( + + {canEdit ? checkBox : null} + + {modelName} + + + ) + } + + const renderButton = (modelName, modelId) => { + const renderMap = { + [ControlMode.VIEW_DEPLOY_STATUS] : deleteCheckButton(modelName, modelId), + [ControlMode.SELECT_TARGETS] : deleteCheckButton(modelName, modelId), + [ControlMode.EDIT_DEPLOY_STATUS]: {modelName} + } + return renderMap[mode] + } + + return ( + + { + models.map( + (model: Model, index: number) => ( + + + {renderButton(model.description, model.modelId)} + + {this.renderStatusRow(model)} + + ) + ) + } + + ) + } + + renderStatusRow = (model: Model) => ( + this.props.services.map( + (service, index) => ( + + {this.renderStatusCell(service.serviceId, model)} + + ) + ) + ) + + renderStatusCell = (serviceId: string, model: Model) => { + const { mode, deployStatus } = this.props + + if (mode !== ControlMode.VIEW_DEPLOY_STATUS) { + return ( + + ) + } + + // View mode (Not able to change) + if (deployStatus[serviceId] === model.modelId) { + return ( + + ) + } + return (
) + } + + /** + * Render submit button(s) + * + * Show delete button if selected targets exist + * Show save button if editing deploy status + */ + renderSubmitButtons(isInitialValid, isSubmitting): JSX.Element { + const { mode } = this.props + + const showSubmitButton: boolean = mode !== ControlMode.VIEW_DEPLOY_STATUS + + if (!showSubmitButton) { + return null + } + + const paramsMap = { + [ControlMode.SELECT_TARGETS]: { color: 'danger', icon: 'trash', text: 'Delete Services/Models' }, + [ControlMode.EDIT_DEPLOY_STATUS]: { color: 'success', icon: 'save', text: 'Save Changes' } + } + + // Submit button element(s) + const buttons = (params) => ( +
+ +
+ ) + + const submittingLoader = ( +
+
+ Submitting... +
+ ) + + return isSubmitting ? submittingLoader : buttons(paramsMap[mode]) + } + + // Handle event methods + + handleDiscardChanges(event): void { + const { changeMode } = this.props + changeMode(ControlMode.VIEW_DEPLOY_STATUS) + } + + handleModeChanges(isInitialValid): void { + const { mode, changeMode } = this.props + if (mode === ControlMode.VIEW_DEPLOY_STATUS && !isInitialValid) { + changeMode(ControlMode.SELECT_TARGETS) + } else if (mode === ControlMode.SELECT_TARGETS && isInitialValid) { + changeMode(ControlMode.VIEW_DEPLOY_STATUS) + } + } +} + +interface DashboardStatusFormCustomProps { + projectId + applicationId + mode: ControlMode + services: Service[] + models: Model[] + deployStatus + canEdit: boolean + onSubmit: (e) => Promise + changeMode: (mode: ControlMode) => void +} + +interface StateProps { + switchModelsStatus: APIRequest + initialValues: { + status + switch + delete + } +} + +const mapStateToProps = (state: any, extraProps: DashboardStatusFormCustomProps) => { + // Map of service ID to delete flag + const initialServiceStatus: { [x: string]: boolean } = + extraProps.services + .map((service) => ({[service.serviceId]: false})) + .reduce((l, r) => Object.assign(l, r), {}) + const initialModelStatus: { [x: string]: boolean } = + extraProps.models + .map((model) => ({[model.modelId]: false})) + .reduce((l, r) => Object.assign(l, r), {}) + + return { + ...state.form, + initialValues: { + status: extraProps.deployStatus, + switch: extraProps.deployStatus, + delete: { + services: initialServiceStatus, + models: initialModelStatus + } + } + } +} + +const mapDispatchToProps = (dispatch): {} => { + return { } +} + +type DashboardStatusFormProps = StateProps & DashboardStatusFormCustomProps + +interface DashboardStatusFormState {} + +export default connect(mapStateToProps, mapDispatchToProps)(DashboardStatusForm) diff --git a/frontend/src/components/App/Dashboard/index.tsx b/frontend/src/components/App/Dashboard/index.tsx new file mode 100644 index 0000000..f1f8013 --- /dev/null +++ b/frontend/src/components/App/Dashboard/index.tsx @@ -0,0 +1,564 @@ +import * as React from 'react' +import { connect } from 'react-redux' +import { withRouter, RouteComponentProps } from 'react-router' +import { Button, Modal, ModalBody, ModalHeader, Row, Col } from 'reactstrap' + +import { APIRequest, isAPISucceeded, isAPIFailed } from '@src/apis/Core' +import { + Model, Service, SwitchModelParam, SyncKubernetesParam, + Application, UserInfo, + FetchApplicationByIdParam, FetchModelByIdParam, FetchServiceParam, + IdParam, FetchKubernetesByIdParam, Kubernetes +} from '@src/apis' +import { + addNotification, + fetchApplicationByIdDispatcher, + fetchAllModelsDispatcher, + fetchAllServicesDispatcher, + switchModelsDispatcher, + deleteServicesDispatcher, + deleteModelsDispatcher, + syncKubernetesDispatcher, fetchAllKubernetesDispatcher +} from '@src/actions' +import { AddModelFileModal } from '@components/App/Model/Modals/AddModelFileModal' +import { APIRequestResultsRenderer } from '@common/APIRequestResultsRenderer' +import DashboardStatusForm from './DashboardStatusForm' + + +export enum ControlMode { + VIEW_DEPLOY_STATUS, + EDIT_DEPLOY_STATUS, + SELECT_TARGETS, + UPLOAD_MODEL, + EVALUATE_MODELS +} + +interface DashboardStatusState { + controlMode: ControlMode + isDeleteModalOpen: boolean + isAddModelFileModalOpen: boolean + selectedData: { services: any[], models: any[] } + submitted: boolean + notified: boolean + syncSubmitted: boolean + syncNotified: boolean +} + +type DashboardStatusProps = DispatchProps & StateProps & RouteComponentProps<{projectId: number, applicationId: string}> + +class Dashboard extends React.Component { + constructor(props, context) { + super(props, context) + + this.state = { + controlMode: ControlMode.VIEW_DEPLOY_STATUS, + isDeleteModalOpen: false, + isAddModelFileModalOpen: false, + selectedData: { services: [], models: [] }, + submitted: false, + notified: false, + syncSubmitted: false, + syncNotified: false + } + + this.onSubmitDashboardStatusChanges = this.onSubmitDashboardStatusChanges.bind(this) + this.onSubmitDelete = this.onSubmitDelete.bind(this) + this.deleteServices = this.deleteServices.bind(this) + this.deleteModels = this.deleteModels.bind(this) + this.toggleDeleteModal = this.toggleDeleteModal.bind(this) + this.toggleAddModelFileModalOpen = this.toggleAddModelFileModalOpen.bind(this) + this.syncKubernetes = this.syncKubernetes.bind(this) + this.renderDashboardStatus = this.renderDashboardStatus.bind(this) + this.changeMode = this.changeMode.bind(this) + this.complete = this.complete.bind(this) + } + + componentDidMount() { + const { projectId, applicationId } = this.props.match.params + const params = { + projectId, + applicationId + } + + this.props.fetchApplicationById(params) + this.props.fetchAllModels(params) + this.props.fetchAllServices(params) + } + + static getDerivedStateFromProps(nextProps: DashboardStatusProps, prevState: DashboardStatusState){ + const { + switchModelsStatus, + deleteServicesStatus, + deleteModelsStatus, + syncKubernetesStatus + } = nextProps + const { projectId, applicationId } = nextProps.match.params + const params = { + projectId, + applicationId + } + const { controlMode, submitted, syncSubmitted, syncNotified } = prevState + + const checkAllApiResultSucceeded = + (result: APIRequest) => + isAPISucceeded(result) && + result.result.reduce((p, c) => (p && c)) + const checkAllApiResultFailed = + (result: APIRequest) => + (isAPISucceeded(result) && !result.result.reduce((p, c) => (p && c))) || isAPIFailed(result) + + // Switch to view mode when API successfully connected + if (submitted && controlMode === ControlMode.EDIT_DEPLOY_STATUS) { + if (checkAllApiResultSucceeded(switchModelsStatus)) { + nextProps.addNotification({ color: 'success', message: 'Successfully changed deployment' }) + } else { + nextProps.addNotification({ color: 'error', message: 'Something went wrong with switching models, try again later' }) + } + nextProps.fetchAllModels(params) + nextProps.fetchAllServices(params) + return { + controlMode: ControlMode.VIEW_DEPLOY_STATUS, + submitted: false, + notified: true, + selectedData: { services: [], models: [] } + } + } + + if (submitted && controlMode === ControlMode.SELECT_TARGETS) { + if (checkAllApiResultSucceeded(deleteServicesStatus)) { + nextProps.addNotification({ color: 'success', message: 'Successfully changed deletion services' }) + } else if (checkAllApiResultFailed(deleteServicesStatus)) { + nextProps.addNotification({ color: 'error', message: 'Something went wrong with deletion services, try again later' }) + } + if (checkAllApiResultSucceeded(deleteModelsStatus)) { + nextProps.addNotification({ color: 'success', message: 'Successfully changed deletion models' }) + } else if (checkAllApiResultFailed(deleteModelsStatus)) { + nextProps.addNotification({ color: 'error', message: 'Something went wrong with deletion models, try again later' }) + } + nextProps.fetchAllModels(params) + nextProps.fetchAllServices(params) + return { + controlMode: ControlMode.VIEW_DEPLOY_STATUS, + submitted: false, + notified: true, + selectedData: { services: [], models: [] } + } + } + + if (syncSubmitted && !syncNotified) { + const succeeded: boolean = isAPISucceeded(syncKubernetesStatus) && syncKubernetesStatus.result + const failed: boolean = (isAPISucceeded(syncKubernetesStatus) && !syncKubernetesStatus.result) || isAPIFailed(syncKubernetesStatus) + + if (succeeded) { + nextProps.addNotification({ color: 'success', message: 'Successfully synced application' }) + } else if (failed) { + nextProps.addNotification({ color: 'error', message: 'Something went wrong with sync application. Try again later' }) + } + nextProps.fetchAllModels(params) + nextProps.fetchAllServices(params) + return { + controlMode: ControlMode.VIEW_DEPLOY_STATUS, + submitted: false, + notified: true, + syncSubmitted: false, + syncNotified: true, + selectedData: { services: [], models: [] } + } + } + } + + // Render methods + + render(): JSX.Element { + const { application, models, services, userInfoStatus, settings } = this.props + const statuses: any = { models, services, application } + if (isAPISucceeded(settings) && settings.result.auth) { + statuses.userInfoStatus = userInfoStatus + } + return ( + + ) + } + + /** + * Render deploy status / related form fields + * with fetched API results + * + * @param fetchedResults Fetched data from APIs + * @param canEdit Boolean value of user's editor permission + */ + renderDashboardStatus(fetchedResults, canEdit): JSX.Element { + const { controlMode } = this.state + const { + onSubmitDashboardStatusChanges, + onSubmitDelete, + changeMode + } = this + const kubernetesMode = fetchedResults.kuberneteses.length > 0 + const applicationName = fetchedResults.application.name + const { projectId, applicationId } = this.props.match.params + + const services: Service[] = fetchedResults.services + const models: Model[] = fetchedResults.models + const deployStatus = this.makeDashboardStatus(services) + const onSubmitMap: { [mode: number]: (params: any) => Promise } = { + [ControlMode.VIEW_DEPLOY_STATUS]: onSubmitDashboardStatusChanges, // Dummy to render form + [ControlMode.EDIT_DEPLOY_STATUS]: onSubmitDashboardStatusChanges, + [ControlMode.SELECT_TARGETS]: onSubmitDelete, + } + + // Render contents to control deploy status + switch (controlMode) { + case ControlMode.VIEW_DEPLOY_STATUS: + case ControlMode.EDIT_DEPLOY_STATUS: + case ControlMode.SELECT_TARGETS: + return ( + this.renderContent( + , + applicationName, + kubernetesMode, + canEdit + ) + ) + + case ControlMode.EVALUATE_MODELS: + return ( + this.renderContent( +
TBD
, + applicationName, + kubernetesMode, + canEdit + ) + ) + } + } + + renderContent = (content: JSX.Element, applicationName, kubernetesMode, canEdit: boolean): JSX.Element => { + return ( +
+ {this.renderTitle(applicationName, kubernetesMode, canEdit)} + +

+ + Service Status +

+
+ {content} + { + this.state.controlMode === ControlMode.SELECT_TARGETS + ? this.renderConfirmDeleteModal() + : null + } +
+ ) + } + + renderTitle = (applicationName, kubernetesMode, canEdit: boolean): JSX.Element => { + const buttons = ( + + + {' '} + {kubernetesMode && canEdit ? this.renderKubernetesControlButtons() : null} + + ) + return ( + + +

+ + {applicationName} +

+ + {canEdit ? buttons : null} +
+ ) + } + + renderKubernetesControlButtons() { + const { push } = this.props.history + const { syncKubernetes } = this + const { projectId, applicationId } = this.props.match.params + + return ( + + + {` `} + + + ) + } + + renderConfirmDeleteModal(): JSX.Element { + const { isDeleteModalOpen } = this.state + + const cancel = () => { + this.toggleDeleteModal() + } + + const executeDeletion = (event) => { + this.deleteServices(this.state.selectedData.services) + this.deleteModels(this.state.selectedData.models) + this.toggleDeleteModal() + } + + return ( + + Delete Services/Models + + Are you sure to delete? + +
+ + +
+
+ ) + } + + syncKubernetes(): void { + this.setState({ syncSubmitted: true, syncNotified: false }) + this.props.syncKubernetes(this.props.match.params) + } + + // Event handing methods + + toggleDeleteModal(): void { + this.setState({ + isDeleteModalOpen: !this.state.isDeleteModalOpen + }) + } + + toggleAddModelFileModalOpen(): void { + this.setState({ + isAddModelFileModalOpen: !this.state.isAddModelFileModalOpen + }) + } + + onSubmitDashboardStatusChanges(params): Promise { + const { switchModels } = this.props + const { projectId, applicationId } = this.props.match.params + + const apiParams: SwitchModelParam[] = + Object.entries(params.switch) + .filter( + ([key, value]) => { + if (params.status[key] === value) { + return false + } + return true + }) + .map( + ([key, value]): SwitchModelParam => ( + { + projectId, + applicationId, + serviceId: key, + modelId: value ? value as number : undefined + })) + + this.setState({ submitted: true }) + + return switchModels(apiParams) + } + + /** + * Handle submit and call API to delete services/models + * Currently only supports to delete k8s services + * + * @param params + */ + onSubmitDelete(params): Promise { + this.setState({ + isDeleteModalOpen: true, + selectedData: { + services: params.delete_services, + models: params.delete_models + } + }) + return Promise.resolve() + } + + deleteServices(params): Promise { + const { deleteServices } = this.props + const { projectId, applicationId } = this.props.match.params + + const apiParams = + Object.entries(params) + .filter(([key, value]) => (value)) + .map( + ([key, value]) => ( + { + projectId, + applicationId, + serviceId: key + })) + + this.setState({ submitted: true }) + + return deleteServices(apiParams) + } + + deleteModels(params): Promise { + const { deleteModels } = this.props + const { projectId, applicationId } = this.props.match.params + + const apiParams = + Object.entries(params) + .filter(([key, value]) => (value)) + .map( + ([key, value]) => ( + { + projectId, + applicationId, + modelId: Number(key) + })) + + this.setState({ submitted: true }) + + return deleteModels(apiParams) + } + + // Utils + /** + * Generate deploy status from services + * + * @param services {Service[]} Fetched Services + */ + makeDashboardStatus(services: Service[]) { + const result = {} + + services.map( + (service: Service) => { if (service.modelId) { result[service.serviceId] = service.modelId } } + ) + return result + } + + changeMode(mode: ControlMode) { + this.setState({ controlMode: mode }) + } + + complete(param) { + this.props.addNotification(param) + this.props.fetchAllModels(this.props.match.params) + this.props.fetchAllServices(this.props.match.params) + this.setState({ + controlMode: ControlMode.VIEW_DEPLOY_STATUS, + submitted: false, + selectedData: { services: [], models: [] } + }) + } +} + +export interface StateProps { + kuberneteses: APIRequest + application: APIRequest + models: APIRequest + services: APIRequest + switchModelsStatus: APIRequest + deleteServicesStatus: APIRequest + deleteModelsStatus: APIRequest + syncKubernetesStatus: APIRequest + userInfoStatus: APIRequest + settings: APIRequest +} + +const mapStateToProps = (state): StateProps => { + return { + kuberneteses: state.fetchAllKubernetesReducer.fetchAllKubernetes, + application: state.fetchApplicationByIdReducer.fetchApplicationById, + models: state.fetchAllModelsReducer.fetchAllModels, + services: state.fetchAllServicesReducer.fetchAllServices, + switchModelsStatus: state.switchModelsReducer.switchModels, + deleteServicesStatus: state.deleteServicesReducer.deleteServices, + deleteModelsStatus: state.deleteModelsReducer.deleteModels, + syncKubernetesStatus: state.syncKubernetesReducer.syncKubernetes, + userInfoStatus: state.userInfoReducer.userInfo, + settings: state.settingsReducer.settings + } +} + +export interface DispatchProps { + addNotification + fetchAllKubernetes: (params: FetchKubernetesByIdParam) => Promise + fetchApplicationById: (params: FetchApplicationByIdParam) => Promise + fetchAllModels: (params: FetchModelByIdParam) => Promise + fetchAllServices: (params: FetchServiceParam) => Promise + switchModels: (params: SwitchModelParam[]) => Promise + deleteServices: (params: IdParam[]) => Promise + deleteModels: (params: IdParam[]) => Promise + syncKubernetes: (params: SyncKubernetesParam) => Promise +} + +const mapDispatchToProps = (dispatch): DispatchProps => { + return { + addNotification: (params) => dispatch(addNotification(params)), + fetchAllKubernetes: (params: FetchKubernetesByIdParam) => fetchAllKubernetesDispatcher(dispatch, params), + fetchApplicationById: (params: FetchApplicationByIdParam) => fetchApplicationByIdDispatcher(dispatch, params), + fetchAllModels: (params: FetchModelByIdParam) => fetchAllModelsDispatcher(dispatch, params), + fetchAllServices: (params: FetchServiceParam) => fetchAllServicesDispatcher(dispatch, params), + switchModels: (params: SwitchModelParam[]) => switchModelsDispatcher(dispatch, params), + deleteServices: (params: IdParam[]) => deleteServicesDispatcher(dispatch, params), + deleteModels: (params: IdParam[]) => deleteModelsDispatcher(dispatch, params), + syncKubernetes: (params: SyncKubernetesParam) => syncKubernetesDispatcher(dispatch, params), + } +} + +export default withRouter( + connect>( + mapStateToProps, mapDispatchToProps + )(Dashboard)) diff --git a/frontend/src/components/App/Deploy/DeployStatusForm.tsx b/frontend/src/components/App/Deploy/DeployStatusForm.tsx deleted file mode 100644 index 9a6d737..0000000 --- a/frontend/src/components/App/Deploy/DeployStatusForm.tsx +++ /dev/null @@ -1,230 +0,0 @@ -import * as React from 'react' -import { connect } from 'react-redux' -import { Button } from 'reactstrap' -import { reduxForm, InjectedFormProps } from 'redux-form' - -import { APIRequest } from '@src/apis/Core' -import { Service } from '@src/apis' -import DeployStatusTable from './DeployStatusTable' -import { ControlMode } from './index' - -class DeployStatusForm extends React.Component { - constructor(props, context) { - super(props, context) - - this.handleDiscardChanges = this.handleDiscardChanges.bind(this) - } - - componentWillReceiveProps(nextProps: DeployStatusFormProps) { - const { mode, pristine, changeMode } = nextProps - - if (mode === ControlMode.VIEW_DEPLOY_STATUS && !pristine) { - changeMode(ControlMode.SELECT_TARGETS) - } else if (mode === ControlMode.SELECT_TARGETS && pristine) { - changeMode(ControlMode.VIEW_DEPLOY_STATUS) - } - } - - render() { - const { - onSubmit, - handleSubmit, - } = this.props - - return ( -
-
- {this.renderSwitchModelsButton()} -
- -
- {this.renderSubmitButtons()} - - ) - } - - renderSwitchModelsButton = () => { - const { mode, changeMode, canEdit } = this.props - - if (!canEdit) { - return null - } - switch (mode) { - case ControlMode.EDIT_DEPLOY_STATUS: - case ControlMode.SELECT_TARGETS: - return ( - - ) - default: - return ( -
- -
- ) - } - } - - /** - * Render submit button(s) - * - * Show delete button if selected targets exist - * Show save button if editing deploy status - */ - renderSubmitButtons(): JSX.Element { - const { - mode, - submitting, - pristine - } = this.props - - const showSubmitButton: boolean = mode !== ControlMode.VIEW_DEPLOY_STATUS - - if (!showSubmitButton) { - return null - } - - const paramsMap = { - [ControlMode.SELECT_TARGETS]: { color: 'danger', icon: 'trash', text: 'Delete Services/Models' }, - [ControlMode.EDIT_DEPLOY_STATUS]: { color: 'success', icon: 'save', text: 'Save Changes' } - } - - // Submit button element(s) - const buttons = (params) => ( -
- -
- ) - - const submittingLoader = ( -
-
- Submitting... -
- ) - - return submitting ? submittingLoader : buttons(paramsMap[mode]) - } - - renderSubmitButtonElements() { - const { - mode, - submitting, - pristine - } = this.props - - const paramsMap = { - [ControlMode.SELECT_TARGETS]: { color: 'danger', icon: 'trash', text: 'Delete Services/Models' }, - [ControlMode.EDIT_DEPLOY_STATUS]: { color: 'success', icon: 'save', text: 'Save Changes' } - } - - // Submit button element(s) - if (mode === ControlMode.EDIT_DEPLOY_STATUS) { - return ( -
- -
- ) - } - return ( -
- -
- ) - } - - // Handle event methods - - handleDiscardChanges(event): void { - const { changeMode, reset } = this.props - reset() - changeMode(ControlMode.VIEW_DEPLOY_STATUS) - } -} - -interface DeployStatusFormCustomProps { - applicationType: string - applicationId - mode: ControlMode - models - services: Service[] - deployStatus - canEdit: boolean - onSubmit: (e) => Promise - changeMode: (mode: ControlMode) => void -} - -interface StateProps { - switchModelsStatus: APIRequest - initialValues: { - status - switch - delete - } -} - -const mapStateToProps = (state: any, extraProps: DeployStatusFormCustomProps) => { - // Map of service ID to delete flag - const initialDeleteStatus: { [x: string]: boolean } = - extraProps.services - .map((service) => ({[service.id]: false})) - .reduce((l, r) => Object.assign(l, r), {}) - - return { - ...state.form, - initialValues: { - status: extraProps.deployStatus, - switch: extraProps.deployStatus, - delete: { - services: initialDeleteStatus - } - } - } -} - -const mapDispatchToProps = (dispatch): {} => { - return { } -} - -type DeployStatusFormProps - = StateProps & DeployStatusFormCustomProps & InjectedFormProps<{}, DeployStatusFormCustomProps> - -export default connect(mapStateToProps, mapDispatchToProps)( - reduxForm<{}, DeployStatusFormCustomProps>( - { - form: 'deployStatusForm' - } - )(DeployStatusForm) -) diff --git a/frontend/src/components/App/Deploy/DeployStatusTable.tsx b/frontend/src/components/App/Deploy/DeployStatusTable.tsx deleted file mode 100644 index e04b871..0000000 --- a/frontend/src/components/App/Deploy/DeployStatusTable.tsx +++ /dev/null @@ -1,263 +0,0 @@ -import * as React from 'react' -import { connect } from 'react-redux' -import { CustomInput, Table, Row } from 'reactstrap' -import { Field, InjectedFormProps } from 'redux-form' -import { Link } from 'react-router-dom' - -import { Model, Service } from '@src/apis' -import { ControlMode } from './index' - -/** - * Table for showing deploy status - * Show forms on editing or selecting models mode - */ -class DeployStatusTable extends React.Component { - constructor(props, context) { - super(props, context) - - this.state = { - tooltipOpen: {} - } - } - - render() { - const { services } = this.props - - return ( - - {this.renderTableHead(services)} - {this.renderTableBody()} -
- ) - } - - toggleTooltip(tag) { - return () => { - const nextTooltipOpen = { - ...this.state.tooltipOpen, - [tag]: !this.state.tooltipOpen[tag] - } - - this.setState({ - tooltipOpen: nextTooltipOpen - }) - } - } - - /** - * Render head row of the table - * - * Render Service names and label (`Models` and `Services`) - * Each Service is rendered with a deploy check box on viewing/deleting mode - * @param services Services to be shown (Currently show all, but should be filtered) - */ - renderTableHead = (services) => { - const { mode, applicationType, applicationId, canEdit } = this.props - - // Button to delete Service (for deleting k8s services) - const deleteCheckButton = (serviceName: string, serviceId: string) => { - return ( - - { applicationType === 'kubernetes' && canEdit ? - - : null } - - {serviceName} - - - ) - } - - const renderButton = (serviceName, serviceId) => { - const renderMap = { - [ControlMode.VIEW_DEPLOY_STATUS] : deleteCheckButton(serviceName, serviceId), - [ControlMode.SELECT_TARGETS] : deleteCheckButton(serviceName, serviceId), - [ControlMode.EDIT_DEPLOY_STATUS]: {serviceName} - } - return renderMap[mode] - } - - return ( - - - - Models \ Services - - {/* Render services */} - {services.map( - (service) => ( - - {renderButton(service.name, service.id)} - - ) - )} - - - ) - } - - /** - * Render body of the table - */ - renderTableBody = () => { - const { - selectModelMode, models, applicationId, mode, canEdit - } = this.props - - const modelSelectCheckBox = (model: Model) => ( - - ) - - const deleteCheckButton = (modelName: string, modelId: string) => { - const checkBox = ( - - ) - return ( - - {canEdit ? checkBox : null} - - {modelName} - - - ) - } - - const renderButton = (modelName, modelId) => { - const renderMap = { - [ControlMode.VIEW_DEPLOY_STATUS] : deleteCheckButton(modelName, modelId), - [ControlMode.SELECT_TARGETS] : deleteCheckButton(modelName, modelId), - [ControlMode.EDIT_DEPLOY_STATUS]: {modelName} - } - return renderMap[mode] - } - - return ( - - { - models.map( - (model: Model, index: number) => ( - - - {selectModelMode ? modelSelectCheckBox(model) : renderButton(model.name, model.id)} - - {this.renderStatusRow(model)} - - ) - ) - } - - ) - } - - renderStatusRow = (model: Model) => ( - this.props.services.map( - (service, index) => ( - - {this.renderStatusCell(service.id, model)} - - ) - ) - ) - - renderStatusCell = (serviceId: string, model: Model) => { - const { mode, deployStatus } = this.props - - if (mode !== ControlMode.VIEW_DEPLOY_STATUS) { - return ( - - ) - } - - // View mode (Not able to change) - if (deployStatus[serviceId] === model.id) { - return ( - - ) - } - return (
) - } - -} - -class CustomRadioButton extends React.Component { - render() { - const { input, modelId } = this.props - - return ( - - ) - } -} - -const CustomCheckBox = (props) => { - const { input, id, label } = props - - return ( - - ) -} - -interface DeployStatusFormCustomProps { - applicationType: string - applicationId - models: Model[], - services: Service[], - deployStatus, - mode: ControlMode, - selectModelMode?: boolean - canEdit: boolean -} - -export interface DispatchProps { - dispatchChange -} - -const mapDispatchToProps = (dispatch): DispatchProps => { - return { - dispatchChange: (field, value, changeMethod) => dispatch(changeMethod(field, value)) - } -} - -const mapStateToProps = (state: any, extraProps: DeployStatusFormCustomProps) => { - return {} -} - -type DeployStatusProps = DispatchProps & DeployStatusFormCustomProps & InjectedFormProps<{}, DeployStatusFormCustomProps> - -export default connect(mapStateToProps, mapDispatchToProps)(DeployStatusTable) diff --git a/frontend/src/components/App/Deploy/index.tsx b/frontend/src/components/App/Deploy/index.tsx deleted file mode 100644 index 2769c51..0000000 --- a/frontend/src/components/App/Deploy/index.tsx +++ /dev/null @@ -1,513 +0,0 @@ -import * as React from 'react' -import { connect } from 'react-redux' -import { withRouter, RouteComponentProps } from 'react-router' -import { Button, Modal, ModalBody, ModalHeader, Row, Col } from 'reactstrap' - -import { APIRequest, isAPISucceeded, isAPIFailed } from '@src/apis/Core' -import { Model, Service, SwitchModelParam, SynKubernetesStatusParam, Application, UserInfo, UserRole } from '@src/apis' -import { - addNotification, - fetchApplicationByIdDispatcher, - fetchAllModelsDispatcher, - fetchAllServicesDispatcher, - switchModelsDispatcher, - deleteKubernetesServicesDispatcher, - syncKubernetesStatusDispatcher -} from '@src/actions' -import { AddModelFileModal } from '@components/App/Model/Modals/AddModelFileModal' -import { APIRequestResultsRenderer } from '@common/APIRequestResultsRenderer' -import DeployStatusForm from './DeployStatusForm' -import { role } from '../Admin/constants' - -export enum ControlMode { - VIEW_DEPLOY_STATUS, - EDIT_DEPLOY_STATUS, - SELECT_TARGETS, - UPLOAD_MODEL, - EVALUATE_MODELS -} - -interface DeployStatusState { - controlMode: ControlMode - isDeleteServicesModalOpen: boolean - isAddModelFileModalOpen: boolean - selectedData: { services: any[], models: any[] } - submitted: boolean - notified: boolean - syncSubmitted: boolean - syncNotified: boolean -} - -type DeployStatusProps = DispatchProps & StateProps & RouteComponentProps<{applicationId: string}> - -class Deploy extends React.Component { - constructor(props, context) { - super(props, context) - - this.state = { - controlMode: ControlMode.VIEW_DEPLOY_STATUS, - isDeleteServicesModalOpen: false, - isAddModelFileModalOpen: false, - selectedData: { services: [], models: [] }, - submitted: false, - notified: false, - syncSubmitted: false, - syncNotified: false - } - - this.onSubmitDeployStatusChanges = this.onSubmitDeployStatusChanges.bind(this) - this.onSubmitDelete = this.onSubmitDelete.bind(this) - this.deleteKubernetesServices = this.deleteKubernetesServices.bind(this) - this.toggleDeleteServicesModal = this.toggleDeleteServicesModal.bind(this) - this.toggleAddModelFileModalOpen = this.toggleAddModelFileModalOpen.bind(this) - this.syncServices = this.syncServices.bind(this) - this.renderDeployStatus = this.renderDeployStatus.bind(this) - this.changeMode = this.changeMode.bind(this) - this.complete = this.complete.bind(this) - } - - componentWillMount() { - const { applicationId } = this.props.match.params - - this.props.fetchApplicationById(applicationId) - this.props.fetchAllModels(applicationId) - this.props.fetchAllServices(applicationId) - } - - componentWillReceiveProps(nextProps: DeployStatusProps) { - const { - switchModelsStatus, - deleteKubernetesServicesStatus, - syncKubernetesServicesStatusStatus - } = nextProps - const { controlMode, submitted } = this.state - - const checkAllApiResultStatus = - (result: APIRequest) => - isAPISucceeded(result) && - result.result.reduce((p, c) => (p && c)) - - // Switch to view mode when API successfully connected - if (submitted && controlMode === ControlMode.EDIT_DEPLOY_STATUS) { - if (checkAllApiResultStatus(switchModelsStatus)) { - this.complete({ color: 'success', message: 'Successfully changed deployment' }) - } else { - this.complete({ color: 'error', message: 'Something went wrong, try again later' }) - } - } - - if (submitted && controlMode === ControlMode.SELECT_TARGETS) { - if (checkAllApiResultStatus(deleteKubernetesServicesStatus)) { - this.complete({ color: 'success', message: 'Successfully changed deletion' }) - } else { - this.complete({ color: 'error', message: 'Something went wrong, try again later' }) - } - } - - this.checkAndNotifyAPIResult( - syncKubernetesServicesStatusStatus, - 'syncSubmitted', 'syncNotified', - 'Successfully synced application' - ) - } - - checkAndNotifyAPIResult(status, submitted: string, notified: string, notificationText) { - const submittedFlag: boolean = this.state[submitted] - const notifiedFlag: boolean = this.state[notified] - - if (submittedFlag && !notifiedFlag) { - const succeeded: boolean = isAPISucceeded(status) && status.result - const failed: boolean = (isAPISucceeded(status) && !status.result) || - isAPIFailed(status) - - if (succeeded) { - this.setState({ submitted: false, notified: true}) - this.complete({ color: 'success', message: notificationText }) - } else if (failed) { - this.setState({ submitted: false, notified: true}) - this.complete({ color: 'error', message: 'Something went wrong. Try again later' }) - } - } - } - - // Render methods - - render(): JSX.Element { - const { application, models, services, userInfoStatus, settings } = this.props - const statuses: any = { models, services, application } - if (isAPISucceeded(settings) && settings.result.auth) { - statuses.userInfoStatus = userInfoStatus - } - if ( this.props.match.params.applicationId === 'add' ) { - return null - } - return ( - - ) - } - - /** - * Render deploy status / related form fields - * with fetched API results - * - * @param fetchedResults Fetched data from APIs - * @param canEdit Boolean value of user's editor permission - */ - renderDeployStatus(fetchedResults, canEdit): JSX.Element { - const { controlMode } = this.state - const { - onSubmitDeployStatusChanges, - onSubmitDelete, - changeMode - } = this - const { kubernetesId, name } = fetchedResults.application - const { applicationId } = this.props.match.params - - const services: Service[] = fetchedResults.services - const models: Model[] = fetchedResults.models - const deployStatus = this.makeDeployStatus(services) - const onSubmitMap: { [mode: number]: (params: any) => Promise } = { - [ControlMode.VIEW_DEPLOY_STATUS]: onSubmitDeployStatusChanges, // Dummy to render form - [ControlMode.EDIT_DEPLOY_STATUS]: onSubmitDeployStatusChanges, - [ControlMode.SELECT_TARGETS]: onSubmitDelete, - } - - // Render contents to control deploy status - switch (controlMode) { - case ControlMode.VIEW_DEPLOY_STATUS: - case ControlMode.EDIT_DEPLOY_STATUS: - case ControlMode.SELECT_TARGETS: - return ( - this.renderContent( - , - name, - kubernetesId, - canEdit - ) - ) - - case ControlMode.EVALUATE_MODELS: - return ( - this.renderContent( -
TBD
, - name, - kubernetesId, - canEdit - ) - ) - } - } - - renderContent = (content: JSX.Element, applicationName, kubernetesId, canEdit: boolean): JSX.Element => { - return ( -
- {this.renderTitle(applicationName, kubernetesId, canEdit)} - -

- - Service Status -

-
- {content} - { - this.state.controlMode === ControlMode.SELECT_TARGETS - ? this.renderConfirmDeleteHostModal() - : null - } -
- ) - } - - renderTitle = (applicationName, kubernetesId, canEdit: boolean): JSX.Element => { - const buttons = ( - - - {' '} - {kubernetesId ? this.renderKubernetesControlButtons(kubernetesId) : null} - - ) - return ( - - -

- - {applicationName} -

- - {canEdit ? buttons : null} -
- ) - } - - renderKubernetesControlButtons(kubernetesId) { - const { push } = this.props.history - const { syncServices } = this - const { applicationId } = this.props.match.params - - return ( - - - {` `} - - - ) - } - - renderConfirmDeleteHostModal(): JSX.Element { - const { isDeleteServicesModalOpen } = this.state - - const cancel = () => { - this.toggleDeleteServicesModal() - } - - const executeDeletion = (event) => { - this.deleteKubernetesServices(this.state.selectedData.services) - this.toggleDeleteServicesModal() - } - - return ( - - Delete Services/Models - - Are you sure to delete? - -
- - -
-
- ) - } - - syncServices(kubernetesId): void { - const { applicationId } = this.props.match.params - - this.setState({ syncSubmitted: true, syncNotified: false }) - this.props.syncKubernetesServicesStatus({applicationId, kubernetesId}) - } - - // Event handing methods - - toggleDeleteServicesModal(): void { - this.setState({ - isDeleteServicesModalOpen: !this.state.isDeleteServicesModalOpen - }) - } - - toggleAddModelFileModalOpen(): void { - this.setState({ - isAddModelFileModalOpen: !this.state.isAddModelFileModalOpen - }) - } - - onSubmitDeployStatusChanges(params): Promise { - const { switchModels } = this.props - const { applicationId } = this.props.match.params - - const apiParams: SwitchModelParam[] = - Object.entries(params.switch) - .filter( - ([key, value]) => { - if (params.status[key] === value) { - return false - } - return true - }) - .map( - ([key, value]): SwitchModelParam => ( - { - applicationId, - serviceId: key, - modelId: value ? value as string : undefined - })) - - this.setState({ submitted: true }) - - return switchModels(apiParams) - } - - /** - * Handle submit and call API to delete services/models - * Currently only supports to delete k8s services - * - * @param params - */ - onSubmitDelete(params): Promise { - this.setState({ - isDeleteServicesModalOpen: true, - selectedData: { - services: params.delete.services, - models: params.delete.models - } - }) - return Promise.resolve() - } - - deleteKubernetesServices(params): Promise { - const { deleteKubernetesServices } = this.props - const { applicationId } = this.props.match.params - - const apiParams = - Object.entries(params) - .filter(([key, value]) => (value)) - .map( - ([key, value]) => ( - { - applicationId, - serviceId: key - })) - - this.setState({ submitted: true }) - - return deleteKubernetesServices(apiParams) - } - - // Utils - /** - * Generate deploy status from services - * - * @param services {Service[]} Fetched Services - */ - makeDeployStatus(services: Service[]) { - const result = {} - - services.map( - (service: Service) => { if (service.modelId) { result[service.id] = service.modelId } } - ) - return result - } - - changeMode(mode: ControlMode) { - this.setState({ controlMode: mode }) - } - - /** - * Reload deploy status - * - * Fetch models and services through API again - */ - complete(param) { - const { - fetchAllModels, - fetchAllServices - } = this.props - const { - applicationId - } = this.props.match.params - - this.props.addNotification(param) - fetchAllModels(applicationId) - fetchAllServices(applicationId) - this.setState({ - controlMode: ControlMode.VIEW_DEPLOY_STATUS, - submitted: false, - selectedData: { services: [], models: [] } - }) - } -} - -export interface StateProps { - application: APIRequest - models: APIRequest - services: APIRequest - switchModelsStatus: APIRequest - deleteKubernetesServicesStatus: APIRequest - syncKubernetesServicesStatusStatus: APIRequest - userInfoStatus: APIRequest - settings: APIRequest -} - -const mapStateToProps = (state): StateProps => { - return { - application: state.fetchApplicationByIdReducer.applicationById, - models: state.fetchAllModelsReducer.models, - services: state.fetchAllServicesReducer.services, - switchModelsStatus: state.switchModelsReducer.switchModels, - deleteKubernetesServicesStatus: state.deleteKubernetesServicesReducer.deleteKubernetesServices, - syncKubernetesServicesStatusStatus: state.syncKubernetesStatusReducer.syncKubernetesStatus, - userInfoStatus: state.userInfoReducer.userInfo, - settings: state.settingsReducer.settings - } -} - -export interface DispatchProps { - addNotification - syncKubernetesServicesStatus - fetchApplicationById: (id: string) => Promise - fetchAllModels: (applicationId: string) => Promise - fetchAllServices: (applicationId: string) => Promise - switchModels: (params: SwitchModelParam[]) => Promise - deleteKubernetesServices: (params) => Promise -} - -const mapDispatchToProps = (dispatch): DispatchProps => { - return { - addNotification: (params) => dispatch(addNotification(params)), - fetchApplicationById: (id: string) => fetchApplicationByIdDispatcher(dispatch, { id }), - fetchAllModels: (applicationId: string) => fetchAllModelsDispatcher(dispatch, { applicationId }), - fetchAllServices: (applicationId: string) => fetchAllServicesDispatcher(dispatch, { applicationId }), - switchModels: (params: SwitchModelParam[]) => switchModelsDispatcher(dispatch, params), - deleteKubernetesServices: (params) => deleteKubernetesServicesDispatcher(dispatch, params), - syncKubernetesServicesStatus: (params: SynKubernetesStatusParam) => syncKubernetesStatusDispatcher(dispatch, params), - } -} - -export default withRouter( - connect>( - mapStateToProps, mapDispatchToProps - )(Deploy)) From 0f847e16db2bb6970b0bbb1bb71597f829ddc5bd Mon Sep 17 00:00:00 2001 From: Hattori Keigo Date: Mon, 8 Apr 2019 11:43:05 +0900 Subject: [PATCH 18/92] Renew `Application` --- .../App/Application/SideMenu/index.tsx | 60 +++++++-- .../src/components/App/Application/index.tsx | 6 +- .../src/components/App/Applications/index.tsx | 85 ++++++------- .../ApplicationDeploymentForm.tsx | 110 +++++++---------- .../components/App/SaveApplication/index.tsx | 114 ++++-------------- 5 files changed, 156 insertions(+), 219 deletions(-) diff --git a/frontend/src/components/App/Application/SideMenu/index.tsx b/frontend/src/components/App/Application/SideMenu/index.tsx index c5157a6..d9be597 100644 --- a/frontend/src/components/App/Application/SideMenu/index.tsx +++ b/frontend/src/components/App/Application/SideMenu/index.tsx @@ -2,18 +2,24 @@ import * as React from 'react' import { NavLink, withRouter, RouteComponentProps } from 'react-router-dom' import { connect } from 'react-redux' -import { UserInfo, UserRole } from '@src/apis' +import { UserInfo, UserApplicationRole } from '@src/apis' import { APIRequest, APIRequestStatusList } from '@src/apis/Core' -import { role } from '@components/App/Admin/constants' +import {applicationRole, serviceLevel} from '@components/Common/Enum' interface Props { + projectId: number applicationId: string } class SideMenu extends React.Component { render() { - const { userInfoStatus } = this.props - const { applicationId } = this.props.match.params + const { userInfoStatus, projectId, applicationId } = this.props + const serviceLevels = Object.values(serviceLevel).map((serviceName: string) => { + return { + text: serviceName, + path: `routing/${serviceName}` + } + }) const menuContents = [ { title: 'Orchestration', @@ -33,12 +39,18 @@ class SideMenu extends React.Component { path: 'models', icon: 'database' }, + { + text: 'Routing', + path: 'routing', + icon: 'route', + items: serviceLevels + }, ] } ] if (userInfoStatus.status === APIRequestStatusList.success) { - userInfoStatus.result.roles.forEach((e: UserRole) => { - if (String(e.applicationId) === applicationId && e.role === role.owner) { + userInfoStatus.result.applicationRoles.forEach((e: UserApplicationRole) => { + if (String(e.applicationId) === applicationId && e.role === applicationRole.admin) { menuContents.push({ title: '', items: [ @@ -53,16 +65,38 @@ class SideMenu extends React.Component { }) } - const fullPath = (menuPath) => `/applications/${applicationId}/${menuPath}` + const fullPath = (menuPath) => `/projects/${projectId}/applications/${applicationId}/${menuPath}` const renderSubmenuItem = (subMenuItem) => { + let children = null + if (subMenuItem.items) { + const child = subMenuItem.items.map((subsubMenuItem) => { + return ( +
  • + + {subsubMenuItem.text} + +
  • + ) + }) + children = ( +
  • +
      + {child} +
    +
  • + ) + } return ( -
  • - - - {subMenuItem.text} - -
  • + +
  • + + + {subMenuItem.text} + +
  • + {children} +
    ) } diff --git a/frontend/src/components/App/Application/index.tsx b/frontend/src/components/App/Application/index.tsx index d6438fb..5c9fc8e 100644 --- a/frontend/src/components/App/Application/index.tsx +++ b/frontend/src/components/App/Application/index.tsx @@ -7,13 +7,13 @@ import SideMenu from './SideMenu/index' * * @param props */ -class Application extends React.Component, {}> { +class Application extends React.Component, {}> { render() { - const { applicationId } = this.props.match.params + const { projectId, applicationId } = this.props.match.params return (
    - +
    {this.props.children}
    diff --git a/frontend/src/components/App/Applications/index.tsx b/frontend/src/components/App/Applications/index.tsx index 4544967..f18698c 100644 --- a/frontend/src/components/App/Applications/index.tsx +++ b/frontend/src/components/App/Applications/index.tsx @@ -1,24 +1,31 @@ import * as React from 'react' import { connect } from 'react-redux' -import { RouterProps, RouteComponentProps } from 'react-router' +import { RouteComponentProps } from 'react-router' import { withRouter, Link } from 'react-router-dom' import { Button } from 'reactstrap' -import {APIRequest, isAPIFailed, isAPISucceeded} from '@src/apis/Core' -import { Application } from '@src/apis' +import { APIRequest, isAPIFailed, isAPISucceeded } from '@src/apis/Core' +import { Application, FetchApplicationByIdParam, SyncKubernetesParam } from '@src/apis' import { fetchAllApplicationsDispatcher, - syncKubernetesStatusDispatcher, + syncKubernetesDispatcher, addNotification } from '@src/actions' import { APIRequestResultsRenderer } from '@common/APIRequestResultsRenderer' + +type ApplicationProps = StateProps & DispatchProps & RouteComponentProps<{projectId: number}> +interface ApplicationState { + syncSubmitted: boolean + syncNotified: boolean +} + /** * Show list of all applications * * Home page to move detaied page for each application */ -class ApplicationList extends React.Component { +class ApplicationList extends React.Component { constructor(props, context) { super(props, context) this.state = { @@ -29,43 +36,29 @@ class ApplicationList extends React.Component(status) && status.result - const failed: boolean = (isAPISucceeded(status) && !status.result) || isAPIFailed(status) + if (syncSubmitted && !syncNotified) { + const succeeded: boolean = isAPISucceeded(syncKubernetes) && syncKubernetes.result + const failed: boolean = (isAPISucceeded(syncKubernetes) && !syncKubernetes.result) || isAPIFailed(syncKubernetes) if (succeeded) { - this.setState({[submitted]: false, [notified]: true}) - push('/applications') - this.props.fetchApplications() - this.props.addNotification({color: 'success', message: notificationText}) + nextProps.fetchApplications({projectId: nextProps.match.params.projectId}) + nextProps.addNotification({color: 'success', message: 'Successfully synced all hosts'}) + return {syncSubmitted: false, syncNotified: true} } else if (failed) { - this.setState({[submitted]: false, [notified]: true}) - this.props.addNotification({color: 'error', message: 'Something went wrong. Try again later'}) + nextProps.addNotification({color: 'error', message: 'Something went wrong. Try again later'}) + return {syncSubmitted: false, syncNotified: true} } } } - componentWillMount() { - this.props.fetchApplications() - } - render() { const status = this.props.applications @@ -82,8 +75,8 @@ class ApplicationList extends React.Component { + this.props.syncKubernetes({projectId: this.props.match.params.projectId}) this.setState({syncSubmitted: true, syncNotified: false}) - this.props.syncAllKubernetesStatus({}) } const title = ( @@ -123,13 +116,14 @@ class ApplicationList extends React.Component ( - + {value.name} @@ -139,10 +133,7 @@ class ApplicationList extends React.Component - {value.kubernetesId ? 'Yes' : 'No'} - - - {value.date.toUTCString()} + {value.registerDate.toUTCString()} ) @@ -153,7 +144,7 @@ class ApplicationList extends React.Component - NameDescriptionKubernetesDate + NameDescriptionDate @@ -166,32 +157,32 @@ class ApplicationList extends React.Component - syncAllKubernetesStatusStatus: APIRequest + syncKubernetes: APIRequest } const mapStateToProps = (state) => { return { ...state.fetchAllApplicationsReducer, - syncAllKubernetesStatusStatus: state.syncKubernetesStatusReducer.syncKubernetesStatus + syncKubernetes: state.syncKubernetesReducer.syncKubernetes } } export interface DispatchProps { - fetchApplications: () => Promise, - syncAllKubernetesStatus: (params) => Promise, + fetchApplications: (params: FetchApplicationByIdParam) => Promise, + syncKubernetes: (params: SyncKubernetesParam) => Promise, addNotification } const mapDispatchToProps = (dispatch): DispatchProps => { return { - fetchApplications: () => fetchAllApplicationsDispatcher(dispatch), - syncAllKubernetesStatus: () => syncKubernetesStatusDispatcher(dispatch, {}), + fetchApplications: (params: FetchApplicationByIdParam) => fetchAllApplicationsDispatcher(dispatch, params), + syncKubernetes: (params: SyncKubernetesParam) => syncKubernetesDispatcher(dispatch, params), addNotification: (params) => dispatch(addNotification(params)) } } export default withRouter( - connect>( + connect>( mapStateToProps, mapDispatchToProps )(ApplicationList) ) diff --git a/frontend/src/components/App/SaveApplication/ApplicationDeploymentForm.tsx b/frontend/src/components/App/SaveApplication/ApplicationDeploymentForm.tsx index b464166..3861bb1 100644 --- a/frontend/src/components/App/SaveApplication/ApplicationDeploymentForm.tsx +++ b/frontend/src/components/App/SaveApplication/ApplicationDeploymentForm.tsx @@ -1,20 +1,24 @@ import * as React from 'react' import { connect } from 'react-redux' -import { reduxForm, InjectedFormProps } from 'redux-form' -import { Card, CardBody, Form, Button } from 'reactstrap' +import { Card, CardBody, Button } from 'reactstrap' +import { Formik, Form, ErrorMessage, Field } from 'formik' +import * as Yup from "yup"; -import { KubernetesHost } from '@src/apis' -import DeploymentSettingFormFields, { kubernetesDeploymentDefultSettings } from '@components/misc/Forms/DeploymentSettingFormFields' + +const ApplicationSchema = Yup.object().shape({ + application_name: Yup.string() + .required('Required') + .max(128), + description: Yup.string(), +}); class ApplicationDeploymentFormImpl extends React.Component { /** * Render form bodies to add application - * Switch depending on whether plain or Kubernetes app * */ render() { - const { selectedApplicationType, handleSubmit, onSubmit } = this.props - const applicationSpeicificForm = this.renderApplicationSpecificForm(selectedApplicationType) + const { onSubmit } = this.props return ( @@ -22,37 +26,35 @@ class ApplicationDeploymentFormImpl extends React.Component
    Add Application -
    - {applicationSpeicificForm} - {applicationSpeicificForm ? this.renderButtons() : null} -
    - - ) - } - /** - * - * Render form fields to setup application specific - * options. The options depend on given `applicationType` - * (simple or Kubernetes). - * - * @param applicationType Type of application to set up - */ - renderApplicationSpecificForm(applicationType) { - return ( - - - - - + + {({ errors, touched }) => ( +
    + + + + {errors.application_name && touched.application_name ? ( +
    {errors.application_name}
    + ) : null} + + + {errors.description && touched.description ? ( +
    {errors.description}
    + ) : null} + +
    +
    + {this.renderButtons()} +
    + )} +
    + ) } @@ -62,12 +64,7 @@ class ApplicationDeploymentFormImpl extends React.Component @@ -82,12 +79,12 @@ class ApplicationDeploymentFormImpl extends React.Component - {' '} - @@ -98,34 +95,17 @@ class ApplicationDeploymentFormImpl extends React.Component +type AddApplicationFormProps = FormCustomProps export const ApplicationDeloymentForm = connect( (state: any, extraProps: FormCustomProps) => ({ - initialValues: { - add: { - application: { - ...kubernetesDeploymentDefultSettings, - dbType: extraProps.selectedApplicationType === 'kubernetes' ? 'mysql' : null, - applicationType: extraProps.selectedApplicationType - } - } - }, ...extraProps, ...state.form }) - )(reduxForm<{}, FormCustomProps>({ - form: 'applicationForm', - touchOnChange: true, - enableReinitialize: true - })(ApplicationDeploymentFormImpl)) + )(ApplicationDeploymentFormImpl) diff --git a/frontend/src/components/App/SaveApplication/index.tsx b/frontend/src/components/App/SaveApplication/index.tsx index b1cab8f..e34e7e9 100644 --- a/frontend/src/components/App/SaveApplication/index.tsx +++ b/frontend/src/components/App/SaveApplication/index.tsx @@ -1,13 +1,11 @@ import * as React from 'react' import { connect } from 'react-redux' -import { RouterProps } from 'react-router' -import { withRouter, RouteComponentProps, Link } from 'react-router-dom' -import { InjectedFormProps } from 'redux-form' +import { withRouter, RouteComponentProps } from 'react-router-dom' import { Row, Col, Alert } from 'reactstrap' +import { ApplicationParam } from '@src/apis' import { APIRequest, isAPISucceeded, isAPIFailed } from '@src/apis/Core' -import { KubernetesHost } from '@src/apis' -import { addApplicationDispatcher, fetchAllKubernetesHostsDispatcher, addNotification } from '@src/actions' +import { saveApplicationDispatcher, addNotification } from '@src/actions' import { FormCustomProps, ApplicationDeloymentForm } from './ApplicationDeploymentForm' /** @@ -20,48 +18,31 @@ class AddApplication extends React.Component(addApplicationStatus) && addApplicationStatus.result - const failed: boolean = (isAPISucceeded(addApplicationStatus) && !addApplicationStatus.result) || - isAPIFailed(addApplicationStatus) + const succeeded: boolean = isAPISucceeded(saveApplicationStatus) && saveApplicationStatus.result + const failed: boolean = (isAPISucceeded(saveApplicationStatus) && !saveApplicationStatus.result) || + isAPIFailed(saveApplicationStatus) if (succeeded) { nextProps.addNotification({ color: 'success', message: 'Successfully added application' }) - this.setState({ notified: true }) push('/applications/') + return { notified: true } } else if (failed) { nextProps.addNotification({ color: 'error', message: 'Something went wrong. Try again later' }) - this.setState({ notified: true }) + return { notified: true } } } - - if ( selectedApplicationType === 'kubernetes' ) { - if (isAPISucceeded(this.props.fetchKubernetesHostsStatus)) { - this.setState({ - fetchedKubernetesHosts: this.props.fetchKubernetesHostsStatus.result - }) - } else if (isAPIFailed(this.props.fetchKubernetesHostsStatus)) { - nextProps.addNotification({ color: 'error', message: 'We could not fetch kubernetes hosts' }) - } - } - } - - componentWillMount() { - this.props.fetchKubernetesHosts() } /** @@ -75,112 +56,63 @@ class AddApplication extends React.Component - {this.hostsNotFoundAlert()} ) } - - hostsNotFoundAlert() { - const { fetchedKubernetesHosts, selectedApplicationType } = this.state - const apiFinished: boolean = - isAPISucceeded(this.props.fetchKubernetesHostsStatus) - || isAPIFailed(this.props.fetchKubernetesHostsStatus) - - if (selectedApplicationType === 'kubernetes' - && apiFinished - && fetchedKubernetesHosts.length === 0) { - const link = ( - - here - - ) - return ( - - Register your Kubernetes host(s) {link} at first. - - ) - } - return null - } } -type AddApplicationProps = StateProps & DispatchProps & RouterProps & InjectedFormProps<{}, FormCustomProps> +type AddApplicationProps = StateProps & DispatchProps & RouteComponentProps<{projectId: number}> interface AddApplicationState { - selectedApplicationType: string submitting: boolean notified: boolean - fetchedKubernetesHosts: KubernetesHost[] } interface StateProps { - addApplicationStatus: APIRequest - fetchKubernetesHostsStatus: APIRequest + saveApplicationStatus: APIRequest } const mapStateToProps = (state: any, extraProps: FormCustomProps) => ( { - addApplicationStatus: state.addApplicationReducer.addApplication, - fetchKubernetesHostsStatus: state.fetchAllKubernetesHostsReducer.kubernetesHosts, + saveApplicationStatus: state.saveApplicationReducer.saveApplication, ...state.form, ...extraProps } ) export interface DispatchProps { - addApplication: (params) => Promise + saveApplication: (params: ApplicationParam) => Promise addNotification: (params) => Promise - fetchKubernetesHosts: () => Promise } const mapDispatchToProps = (dispatch): DispatchProps => { return { - addApplication: (params) => addApplicationDispatcher(dispatch, params), - addNotification: (params) => dispatch(addNotification(params)), - fetchKubernetesHosts: () => fetchAllKubernetesHostsDispatcher(dispatch) + saveApplication: (params: ApplicationParam) => saveApplicationDispatcher(dispatch, params), + addNotification: (params) => dispatch(addNotification(params)) } } export default withRouter( - connect & FormCustomProps>( + connect & FormCustomProps>( mapStateToProps, mapDispatchToProps )(AddApplication) ) From db836f652458ab32f7ff5c07128773d075d0867c Mon Sep 17 00:00:00 2001 From: Hattori Keigo Date: Mon, 8 Apr 2019 11:44:14 +0900 Subject: [PATCH 19/92] Refactor --- .../APIRequestResultsRenderer/index.tsx | 2 +- frontend/src/components/Common/Enum/index.tsx | 56 +++++++++++++++++++ .../components/misc/NavigationBar/index.tsx | 41 ++++++++++---- 3 files changed, 88 insertions(+), 11 deletions(-) create mode 100644 frontend/src/components/Common/Enum/index.tsx diff --git a/frontend/src/components/Common/APIRequestResultsRenderer/index.tsx b/frontend/src/components/Common/APIRequestResultsRenderer/index.tsx index 3942281..31eea6e 100644 --- a/frontend/src/components/Common/APIRequestResultsRenderer/index.tsx +++ b/frontend/src/components/Common/APIRequestResultsRenderer/index.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import { withRouter, RouteComponentProps } from 'react-router-dom' import { isAPISucceeded, APIRequestStatusList } from '@src/apis/Core' import { UserProjectRole, UserApplicationRole } from "@src/apis"; -import { projectRole, applicationRole } from "@components/App/Admin/constants"; +import { projectRole, applicationRole } from "@components/Common/Enum"; /** * Render component with API requests diff --git a/frontend/src/components/Common/Enum/index.tsx b/frontend/src/components/Common/Enum/index.tsx new file mode 100644 index 0000000..3c34cf7 --- /dev/null +++ b/frontend/src/components/Common/Enum/index.tsx @@ -0,0 +1,56 @@ +export enum serviceLevel { + development = 'development', + beta = 'beta', + staging = 'staging', + sandbox = 'sandbox', + production = 'production' +} + +export enum dataServerMode { + local = 'local', + ceph_s3 = 'ceph_s3', + aws_s3 = 'aws_s3' +} + +export enum projectRole { + member = 'member', + admin = 'admin' +} + +export enum applicationRole { + viewer = 'viewer', + editor = 'editor', + admin = 'admin' +} + +export const apiConvertDataServerMode = (param) => { + if (param === 'DataServerModeEnum.local') { + return dataServerMode.local.toString() + } else if (param === 'DataServerModeEnum.ceph_s3') { + return dataServerMode.ceph_s3.toString() + } else if (param === 'DataServerModeEnum.aws_s3') { + return dataServerMode.aws_s3.toString() + } else { + return false + } +}; + +export const apiConvertProjectRole = (param) => { + if (param === 'ProjectRole.member') { + return projectRole.member.toString() + } else if (param === 'ProjectRole.admin') { + return projectRole.admin.toString() + } else { + return false + } +}; + +export const apiConvertApplicationRole = (param) => { + if (param === 'ApplicationRole.admin') { + return applicationRole.admin.toString() + } else if (param === 'ApplicationRole.editor') { + return applicationRole.editor.toString() + } else { + return applicationRole.viewer.toString() + } +}; diff --git a/frontend/src/components/misc/NavigationBar/index.tsx b/frontend/src/components/misc/NavigationBar/index.tsx index 2984bcf..9424892 100644 --- a/frontend/src/components/misc/NavigationBar/index.tsx +++ b/frontend/src/components/misc/NavigationBar/index.tsx @@ -36,6 +36,8 @@ class NavigationBar extends React.Component { } render() { const { userInfoStatus, auth } = this.props + const { projectId } = this.props.match.params + let user: React.ReactNode if (auth) { user = ( @@ -46,23 +48,41 @@ class NavigationBar extends React.Component { /> ) } + + let projectAdmin: React.ReactNode + if (projectId) { + const adminlink = `/projects/${projectId}/admin` + const kubelink = `/projects/${projectId}/kubernetes/admin` + projectAdmin = ( + + + + + Admin + + + + + + Kubernetes + + + + ) + } + return ( Rekcurd Dashboard {user} @@ -124,10 +144,11 @@ const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => { } } -type NavigationBarProps = RouteComponentProps<{}> & StateProps & DispatchProps & Props +type NavigationBarProps = + RouteComponentProps<{ projectId?: number }> & StateProps & DispatchProps & Props export default withRouter( - connect>( + connect>( mapStateToProps, mapDispatchToProps )(NavigationBar) ) From 688ea9a173af3a661b0d245aaafa35da3bfc2ef1 Mon Sep 17 00:00:00 2001 From: Hattori Keigo Date: Mon, 8 Apr 2019 11:44:38 +0900 Subject: [PATCH 20/92] Renew `Model` --- .../App/Model/Modals/AddModelFileModal.tsx | 178 ++++++++---------- .../ModelDescription/ModelDescriptionForm.tsx | 142 +++++++------- .../App/Model/ModelDescription/index.tsx | 109 +++++------ frontend/src/components/App/Model/index.tsx | 105 ++++++----- 4 files changed, 247 insertions(+), 287 deletions(-) diff --git a/frontend/src/components/App/Model/Modals/AddModelFileModal.tsx b/frontend/src/components/App/Model/Modals/AddModelFileModal.tsx index eb14fe8..507de70 100644 --- a/frontend/src/components/App/Model/Modals/AddModelFileModal.tsx +++ b/frontend/src/components/App/Model/Modals/AddModelFileModal.tsx @@ -1,134 +1,119 @@ import * as React from 'react' import { connect } from 'react-redux' -import { reduxForm, Field, InjectedFormProps } from 'redux-form' import { uploadModelDispatcher, addNotification } from '@src/actions/index' import { APIRequest, isAPISucceeded, isAPIFailed } from '@src/apis/Core/index' -import { SingleFormField } from '@common/Field/SingleFormField' -import { FileUploadInputField } from '@common/Field/FileUploadInputField' -import { required } from '@common/Field/Validateors' +import { UploadModelParam } from '@src/apis' import { Modal, ModalHeader, ModalBody, ModalFooter, Button } from 'reactstrap' -class AddModelFileFormImpl extends React.Component { +import * as Yup from "yup"; +import { ErrorMessage, Field, Form, Formik } from "formik"; + + +const AddModelFileSchema = Yup.object().shape({ + description: Yup.string() + .required('Required'), + filepath: Yup.mixed() + .required('Required') +}); + +class AddModelFileFormImpl extends React.Component { constructor(props, context) { super(props, context) - this.onChange = this.onChange.bind(this) - this.onSubmit = this.onSubmit.bind(this) - this.onCancel = this.onCancel.bind(this) this.state = { - fileName: null + submitting: false } + + this.onSubmit = this.onSubmit.bind(this) + this.onCancel = this.onCancel.bind(this) } onSubmit(params) { - const { applicationId, uploadModel } = this.props + const { projectId, applicationId, uploadModel } = this.props const request = { + projectId, applicationId, - ...params.upload.model + ...params } + this.setState({submitting: true}) return uploadModel(request) } - componentWillReceiveProps(nextProps) { + static getDerivedStateFromProps(nextProps: AddModelFileFormProps, prevState: AddModelFileFormState){ const { - uploadModelFileStatus, toggle, isModalOpen, - submitting, reset, reload + uploadModelStatus, toggle, isModalOpen, reload } = nextProps - // Close modal when API successfully connected - // Close modal when API successfully connected - if (isModalOpen && submitting) { - const succeeded: boolean = isAPISucceeded(uploadModelFileStatus) && uploadModelFileStatus.result - const failed: boolean = (isAPISucceeded(uploadModelFileStatus) && !uploadModelFileStatus.result) || - isAPIFailed(uploadModelFileStatus) + if (isModalOpen && prevState.submitting) { + const succeeded: boolean = isAPISucceeded(uploadModelStatus) && uploadModelStatus.result + const failed: boolean = (isAPISucceeded(uploadModelStatus) && !uploadModelStatus.result) || isAPIFailed(uploadModelStatus) if (succeeded) { - reset() toggle() reload({color: 'success', message: 'Successfully added model'}) + return {submitting: false} } else if (failed) { - nextProps.addNotification({color: 'error', message: 'Something went wrong. Try again later'}) + nextProps.addNotification({color: 'error', message: 'Something went wrong with uploading model. Try again later'}) + return {submitting: false} } } } onCancel(event) { - const { reset, submitting, toggle } = this.props - if (!submitting) { - this.setState({fileName: null}) - reset() - toggle() - } - } - - onChange(event) { - if (event.target.files[0]) { - this.setState({fileName: event.target.files[0].name}) + if (!this.state.submitting) { + this.props.toggle() } } render() { const { - handleSubmit, isModalOpen + isModalOpen } = this.props return ( -
    - - - Add Model - - {this.renderBodyForm()} - {this.renderFooterButtons()} -
    + + {({ errors, touched, setFieldValue, isSubmitting }) => ( +
    + + + Add Model + + + + {errors.description && touched.description ? ( +
    {errors.description}
    + ) : null} + + { + setFieldValue("filepath", event.currentTarget.files[0]); + }} /> + {errors.filepath && touched.filepath ? ( +
    {errors.filepath}
    + ) : null} + +
    + {this.renderFooterButtons(isSubmitting)} +
    + )} +
    ) } - renderBodyForm() { - const { fileName } = this.state - const uploadForm = ( - - ) - - const descriptionForm = ( - - ) - - return ( - - {uploadForm} - {descriptionForm} - - ) - } - /** * Render control buttons * * Put on footer of this modal */ - renderFooterButtons() { - const { submitting } = this.props - - if (submitting) { + renderFooterButtons(isSubmitting) { + if (isSubmitting) { return(
    @@ -153,45 +138,44 @@ class AddModelFileFormImpl extends React.Component + uploadModelStatus: APIRequest } -const mapStateToProps = (state: any, extraProps: AddModelFileFormCustomProps) => { +const mapStateToProps = (state: any, extraProps: CustomProps) => { return { - uploadModelFileStatus: state.uploadModelReducer.uploadModel, + uploadModelStatus: state.uploadModelReducer.uploadModel, ...state.form, ...extraProps } } export interface DispatchProps { - uploadModel: (params) => Promise + uploadModel: (params: UploadModelParam) => Promise addNotification: (params) => any } const mapDispatchToProps = (dispatch): DispatchProps => { return { - uploadModel: (params) => uploadModelDispatcher(dispatch, params), + uploadModel: (params: UploadModelParam) => uploadModelDispatcher(dispatch, params), addNotification: (params) => dispatch(addNotification(params)) } } -type AddModelFileFormProps - = StateProps & DispatchProps & AddModelFileFormCustomProps & InjectedFormProps<{}, AddModelFileFormCustomProps> +type AddModelFileFormProps = StateProps & DispatchProps & CustomProps -export const AddModelFileModal = connect(mapStateToProps, mapDispatchToProps)( - reduxForm( - { - form: 'uploadModelFileForm', - touchOnChange: true - } - )(AddModelFileFormImpl) -) +interface AddModelFileFormState { + submitting: boolean +} + +export const AddModelFileModal = connect( + mapStateToProps, mapDispatchToProps +)(AddModelFileFormImpl) diff --git a/frontend/src/components/App/Model/ModelDescription/ModelDescriptionForm.tsx b/frontend/src/components/App/Model/ModelDescription/ModelDescriptionForm.tsx index 6f14448..99a70c3 100644 --- a/frontend/src/components/App/Model/ModelDescription/ModelDescriptionForm.tsx +++ b/frontend/src/components/App/Model/ModelDescription/ModelDescriptionForm.tsx @@ -1,59 +1,52 @@ import * as React from 'react' import { connect } from 'react-redux' -import { reduxForm, InjectedFormProps, Field } from 'redux-form' -import { Card, CardBody, Form, Button } from 'reactstrap' +import { Card, CardBody, Button } from 'reactstrap' +import { Formik, Form, ErrorMessage, Field } from 'formik' +import * as Yup from 'yup'; -import { Model } from '@src/apis' -import { SingleFormField } from '@common/Field/SingleFormField' -class ModelDescriptionFormImpl extends React.Component { - render() { - const { handleSubmit, onSubmit } = this.props - const modelSpeicificForm = this.renderModelSpecificForm() +const ModelSchema = Yup.object().shape({ + description: Yup.string() + .required('Required') +}); +class ModelDescriptionFormImpl extends React.Component { + render() { return ( -
    - {modelSpeicificForm} - {this.renderButtons()} -
    + + {({ errors, touched, isSubmitting }) => ( +
    + + + + {errors.description && touched.description ? ( +
    {errors.description}
    + ) : null} + + + {this.renderButtons(isSubmitting)} + + )} +
    ) } - /** - * - * - * @param applicationType Type of application to set up - * @returns {JSX.Element} Config fields - */ - renderModelSpecificForm(): JSX.Element { - - return ( - - - - ) - } - /** * Render control buttons * * Put on footer of this modal */ - renderButtons(): JSX.Element { - const { submitting, reset } = this.props - - if (submitting) { + renderButtons(isSubmitting): JSX.Element { + if (isSubmitting) { return (
    @@ -63,59 +56,50 @@ class ModelDescriptionFormImpl extends React.Component - - {' '} - - + + + {' '} + + ) } } +const defaultInitialValues = { + description: '' +} + interface CustomProps { onCancel onSubmit - model?: Model + initialValues: { + description: string + } } interface StateProps { initialValues } -type ModelDescriptionFormProps = - CustomProps - & StateProps - & InjectedFormProps<{}, CustomProps> +interface DispatchProps {} -const generateInitialValues = (props: CustomProps) => ( - { - edit: { - model: { - ...props.model, - } - } - } -) +type ModelDescriptionFormProps = CustomProps & StateProps & DispatchProps + +interface ModelDescriptionFormState {} -/** - * Description form - * Shown only when description - */ -export const ModelDescriptionForm = - connect( - (state: any, extraProps: CustomProps) => ({ - initialValues: generateInitialValues(extraProps), - ...extraProps, - ...state.form - }) - )(reduxForm<{}, CustomProps>({ - form: 'modelDescriptionForm', - touchOnChange: true, - enableReinitialize: true - })(ModelDescriptionFormImpl)) +export const ModelDescriptionForm = connect( + (state: any, extraProps: CustomProps) => ({ + ...state.form, + ...extraProps, + initialValues: { + ...defaultInitialValues, + ...extraProps.initialValues + }, + }) +)(ModelDescriptionFormImpl) diff --git a/frontend/src/components/App/Model/ModelDescription/index.tsx b/frontend/src/components/App/Model/ModelDescription/index.tsx index 87f14a1..20028ec 100644 --- a/frontend/src/components/App/Model/ModelDescription/index.tsx +++ b/frontend/src/components/App/Model/ModelDescription/index.tsx @@ -3,15 +3,16 @@ import { connect } from 'react-redux' import { withRouter, RouteComponentProps } from 'react-router-dom' import { APIRequest, isAPISucceeded, isAPIFailed } from '@src/apis/Core' -import { Model, Application, } from '@src/apis' +import { Model, FetchModelByIdParam, UpdateModelParam } from '@src/apis' import { - saveModelDispatcher, addNotification, + updateModelDispatcher, fetchModelByIdDispatcher } from '@src/actions' import { APIRequestResultsRenderer } from '@common/APIRequestResultsRenderer' import { ModelDescriptionForm } from './ModelDescriptionForm' + /** * Page for adding model * You can create model ONLY when your application is deployed with Kubernetes. @@ -29,52 +30,45 @@ class ModelDescription extends React.Component(saveModelStatus) && saveModelStatus.result - const failed: boolean = (isAPISucceeded(saveModelStatus) && !saveModelStatus.result) || - isAPIFailed(saveModelStatus) + const succeeded: boolean = isAPISucceeded(updateModelStatus) && updateModelStatus.result + const failed: boolean = (isAPISucceeded(updateModelStatus) && !updateModelStatus.result) || + isAPIFailed(updateModelStatus) if (succeeded) { nextProps.addNotification({ color: 'success', message: 'Successfully saved model description' }) - this.setState({ notified: true }) - push(`/applications/${applicationId}`) + push(`/projects/${projectId}/applications/${applicationId}`) + return { notified: true } } else if (failed) { nextProps.addNotification({ color: 'error', message: 'Something went wrong. Try again later' }) - this.setState({ notified: true }) + return { notified: true } } } - - } - - componentWillMount() { - const { mode } = this.props - const { modelId, applicationId } = this.props.match.params - - if (mode === 'edit') { - this.props.fetchModelById( - { - id: modelId, - applicationId - } - ) - } } render() { - const { fetchModelByIdStatus, mode } = this.props - const targetStatus = { model: fetchModelByIdStatus } + const { fetchModelByIdStatus, method } = this.props - if (mode === 'edit') { + if (method === 'patch') { return( ) } @@ -82,13 +76,11 @@ class ModelDescription extends React.Component ) } @@ -100,31 +92,32 @@ class ModelDescription extends React.Component { - const { saveModelDescription, mode } = this.props - const { applicationId, modelId } = this.props.match.params + const { updateModel, method } = this.props + const { projectId, applicationId, modelId } = this.props.match.params const request = { - ...parameters[mode].model, - mode, - id: modelId, + ...parameters, + method, + projectId, applicationId, + modelId, saveDescription: true } this.setState({ submitting: true, notified: false }) - return saveModelDescription(request) + return updateModel(request) } } type ModelDescriptionProps = StateProps & DispatchProps - & RouteComponentProps<{applicationId: string, modelId?: string}> + & RouteComponentProps<{projectId: number, applicationId: string, modelId: number}> & CustomProps interface ModelDescriptionState { @@ -132,42 +125,40 @@ interface ModelDescriptionState { notified: boolean } -interface StateProps { - application: APIRequest - saveModelStatus: APIRequest - fetchModelByIdStatus: APIRequest +interface CustomProps { + method: string } -interface CustomProps { - mode: string +interface StateProps { + fetchModelByIdStatus: APIRequest + updateModelStatus: APIRequest } const mapStateToProps = (state: any, extraProps: CustomProps) => ( { - application: state.fetchApplicationByIdReducer.applicationById, - saveModelStatus: state.saveModelReducer.saveModel, - fetchModelByIdStatus: state.fetchModelByIdReducer.modelById, + fetchModelByIdStatus: state.fetchModelByIdReducer.fetchModelById, + updateModelStatus: state.updateModelReducer.updateModel, ...state.form, ...extraProps } ) export interface DispatchProps { - saveModelDescription: (params) => Promise - fetchModelById: (params) => Promise + fetchModelById: (params: FetchModelByIdParam) => Promise + updateModel: (params: UpdateModelParam) => Promise addNotification: (params) => Promise } const mapDispatchToProps = (dispatch): DispatchProps => { return { - fetchModelById: (params) => fetchModelByIdDispatcher(dispatch, params), - saveModelDescription: (params) => saveModelDispatcher(dispatch, params), + fetchModelById: (params: FetchModelByIdParam) => fetchModelByIdDispatcher(dispatch, params), + updateModel: (params: UpdateModelParam) => updateModelDispatcher(dispatch, params), addNotification: (params) => dispatch(addNotification(params)) } } export default withRouter( - connect & CustomProps>( + connect & CustomProps>( mapStateToProps, mapDispatchToProps )(ModelDescription) ) diff --git a/frontend/src/components/App/Model/index.tsx b/frontend/src/components/App/Model/index.tsx index f9adf81..0140d30 100644 --- a/frontend/src/components/App/Model/index.tsx +++ b/frontend/src/components/App/Model/index.tsx @@ -1,18 +1,22 @@ import * as React from 'react' import { connect } from 'react-redux' -import { withRouter, RouteComponentProps, Link } from 'react-router-dom' +import { withRouter, RouteComponentProps } from 'react-router-dom' import { Row, Col } from 'reactstrap' import { APIRequest, isAPISucceeded, isAPIFailed } from '@src/apis/Core' -import { Model, Application, UserInfo, UserRole } from '@src/apis' import { + Model, Application, UserInfo, UserApplicationRole, + UpdateModelParam, FetchApplicationByIdParam, FetchModelByIdParam +} from '@src/apis' +import { + fetchModelByIdDispatcher, fetchApplicationByIdDispatcher, - saveModelDispatcher, + updateModelDispatcher, addNotification } from '@src/actions' import { APIRequestResultsRenderer } from '@common/APIRequestResultsRenderer' import ModelDescription from './ModelDescription' -import { role } from '../Admin/constants' +import { applicationRole } from '@common/Enum' /** * Page for adding model @@ -29,43 +33,17 @@ class SaveModel extends React.Component { } } - componentWillReceiveProps(nextProps: ModelProps) { - const { saveModelStatus } = nextProps - const { push } = nextProps.history - const { applicationId } = this.props.match.params - const { submitting, notified } = this.state - - // Close modal when API successfully finished - if (submitting && !notified) { - const succeeded: boolean = isAPISucceeded(saveModelStatus) && saveModelStatus.result - const failed: boolean = (isAPISucceeded(saveModelStatus) && !saveModelStatus.result) || - isAPIFailed(saveModelStatus) - if (succeeded) { - nextProps.addNotification({ color: 'success', message: 'Successfully saved model' }) - this.setState({ notified: true }) - push(`/applications/${applicationId}`) - } else if (failed) { - nextProps.addNotification({ color: 'error', message: 'Something went wrong. Try again later' }) - this.setState({ notified: true }) - } - } - } - - componentWillMount() { - const { applicationId } = this.props.match.params - const { fetchApplicationById } = this.props - - fetchApplicationById({id: applicationId}) - } - componentDidMount() { const { userInfoStatus, history } = this.props const { applicationId } = this.props.match.params + + this.props.fetchApplicationById(this.props.match.params) + const userInfo: UserInfo = isAPISucceeded(userInfoStatus) && userInfoStatus.result if (userInfo) { - const canEdit: boolean = userInfo.roles.some((userRole: UserRole) => { - return String(userRole.applicationId) === applicationId && - (userRole.role === role.editor || userRole.role === role.owner) + const canEdit: boolean = userInfo.applicationRoles.some((role: UserApplicationRole) => { + return String(role.applicationId) === applicationId && + (role.role === applicationRole.editor || role.role === applicationRole.admin) }) if (!canEdit) { history.goBack() @@ -73,29 +51,50 @@ class SaveModel extends React.Component { } } + static getDerivedStateFromProps(nextProps: ModelProps, prevState: ModelState){ + const { updateModelStatus } = nextProps + const { push } = nextProps.history + const { submitting, notified } = prevState + const { projectId, applicationId } = nextProps.match.params + + // Close modal when API successfully finished + if (submitting && !notified) { + const succeeded: boolean = isAPISucceeded(updateModelStatus) && updateModelStatus.result + const failed: boolean = (isAPISucceeded(updateModelStatus) && !updateModelStatus.result) || + isAPIFailed(updateModelStatus) + if (succeeded) { + nextProps.addNotification({ color: 'success', message: 'Successfully saved model' }) + push(`/projects/${projectId}/applications/${applicationId}`) + return { notified: true } + } else if (failed) { + nextProps.addNotification({ color: 'error', message: 'Something went wrong. Try again later' }) + return { notified: true } + } + } + } + render() { const { application } = this.props - const targetStatus = { application } return( ) } renderForm(result) { - const { mode } = this.props + const { method } = this.props return (

    - {mode === 'edit' ? 'Edit' : 'Add'} Model + {method === 'patch' ? 'Edit' : 'Add'} Model

    - { mode === 'edit' ? : null } + { method === 'patch' ? : null }
    ) @@ -104,7 +103,7 @@ class SaveModel extends React.Component { type ModelProps = StateProps & DispatchProps - & RouteComponentProps<{applicationId: string, modelId?: string}> + & RouteComponentProps<{projectId: number, applicationId: string, modelId: number}> & CustomProps interface ModelState { @@ -115,19 +114,19 @@ interface ModelState { interface StateProps { model: APIRequest application: APIRequest - saveModelStatus: APIRequest + updateModelStatus: APIRequest userInfoStatus: APIRequest } interface CustomProps { - mode: string + method: string } const mapStateToProps = (state: any, extraProps: CustomProps): StateProps => ( { - application: state.fetchApplicationByIdReducer.applicationById, - model: state.fetchModelByIdReducer.modelById, - saveModelStatus: state.saveModelReducer.saveModel, + model: state.fetchModelByIdReducer.fetchModelById, + application: state.fetchApplicationByIdReducer.fetchApplicationById, + updateModelStatus: state.updateModelReducer.updateModel, userInfoStatus: state.userInfoReducer.userInfo, ...state.form, ...extraProps @@ -135,21 +134,23 @@ const mapStateToProps = (state: any, extraProps: CustomProps): StateProps => ( ) export interface DispatchProps { - fetchApplicationById: (params) => Promise - saveModel: (params) => Promise + fetchModelById: (params: FetchModelByIdParam) => Promise + fetchApplicationById: (params: FetchApplicationByIdParam) => Promise + updateModel: (params: UpdateModelParam) => Promise addNotification: (params) => Promise } const mapDispatchToProps = (dispatch): DispatchProps => { return { - fetchApplicationById: (params) => fetchApplicationByIdDispatcher(dispatch, params), - saveModel: (params) => saveModelDispatcher(dispatch, params), + fetchModelById: (params: FetchModelByIdParam) => fetchModelByIdDispatcher(dispatch, params), + fetchApplicationById: (params: FetchApplicationByIdParam) => fetchApplicationByIdDispatcher(dispatch, params), + updateModel: (params: UpdateModelParam) => updateModelDispatcher(dispatch, params), addNotification: (params) => dispatch(addNotification(params)) } } export default withRouter( - connect & CustomProps>( + connect & CustomProps>( mapStateToProps, mapDispatchToProps )(SaveModel) ) From 3649c350b59aeee1ca47ed506973f63a35dd29a8 Mon Sep 17 00:00:00 2001 From: Hattori Keigo Date: Mon, 8 Apr 2019 11:44:52 +0900 Subject: [PATCH 21/92] Renew `Models` --- .../App/Models/ModelsDeleteForm.tsx | 183 ++++++++------ .../App/Models/ModelsStatusTable.tsx | 148 ----------- frontend/src/components/App/Models/index.tsx | 230 +++++++++++------- 3 files changed, 244 insertions(+), 317 deletions(-) delete mode 100644 frontend/src/components/App/Models/ModelsStatusTable.tsx diff --git a/frontend/src/components/App/Models/ModelsDeleteForm.tsx b/frontend/src/components/App/Models/ModelsDeleteForm.tsx index ed2b978..2ae54ee 100644 --- a/frontend/src/components/App/Models/ModelsDeleteForm.tsx +++ b/frontend/src/components/App/Models/ModelsDeleteForm.tsx @@ -1,46 +1,104 @@ import * as React from 'react' import { connect } from 'react-redux' -import { Button } from 'reactstrap' -import { reduxForm, InjectedFormProps } from 'redux-form' +import { Button, Table, Row } from 'reactstrap' +import { Link } from 'react-router-dom' +import { Formik, Form } from 'formik' import { Model } from '@src/apis' -import ModelsStatusTable from './ModelsStatusTable' import { ControlMode } from './index' +import { Checkbox } from '@common/Field' -class ModelsDeleteForm extends React.Component { + +class ModelsDeleteForm extends React.Component { constructor(props, context) { super(props, context) this.handleDiscardChanges = this.handleDiscardChanges.bind(this) - } - - componentWillReceiveProps(nextProps: ModelsDeleteFormProps) { - const { mode, pristine, changeMode } = nextProps - - if (mode === ControlMode.VIEW_MODELS_STATUS && !pristine) { - changeMode(ControlMode.SELECT_TARGETS) - } else if (mode === ControlMode.SELECT_TARGETS && pristine) { - changeMode(ControlMode.VIEW_MODELS_STATUS) - } + this.handleModeChanges = this.handleModeChanges.bind(this) } render() { const { - onSubmit, - handleSubmit, + onSubmit } = this.props return ( -
    -
    - {this.renderDiscardButton()} -
    - -
    - {this.renderSubmitButtons()} - + + {({ isInitialValid, isSubmitting }) => ( +
    + {this.handleModeChanges(isInitialValid)} +
    + {this.renderDiscardButton()} +
    + + {this.renderTableHead()} + {this.renderTableBody()} +
    +
    + {this.renderSubmitButtons(isInitialValid, isSubmitting)} +
    + )} +
    + ) + } + + /** + * Render head row of the table + */ + renderTableHead = () => { + return ( + + + Description + Registered Date + + + ) + } + + /** + * Render body of the table + * + * Render Model names + * Each Model is rendered with a deploy check box on viewing/deleting mode + * @param models Models to be shown (Currently show all, but should be filtered) + */ + renderTableBody = () => { + const { projectId, applicationId, canEdit, models } = this.props + + // Button to delete Model (for deleting k8s models) + const deleteCheckButton = (modelName: string, modelId: number) => { + return ( + + { canEdit ? + + : null } + + {modelName} + + + ) + } + + return ( + + {models.map( + (model: Model, index: number) => ( + + + {deleteCheckButton(model.description, model.modelId)} + + + {model.registerDate.toUTCString()} + + + ) + )} + ) } @@ -66,14 +124,10 @@ class ModelsDeleteForm extends React.Component { * Show delete button if selected targets exist * Show save button if editing deploy status */ - renderSubmitButtons(): JSX.Element { - const { - mode, - submitting, - pristine - } = this.props + renderSubmitButtons(isInitialValid, isSubmitting): JSX.Element { + const { mode } = this.props - const showSubmitButton: boolean = mode !== ControlMode.VIEW_MODELS_STATUS + const showSubmitButton: boolean = mode !== ControlMode.VIEW_SERVICES_STATUS if (!showSubmitButton) { return null @@ -89,7 +143,7 @@ class ModelsDeleteForm extends React.Component {
    ) - return submitting ? submittingLoader : buttons(paramsMap[mode]) - } - - renderSubmitButtonElements() { - const { - submitting, - pristine - } = this.props - - const paramsMap = { - [ControlMode.SELECT_TARGETS]: { color: 'danger', icon: 'trash', text: 'Delete Models' }, - } - - return ( -
    - -
    - ) + return isSubmitting ? submittingLoader : buttons(paramsMap[mode]) } // Handle event methods handleDiscardChanges(event): void { - const { changeMode, reset } = this.props - reset() - changeMode(ControlMode.VIEW_MODELS_STATUS) + const { changeMode } = this.props + changeMode(ControlMode.VIEW_SERVICES_STATUS) + } + + handleModeChanges(isInitialValid): void { + const { mode, changeMode } = this.props + if (mode === ControlMode.VIEW_SERVICES_STATUS && !isInitialValid) { + changeMode(ControlMode.SELECT_TARGETS) + } else if (mode === ControlMode.SELECT_TARGETS && isInitialValid) { + changeMode(ControlMode.VIEW_SERVICES_STATUS) + } } } interface ModelsDeleteFormCustomProps { - applicationType: string + projectId applicationId mode: ControlMode models: Model[] canEdit: boolean - onSubmit: (e) => Promise + onSubmit: (e) => void changeMode: (mode: ControlMode) => void } +interface ModelDeleteFormCustomState {} + interface StateProps { initialValues: { status @@ -162,8 +202,8 @@ const mapStateToProps = (state: any, extraProps: ModelsDeleteFormCustomProps) => // Map of model ID to delete flag const initialDeleteStatus: { [x: string]: boolean } = extraProps.models - .map((model) => ({[model.id]: false})) - .reduce((l, r) => Object.assign(l, r), {}) + .map((model) => ({[model.modelId]: false})) + .reduce((l, r) => Object.assign(l, r), {}) return { ...state.form, @@ -179,13 +219,6 @@ const mapDispatchToProps = (dispatch): {} => { return { } } -type ModelsDeleteFormProps - = StateProps & ModelsDeleteFormCustomProps & InjectedFormProps<{}, ModelsDeleteFormCustomProps> +type ModelsDeleteFormProps = StateProps & ModelsDeleteFormCustomProps -export default connect(mapStateToProps, mapDispatchToProps)( - reduxForm<{}, ModelsDeleteFormCustomProps>( - { - form: 'deployStatusForm' - } - )(ModelsDeleteForm) -) +export default connect(mapStateToProps, mapDispatchToProps)(ModelsDeleteForm) diff --git a/frontend/src/components/App/Models/ModelsStatusTable.tsx b/frontend/src/components/App/Models/ModelsStatusTable.tsx deleted file mode 100644 index 8229118..0000000 --- a/frontend/src/components/App/Models/ModelsStatusTable.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import * as React from 'react' -import { connect } from 'react-redux' -import { CustomInput, Table, Row } from 'reactstrap' -import { Field, InjectedFormProps } from 'redux-form' -import { Link } from 'react-router-dom' - -import { Model } from '@src/apis' -import { ControlMode } from './index' - -/** - * Table for showing models status - */ -class ModelsStatusTable extends React.Component { - constructor(props, context) { - super(props, context) - - this.state = { - tooltipOpen: {} - } - } - - render() { - const { models } = this.props - - return ( - - {this.renderTableHead()} - {this.renderTableBody(models)} -
    - ) - } - - toggleTooltip(tag) { - return () => { - const nextTooltipOpen = { - ...this.state.tooltipOpen, - [tag]: !this.state.tooltipOpen[tag] - } - - this.setState({ - tooltipOpen: nextTooltipOpen - }) - } - } - - /** - * Render head row of the table - */ - renderTableHead = () => { - return ( - - - DescriptionRegister Date - - - ) - } - - /** - * Render body of the table - * - * Render Model names - * Each Model is rendered with a deploy check box on viewing/deleting mode - * @param models Models to be shown (Currently show all, but should be filtered) - */ - renderTableBody = (models) => { - const { applicationType, applicationId, canEdit } = this.props - - // Button to delete Model (for deleting k8s models) - const deleteCheckButton = (modelName: string, modelId: string) => { - return ( - - { applicationType === 'kubernetes' && canEdit ? - - : null } - - {modelName} - - - ) - } - - return ( - - {models.map( - (model, index: number) => ( - - - {deleteCheckButton(model.name, model.id)} - - - {model.registeredDate.toUTCString()} - - - ) - )} - - ) - } - -} - -const CustomCheckBox = (props) => { - const { input, id, label } = props - - return ( - - ) -} - -interface ModelsStatusFormCustomProps { - applicationType: string - applicationId - models: Model[] - mode: ControlMode - canEdit: boolean -} - -export interface DispatchProps { - dispatchChange -} - -const mapDispatchToProps = (dispatch): DispatchProps => { - return { - dispatchChange: (field, value, changeMethod) => dispatch(changeMethod(field, value)) - } -} - -const mapStateToProps = (state: any, extraProps: ModelsStatusFormCustomProps) => { - return {} -} - -type ModelsStatusProps = DispatchProps & ModelsStatusFormCustomProps & InjectedFormProps<{}, ModelsStatusFormCustomProps> - -export default connect(mapStateToProps, mapDispatchToProps)(ModelsStatusTable) diff --git a/frontend/src/components/App/Models/index.tsx b/frontend/src/components/App/Models/index.tsx index 9b30e09..197ec14 100644 --- a/frontend/src/components/App/Models/index.tsx +++ b/frontend/src/components/App/Models/index.tsx @@ -3,60 +3,72 @@ import { connect } from 'react-redux' import { withRouter, RouteComponentProps } from 'react-router' import { Button, Modal, ModalBody, ModalHeader, Row, Col } from 'reactstrap' -import { APIRequest, isAPISucceeded } from '@src/apis/Core' -import { Model, Application, UserInfo, UserRole } from '@src/apis' +import { APIRequest, isAPISucceeded, isAPIFailed } from '@src/apis/Core' +import { + Kubernetes, Model, SyncKubernetesParam, Application, UserInfo, + FetchApplicationByIdParam, FetchModelByIdParam, IdParam, FetchKubernetesByIdParam +} from '@src/apis' import { addNotification, + fetchAllKubernetesDispatcher, fetchApplicationByIdDispatcher, fetchAllModelsDispatcher, - deleteKubernetesModelsDispatcher, + deleteModelsDispatcher, + syncKubernetesDispatcher } from '@src/actions' -import { AddModelFileModal } from '@components/App/Model/Modals/AddModelFileModal' import { APIRequestResultsRenderer } from '@common/APIRequestResultsRenderer' import ModelsDeleteForm from './ModelsDeleteForm' -import { role } from '../Admin/constants' + export enum ControlMode { - VIEW_MODELS_STATUS, + VIEW_SERVICES_STATUS, SELECT_TARGETS, - UPLOAD_MODEL } -type ModelsStatusProps = DispatchProps & StateProps & RouteComponentProps<{applicationId: string}> +type ModelsStatusProps = DispatchProps & StateProps & RouteComponentProps<{projectId: number, applicationId: string}> + +interface ModelsStatusState { + controlMode: ControlMode + isDeleteModelsModalOpen: boolean + selectedData: IdParam[] + submitted: boolean + syncSubmitted: boolean + syncNotified: boolean +} -class Models extends React.Component { +class Models extends React.Component { constructor(props, context) { super(props, context) this.state = { - controlMode: ControlMode.VIEW_MODELS_STATUS, - isAddModelFileModalOpen: false, + controlMode: ControlMode.VIEW_SERVICES_STATUS, isDeleteModelsModalOpen: false, - selectedData: { models: [] }, + selectedData: [], submitted: false, syncSubmitted: false, syncNotified: false } this.onSubmitDelete = this.onSubmitDelete.bind(this) - this.deleteKubernetesModels = this.deleteKubernetesModels.bind(this) + this.deleteModels = this.deleteModels.bind(this) this.toggleDeleteModelsModal = this.toggleDeleteModelsModal.bind(this) - this.toggleAddModelFileModalOpen = this.toggleAddModelFileModalOpen.bind(this) + this.syncModels = this.syncModels.bind(this) this.renderModels = this.renderModels.bind(this) this.changeMode = this.changeMode.bind(this) this.complete = this.complete.bind(this) } - componentWillMount() { - const { applicationId } = this.props.match.params - - this.props.fetchApplicationById(applicationId) - this.props.fetchAllModels(applicationId) + componentDidMount() { + this.props.fetchApplicationById(this.props.match.params) + this.props.fetchAllModels(this.props.match.params) } - componentWillReceiveProps(nextProps: ModelsStatusProps) { - const { deleteKubernetesModelsStatus } = nextProps - const { controlMode, submitted } = this.state + static getDerivedStateFromProps(nextProps: ModelsStatusProps, prevState: ModelsStatusState){ + const { + deleteModelsStatus, + syncKubernetesStatus + } = nextProps + const { controlMode, submitted, syncSubmitted, syncNotified } = prevState const checkAllApiResultStatus = (result: APIRequest) => @@ -64,10 +76,34 @@ class Models extends React.Component { result.result.reduce((p, c) => (p && c)) if (submitted && controlMode === ControlMode.SELECT_TARGETS) { - if (checkAllApiResultStatus(deleteKubernetesModelsStatus)) { - this.complete({ color: 'success', message: 'Successfully changed deletion' }) + if (checkAllApiResultStatus(deleteModelsStatus)) { + nextProps.addNotification({ color: 'success', message: 'Successfully changed deletion' }) } else { - this.complete({ color: 'error', message: 'Something went wrong, try again later' }) + nextProps.addNotification({ color: 'error', message: 'Something went wrong, try again later' }) + } + nextProps.fetchAllModels(nextProps.match.params) + return { + controlMode: ControlMode.VIEW_SERVICES_STATUS, + submitted: false, + selectedData: [] + } + } + + if (syncSubmitted && !syncNotified) { + const succeeded: boolean = isAPISucceeded(syncKubernetesStatus) && syncKubernetesStatus.result + const failed: boolean = (isAPISucceeded(syncKubernetesStatus) && !syncKubernetesStatus.result) || isAPIFailed(syncKubernetesStatus) + + if (succeeded) { + nextProps.addNotification({ color: 'success', message: 'Successfully synced application' }) + } else if (failed) { + nextProps.addNotification({ color: 'error', message: 'Something went wrong. Try again later' }) + } + return { + controlMode: ControlMode.VIEW_SERVICES_STATUS, + submitted: false, + syncSubmitted: false, + syncNotified: true, + selectedData: [] } } } @@ -80,13 +116,11 @@ class Models extends React.Component { if (isAPISucceeded(settings) && settings.result.auth) { statuses.userInfoStatus = userInfoStatus } - if ( this.props.match.params.applicationId === 'add' ) { - return null - } return ( ) @@ -107,19 +141,20 @@ class Models extends React.Component { changeMode } = this - const { kubernetesId, name } = fetchedResults.application - const { applicationId } = this.props.match.params + const kubernetesMode = fetchedResults.kuberneteses.length > 0 + const applicationName = fetchedResults.application.name + const { projectId, applicationId } = this.props.match.params const models: Model[] = fetchedResults.models const onSubmitMap = { - [ControlMode.VIEW_MODELS_STATUS]: onSubmitNothing, + [ControlMode.VIEW_SERVICES_STATUS]: onSubmitNothing, [ControlMode.SELECT_TARGETS]: onSubmitDelete, } return ( this.renderContent( { changeMode={changeMode} canEdit={canEdit} />, - name, - kubernetesId, + applicationName, + kubernetesMode, canEdit ) ) } - renderContent = (content: JSX.Element, applicationName, kubernetesId, canEdit: boolean): JSX.Element => { + renderContent = (content: JSX.Element, applicationName, kubernetesMode, canEdit: boolean): JSX.Element => { return (
    - {this.renderTitle(applicationName, canEdit)} - + {this.renderTitle(applicationName, kubernetesMode, canEdit)}

    - + Models


    @@ -159,17 +188,7 @@ class Models extends React.Component { ) } - renderTitle = (applicationName, canEdit: boolean): JSX.Element => { - const button = ( - - ) + renderTitle = (applicationName, kubernetesMode, canEdit: boolean): JSX.Element => { return ( @@ -179,12 +198,39 @@ class Models extends React.Component { - {canEdit ? button : null} + {kubernetesMode && canEdit ? this.renderKubernetesControlButtons() : null} ) } + renderKubernetesControlButtons() { + const { push } = this.props.history + const { syncModels } = this + const { projectId, applicationId } = this.props.match.params + + return ( + + + {` `} + + + ) + } + renderConfirmDeleteHostModal(): JSX.Element { const { isDeleteModelsModalOpen } = this.state @@ -193,7 +239,7 @@ class Models extends React.Component { } const executeDeletion = (event) => { - this.deleteKubernetesModels(this.state.selectedData.models) + this.deleteModels(this.state.selectedData) this.toggleDeleteModelsModal() } @@ -227,6 +273,11 @@ class Models extends React.Component { ) } + syncModels(): void { + this.setState({ syncSubmitted: true, syncNotified: false }) + this.props.syncKubernetes(this.props.match.params) + } + // Event handing methods toggleDeleteModelsModal(): void { this.setState({ @@ -234,15 +285,7 @@ class Models extends React.Component { }) } - toggleAddModelFileModalOpen(): void { - this.setState({ - isAddModelFileModalOpen: !this.state.isAddModelFileModalOpen - }) - } - - onSubmitNothing(params): void { - this.setState({}) - } + onSubmitNothing(params): void {} /** * Handle submit and call API to delete models @@ -253,15 +296,12 @@ class Models extends React.Component { onSubmitDelete(params): void { this.setState({ isDeleteModelsModalOpen: true, - selectedData: { - models: params.delete.models, - } + selectedData: params }) } - deleteKubernetesModels(params): Promise { - const { deleteKubernetesModels } = this.props - const { applicationId } = this.props.match.params + deleteModels(params): Promise { + const { projectId, applicationId } = this.props.match.params const apiParams = Object.entries(params) @@ -269,13 +309,14 @@ class Models extends React.Component { .map( ([key, value]) => ( { + projectId, applicationId, - modelId: key + modelId: Number(key) })) this.setState({ submitted: true }) - return deleteKubernetesModels(apiParams) + return this.props.deleteModels(apiParams) } // Utils @@ -289,36 +330,33 @@ class Models extends React.Component { * Fetch models through API again */ complete(param) { - const { - fetchAllModels - } = this.props - const { - applicationId - } = this.props.match.params - this.props.addNotification(param) - fetchAllModels(applicationId) + this.props.fetchAllModels(this.props.match.params) this.setState({ - controlMode: ControlMode.VIEW_MODELS_STATUS, + controlMode: ControlMode.VIEW_SERVICES_STATUS, submitted: false, - selectedData: { models: [] } + selectedData: [] }) } } export interface StateProps { + syncKubernetesStatus: APIRequest + kuberneteses: APIRequest application: APIRequest models: APIRequest - deleteKubernetesModelsStatus: APIRequest + deleteModelsStatus: APIRequest userInfoStatus: APIRequest settings: APIRequest } const mapStateToProps = (state): StateProps => { const props = { - application: state.fetchApplicationByIdReducer.applicationById, - models: state.fetchAllModelsReducer.models, - deleteKubernetesModelsStatus: state.deleteKubernetesModelsReducer.deleteKubernetesModels, + syncKubernetesStatus: state.syncKubernetesReducer.syncKubernetes, + kuberneteses: state.fetchAllKubernetesReducer.fetchAllKubernetes, + application: state.fetchApplicationByIdReducer.fetchApplicationById, + models: state.fetchAllModelsReducer.fetchAllModels, + deleteModelsStatus: state.deleteModelsReducer.deleteModels, userInfoStatus: state.userInfoReducer.userInfo, settings: state.settingsReducer.settings } @@ -327,21 +365,25 @@ const mapStateToProps = (state): StateProps => { export interface DispatchProps { addNotification - fetchApplicationById: (id: string) => Promise - fetchAllModels: (applicationId: string) => Promise - deleteKubernetesModels: (params) => Promise + syncKubernetes: (params: SyncKubernetesParam) => Promise + fetchAllKubernetes: (params: FetchKubernetesByIdParam) => Promise + fetchApplicationById: (params: FetchApplicationByIdParam) => Promise + fetchAllModels: (params: FetchModelByIdParam) => Promise + deleteModels: (params: IdParam[]) => Promise } const mapDispatchToProps = (dispatch): DispatchProps => { return { addNotification: (params) => dispatch(addNotification(params)), - fetchApplicationById: (id: string) => fetchApplicationByIdDispatcher(dispatch, { id }), - fetchAllModels: (applicationId: string) => fetchAllModelsDispatcher(dispatch, { applicationId }), - deleteKubernetesModels: (params) => deleteKubernetesModelsDispatcher(dispatch, params), + syncKubernetes: (params: SyncKubernetesParam) => syncKubernetesDispatcher(dispatch, params), + fetchAllKubernetes: (params: FetchKubernetesByIdParam) => fetchAllKubernetesDispatcher(dispatch, params), + fetchApplicationById: (params: FetchApplicationByIdParam) => fetchApplicationByIdDispatcher(dispatch, params), + fetchAllModels: (params: FetchModelByIdParam) => fetchAllModelsDispatcher(dispatch, params), + deleteModels: (params: IdParam[]) => deleteModelsDispatcher(dispatch, params), } } export default withRouter( - connect>( + connect>( mapStateToProps, mapDispatchToProps )(Models)) From a2e0d9164390dc806f7ff21b83066db7c7e04c88 Mon Sep 17 00:00:00 2001 From: Hattori Keigo Date: Mon, 8 Apr 2019 11:45:12 +0900 Subject: [PATCH 22/92] Renew `Service` --- .../ServiceDeploymentForm.tsx | 277 ++++++++++------- .../ServiceDeployment/SingleServiceForm.tsx | 135 ++++++++ .../App/Service/ServiceDeployment/index.tsx | 289 +++++++++++------- .../ServiceDescriptionForm.tsx | 130 -------- .../App/Service/ServiceDescription/index.tsx | 173 ----------- frontend/src/components/App/Service/index.tsx | 113 ++++--- 6 files changed, 539 insertions(+), 578 deletions(-) create mode 100644 frontend/src/components/App/Service/ServiceDeployment/SingleServiceForm.tsx delete mode 100644 frontend/src/components/App/Service/ServiceDescription/ServiceDescriptionForm.tsx delete mode 100644 frontend/src/components/App/Service/ServiceDescription/index.tsx diff --git a/frontend/src/components/App/Service/ServiceDeployment/ServiceDeploymentForm.tsx b/frontend/src/components/App/Service/ServiceDeployment/ServiceDeploymentForm.tsx index 66dd2b4..4422865 100644 --- a/frontend/src/components/App/Service/ServiceDeployment/ServiceDeploymentForm.tsx +++ b/frontend/src/components/App/Service/ServiceDeployment/ServiceDeploymentForm.tsx @@ -1,128 +1,195 @@ import * as React from 'react' import { connect } from 'react-redux' -import { reduxForm, InjectedFormProps } from 'redux-form' -import { Card, CardBody, Form, Button } from 'reactstrap' +import { Card, CardBody, Button } from 'reactstrap' -import { KubernetesHost, Model, Service } from '@src/apis' -import DeploymentSettingFormFields, { kubernetesDeploymentDefultSettings } from '@components/misc/Forms/DeploymentSettingFormFields' +import * as Yup from "yup"; +import { ErrorMessage, Field } from "formik"; + + +export const ServiceDeploymentSchema = { + replicasDefault: Yup.number() + .required('Required') + .positive() + .integer(), + replicasMaximum: Yup.number() + .positive() + .integer() + .moreThan(Yup.ref('replicasDefault')), + replicasMinimum: Yup.number() + .positive() + .integer() + .lessThan(Yup.ref('replicasDefault')), + autoscaleCpuThreshold: Yup.number() + .positive() + .integer() + .min(0) + .max(100), + policyMaxSurge: Yup.number() + .positive() + .integer(), + policyMaxUnavailable: Yup.number() + .positive() + .integer(), + policyWaitSeconds: Yup.number() + .positive() + .integer() + .max(86400), + containerImage: Yup.string() + .required('Required') + .max(512), + serviceGitUrl: Yup.string() + .max(512), + serviceGitBranch: Yup.string() + .max(512), + serviceBootScript: Yup.string() + .max(512), + resourceRequestCpu: Yup.number() + .required('Required') + .positive() + .integer(), + resourceRequestMemory: Yup.string() + .required('Required') + .max(16), + resourceLimitCpu: Yup.number() + .positive() + .integer(), + resourceLimitMemory: Yup.string() + .max(16) +} class ServiceDeploymentFormImpl extends React.Component { render() { - const { handleSubmit, onSubmit } = this.props - const serviceSpeicificForm = this.renderServiceSpecificForm() + const { errors, touched } = this.props return ( -
    - {serviceSpeicificForm} - {serviceSpeicificForm ? this.renderButtons() : null} -
    + + + {errors.containerImage && touched.containerImage ? ( +
    {errors.containerImage}
    + ) : null} + + + {errors.serviceGitUrl && touched.serviceGitUrl ? ( +
    {errors.serviceGitUrl}
    + ) : null} + + + {errors.serviceGitBranch && touched.serviceGitBranch ? ( +
    {errors.serviceGitBranch}
    + ) : null} + + + {errors.serviceBootScript && touched.serviceBootScript ? ( +
    {errors.serviceBootScript}
    + ) : null} + +
    -
    - ) - } - /** - * - * Render form fields to setup service specific - * options. The options depend on given `applicationType` - * (simple or Kubernetes). - * - * @param applicationType Type of application to set up - * @returns {JSX.Element} Config fields - */ - renderServiceSpecificForm(): JSX.Element { - const { applicationType, mode, models, kubernetesHost } = this.props - - return ( - - - - ) - } - - /** - * Render control buttons - * - * Put on footer of this modal - */ - renderButtons(): JSX.Element { - const { submitting, reset, mode } = this.props + + + + {errors.resourceRequestCpu && touched.resourceRequestCpu ? ( +
    {errors.resourceRequestCpu}
    + ) : null} + + + {errors.resourceRequestMemory && touched.resourceRequestMemory ? ( +
    {errors.resourceRequestMemory}
    + ) : null} + + + {errors.resourceLimitCpu && touched.resourceLimitCpu ? ( +
    {errors.resourceLimitCpu}
    + ) : null} + + + {errors.resourceLimitMemory && touched.resourceLimitMemory ? ( +
    {errors.resourceLimitMemory}
    + ) : null} + +
    +
    - if (submitting) { - return ( - -
    - Submitting... - - ) - } + + + + {errors.replicasDefault && touched.replicasDefault ? ( +
    {errors.replicasDefault}
    + ) : null} + + + {errors.replicasMaximum && touched.replicasMaximum ? ( +
    {errors.replicasMaximum}
    + ) : null} + + + {errors.replicasMinimum && touched.replicasMinimum ? ( +
    {errors.replicasMinimum}
    + ) : null} + +
    +
    - return ( - - - {' '} - - + + + + {errors.autoscaleCpuThreshold && touched.autoscaleCpuThreshold ? ( +
    {errors.autoscaleCpuThreshold}
    + ) : null} + + + {errors.policyMaxSurge && touched.policyMaxSurge ? ( +
    {errors.policyMaxSurge}
    + ) : null} + + + {errors.policyMaxUnavailable && touched.policyMaxUnavailable ? ( +
    {errors.policyMaxUnavailable}
    + ) : null} + + + {errors.policyWaitSeconds && touched.policyWaitSeconds ? ( +
    {errors.policyWaitSeconds}
    + ) : null} + +
    +
    + ) } } -interface CustomProps { - mode: string - models: Model[] - onCancel - onSubmit - applicationType: string - kubernetesHost?: KubernetesHost - service?: Service +export const ServiceDeploymentDefaultInitialValues = { + replicasDefault: 1, + replicasMaximum: 1, + replicasMinimum: 1, + autoscaleCpuThreshold: 80, + policyMaxSurge: 1, + policyMaxUnavailable: 0, + policyWaitSeconds: 300, + containerImage: 'rekcurd/rekcurd:python-latest', + serviceGitUrl: null, + serviceGitBranch: null, + serviceBootScript: null, + resourceRequestCpu: 1, + resourceRequestMemory: '512Mi', + resourceLimitCpu: 1, + resourceLimitMemory: '512Mi', } -interface StateProps { - initialValues +interface CustomProps { + errors + touched } -type ServiceDeploymentFormProps = - CustomProps - & StateProps - & InjectedFormProps<{}, CustomProps> - -const generateInitialValues = (props: CustomProps) => ( - { - [props.mode]: { - service: { - ...kubernetesDeploymentDefultSettings, - ...props.service, - kubernetesId: props.kubernetesHost.id, - applicationType: 'kubernetes' - } - } - } -) +type ServiceDeploymentFormProps = CustomProps -export const ServiceDeploymentForm = - connect( - (state: any, extraProps: CustomProps) => ({ - ...extraProps, - ...state.form, - initialValues: generateInitialValues(extraProps) - }) - )(reduxForm<{}, CustomProps>({ - form: 'serviceForm', - touchOnChange: true, - enableReinitialize: true - })(ServiceDeploymentFormImpl)) +export const ServiceDeploymentForm = connect( + (state: any, extraProps: CustomProps) => ({ + ...state.form, + ...extraProps, + }) +)(ServiceDeploymentFormImpl) diff --git a/frontend/src/components/App/Service/ServiceDeployment/SingleServiceForm.tsx b/frontend/src/components/App/Service/ServiceDeployment/SingleServiceForm.tsx new file mode 100644 index 0000000..2ae155b --- /dev/null +++ b/frontend/src/components/App/Service/ServiceDeployment/SingleServiceForm.tsx @@ -0,0 +1,135 @@ +import * as React from 'react' +import { connect } from 'react-redux' +import { Card, CardBody, Button } from 'reactstrap' + +import { serviceLevel } from '@components/Common/Enum' +import { Model } from '@src/apis' + +import * as Yup from "yup"; +import { ErrorMessage, Field } from "formik"; + + +export const SingleServiceSchema = { + displayName: Yup.string() + .required('Required') + .max(128), + description: Yup.string(), + serviceLevel: Yup.string() + .required('Required') + .oneof([serviceLevel.development.toString(), serviceLevel.staging.toString(), + serviceLevel.production.toString(), serviceLevel.beta.toString(), + serviceLevel.sandbox.toString()]), + version: Yup.string() + .required('Required') + .oneof(['v3']), + serviceInsecureHost: Yup.string() + .required('Required') + .max(512), + serviceInsecurePort: Yup.number() + .required('Required') + .positive() + .integer(), + serviceModelAssignment: Yup.number() + .required('Required') + .positive() + .integer(), +} + +class SingleServiceFormImpl extends React.Component { + render() { + const { models, errors, touched } = this.props + + const serviceLevels = Object.values(serviceLevel).map((serviceLevelName: string) => { + return ( + + ) + }) + const versions = ( + + ) + const serviceModelAssignments = models.map( (model: Model) => { + return ( + + ) + }) + + return ( + + + + {errors.displayName && touched.displayName ? ( +
    {errors.displayName}
    + ) : null} + + + {serviceLevels} + + {errors.serviceLevel && touched.serviceLevel ? ( +
    {errors.serviceLevel}
    + ) : null} + + + {versions} + + {errors.version && touched.version ? ( +
    {errors.version}
    + ) : null} + + + {errors.serviceInsecureHost && touched.serviceInsecureHost ? ( +
    {errors.serviceInsecureHost}
    + ) : null} + + + {errors.serviceInsecurePort && touched.serviceInsecurePort ? ( +
    {errors.serviceInsecurePort}
    + ) : null} + + + {serviceModelAssignments} + + {errors.serviceModelAssignment && touched.serviceModelAssignment ? ( +
    {errors.serviceModelAssignment}
    + ) : null} + + + {errors.description && touched.description ? ( +
    {errors.description}
    + ) : null} + +
    +
    + ) + } +} + +export const SingleServiceDefaultInitialValues = { + displayName: null, + description: '', + serviceLevel: serviceLevel.development.toString(), + version: 'v3', + serviceInsecureHost: '[::]', + serviceInsecurePort: 5000, + serviceModelAssignment: null, +} + +interface CustomProps { + models: Model[] + errors + touched +} + +type SingleServiceFormProps = CustomProps + +export const SingleServiceForm = connect( + (state: any, extraProps: CustomProps) => ({ + ...state.form, + ...extraProps, + }) +)(SingleServiceFormImpl) diff --git a/frontend/src/components/App/Service/ServiceDeployment/index.tsx b/frontend/src/components/App/Service/ServiceDeployment/index.tsx index 19ace14..487b5c6 100644 --- a/frontend/src/components/App/Service/ServiceDeployment/index.tsx +++ b/frontend/src/components/App/Service/ServiceDeployment/index.tsx @@ -1,19 +1,26 @@ import * as React from 'react' import { connect } from 'react-redux' -import { withRouter, RouteComponentProps, Link } from 'react-router-dom' -import { Alert } from 'reactstrap' +import { withRouter, RouteComponentProps } from 'react-router-dom' +import { Alert, Card, CardBody, Button } from 'reactstrap' +import { Formik, Form } from 'formik' +import * as Yup from "yup"; import { APIRequest, isAPISucceeded, isAPIFailed } from '@src/apis/Core' -import { KubernetesHost, Model, Service, Application } from '@src/apis' import { - fetchKubernetesHostByIdDispatcher, + Service, Model, FetchServiceByIdParam, + FetchModelByIdParam, ServiceDeploymentParam, UpdateServiceParam +} from '@src/apis' +import { fetchServiceByIdDispatcher, fetchAllModelsDispatcher, - saveServiceDispatcher, + saveServiceDeploymentDispatcher, + updateServiceDispatcher, addNotification } from '@src/actions' import { APIRequestResultsRenderer } from '@common/APIRequestResultsRenderer' -import { ServiceDeploymentForm } from './ServiceDeploymentForm' +import * as ServiceDeploymentForm from './ServiceDeploymentForm' +import * as SingleServiceForm from './SingleServiceForm' + /** * Page for adding service @@ -24,7 +31,8 @@ class ServiceDeployment extends React.Component(saveServiceStatus) && saveServiceStatus.result - const failed: boolean = (isAPISucceeded(saveServiceStatus) && !saveServiceStatus.result) || - isAPIFailed(saveServiceStatus) + const succeeded: boolean = isAPISucceeded(saveServiceDeploymentStatus) && saveServiceDeploymentStatus.result + const failed: boolean = (isAPISucceeded(saveServiceDeploymentStatus) && !saveServiceDeploymentStatus.result) || isAPIFailed(saveServiceDeploymentStatus) if (succeeded) { nextProps.addNotification({ color: 'success', message: 'Successfully saved service' }) - this.setState({ notified: true }) - push(`/applications/${applicationId}`) + push(`/projects/${projectId}/applications/${applicationId}`) + return { notified: true } } else if (failed) { nextProps.addNotification({ color: 'error', message: 'Something went wrong. Try again later' }) - this.setState({ notified: true }) + return { notified: true } } } - - } - - componentWillMount() { - this.props.fetchKubernetesHostById({id: this.props.kubernetesId}) - this.props.fetchAllModels({applicationId: this.props.match.params.applicationId}) - if (this.props.mode === 'edit') { - this.props.fetchServiceById( - { - kubernetes: true, - id: this.props.match.params.serviceId, - applicationId: this.props.match.params.applicationId, - kubernetesId: this.props.kubernetesId - } - ) - } } render() { - const { - kubernetesId, - fetchKubernetesHostStatus, - fetchServiceByIdStatus, - fetchAllModelsStatus, - mode - } = this.props - - const targetStatus = - mode === 'edit' - ? { hosts: fetchKubernetesHostStatus, service: fetchServiceByIdStatus, models: fetchAllModelsStatus } - : { hosts: fetchKubernetesHostStatus, models: fetchAllModelsStatus } - - if (!kubernetesId && mode === 'edit') { - // TODO: Check whether this is fine - return null - } + const { fetchServiceByIdStatus, fetchAllModelsStatus } = this.props + const targetStatus = {service: fetchServiceByIdStatus, models: fetchAllModelsStatus} return( + {({ errors, touched, isSubmitting }) => ( +
    + + {this.renderButtons(isSubmitting)} + + )} + + ) + } else { + ValidationSchema = Yup.object().shape({ + ...SingleServiceForm.SingleServiceSchema + }) + if (method === 'post') { + InitialValues = { + ...SingleServiceForm.SingleServiceDefaultInitialValues + } + } else { + InitialValues = { + ...params.service + } + } + FormikContents = ( + + {({ errors, touched, isSubmitting }) => ( +
    + + {this.renderButtons(isSubmitting)} + + )} +
    + ) + } return ( - {this.hostsNotFoundAlert()} - + { FormikContents } ) } - hostsNotFoundAlert() { - const failed: boolean = - isAPIFailed(this.props.fetchKubernetesHostStatus) - - if (failed) { - const link = ( - - here - - ) - return ( - - We could not fetch Kubernetes host. - Check {` `}{link}. - - ) + /** + * Render control buttons + * + * Put on footer of this modal + */ + renderButtons(isSubmitting): JSX.Element { + const { method, kubernetesMode } = this.props + + if (isSubmitting) { + return ( + +
    + Submitting... + + ) } - return null + + return ( + + + {' '} + + + ) } /** @@ -143,22 +194,36 @@ class ServiceDeployment extends React.Component + & RouteComponentProps<{projectId: number, applicationId: string, serviceId?: string}> & CustomProps interface SaveServiceState { @@ -176,51 +241,49 @@ interface SaveServiceState { notified: boolean } +interface CustomProps { + method: string + kubernetesMode: boolean +} + interface StateProps { - application: APIRequest - saveServiceStatus: APIRequest - fetchKubernetesHostStatus: APIRequest fetchServiceByIdStatus: APIRequest fetchAllModelsStatus: APIRequest -} - -interface CustomProps { - mode: string - kubernetesId: string + saveServiceDeploymentStatus: APIRequest + updateServiceStatus: APIRequest } const mapStateToProps = (state: any, extraProps: CustomProps) => ( { - application: state.fetchApplicationByIdReducer.applicationById, - saveServiceStatus: state.saveServiceReducer.saveService, - fetchKubernetesHostStatus: state.fetchKubernetesHostByIdReducer.kubernetesHostById, - fetchServiceByIdStatus: state.fetchServiceByIdReducer.serviceById, - fetchAllModelsStatus: state.fetchAllModelsReducer.models, + fetchServiceByIdStatus: state.fetchServiceByIdReducer.fetchServiceById, + fetchAllModelsStatus: state.fetchAllModelsReducer.fetchAllModels, + saveServiceDeploymentStatus: state.saveServiceDeploymentReducer.saveServiceDeployment, + updateServiceStatus: state.updateServiceReducer.updateService, ...state.form, ...extraProps } ) export interface DispatchProps { - saveServiceDeployment: (params) => Promise - fetchKubernetesHostById: (params) => Promise - fetchServiceById: (params) => Promise - fetchAllModels: (params) => Promise + fetchServiceById: (params: FetchServiceByIdParam) => Promise + fetchAllModels: (params: FetchModelByIdParam) => Promise + saveServiceDeployment: (params: ServiceDeploymentParam) => Promise + updateServiceDeployment: (params: UpdateServiceParam) => Promise addNotification: (params) => Promise } const mapDispatchToProps = (dispatch): DispatchProps => { return { - fetchKubernetesHostById: (params) => fetchKubernetesHostByIdDispatcher(dispatch, params), - fetchServiceById: (params) => fetchServiceByIdDispatcher(dispatch, params), - fetchAllModels: (params) => fetchAllModelsDispatcher(dispatch, params), - saveServiceDeployment: (params) => saveServiceDispatcher(dispatch, params), + fetchServiceById: (params: FetchServiceByIdParam) => fetchServiceByIdDispatcher(dispatch, params), + fetchAllModels: (params: FetchModelByIdParam) => fetchAllModelsDispatcher(dispatch, params), + saveServiceDeployment: (params: ServiceDeploymentParam) => saveServiceDeploymentDispatcher(dispatch, params), + updateServiceDeployment: (params: UpdateServiceParam) => updateServiceDispatcher(dispatch, params), addNotification: (params) => dispatch(addNotification(params)) } } export default withRouter( - connect & CustomProps>( + connect & CustomProps>( mapStateToProps, mapDispatchToProps )(ServiceDeployment) ) diff --git a/frontend/src/components/App/Service/ServiceDescription/ServiceDescriptionForm.tsx b/frontend/src/components/App/Service/ServiceDescription/ServiceDescriptionForm.tsx deleted file mode 100644 index 4295820..0000000 --- a/frontend/src/components/App/Service/ServiceDescription/ServiceDescriptionForm.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import * as React from 'react' -import { connect } from 'react-redux' -import { reduxForm, InjectedFormProps, Field } from 'redux-form' -import { Card, CardBody, Form, Button } from 'reactstrap' - -import { Service } from '@src/apis' -import { SingleFormField } from '@common/Field/SingleFormField' -import { required } from '@common/Field/Validateors' - -class ServiceDescriptionFormImpl extends React.Component { - render() { - const { handleSubmit, onSubmit } = this.props - const serviceSpeicificForm = this.renderServiceSpecificForm() - - return ( - - -
    - {serviceSpeicificForm} - {this.renderButtons()} -
    -
    -
    - ) - } - - /** - * - * - * @param applicationType Type of application to set up - * @returns {JSX.Element} Config fields - */ - renderServiceSpecificForm(): JSX.Element { - - return ( - - - - - ) - } - - /** - * Render control buttons - * - * Put on footer of this modal - */ - renderButtons(): JSX.Element { - const { submitting, reset } = this.props - - if (submitting) { - return ( - -
    - Submitting... - - ) - } - - return ( - - - {' '} - - - ) - } -} - -interface CustomProps { - onCancel - onSubmit - service?: Service -} - -interface StateProps { - initialValues -} - -type ServiceDescriptionFormProps = - CustomProps - & StateProps - & InjectedFormProps<{}, CustomProps> - -const generateInitialValues = (props: CustomProps) => ( - { - edit: { - service: { - ...props.service, - } - } - } -) - -/** - * Description form - * Shown only when description - */ -export const ServiceDescriptionForm = - connect( - (state: any, extraProps: CustomProps) => ({ - initialValues: generateInitialValues(extraProps), - ...extraProps, - ...state.form - }) - )(reduxForm<{}, CustomProps>({ - form: 'serviceDescriptionForm', - touchOnChange: true, - enableReinitialize: true - })(ServiceDescriptionFormImpl)) diff --git a/frontend/src/components/App/Service/ServiceDescription/index.tsx b/frontend/src/components/App/Service/ServiceDescription/index.tsx deleted file mode 100644 index f7129ab..0000000 --- a/frontend/src/components/App/Service/ServiceDescription/index.tsx +++ /dev/null @@ -1,173 +0,0 @@ -import * as React from 'react' -import { connect } from 'react-redux' -import { withRouter, RouteComponentProps } from 'react-router-dom' - -import { APIRequest, isAPISucceeded, isAPIFailed } from '@src/apis/Core' -import { Service, Application, } from '@src/apis' -import { - saveServiceDispatcher, - addNotification, - fetchServiceDescriptionsDispatcher -} from '@src/actions' -import { APIRequestResultsRenderer } from '@common/APIRequestResultsRenderer' -import { ServiceDescriptionForm } from './ServiceDescriptionForm' - -/** - * Page for adding service - * You can create service ONLY when your application is deployed with Kubernetes. - */ -class ServiceDescription extends React.Component { - constructor(props, context) { - super(props, context) - - this.renderForm = this.renderForm.bind(this) - this.onSubmit = this.onSubmit.bind(this) - this.onCancel = this.onCancel.bind(this) - this.state = { - submitting: false, - notified: false, - } - } - - componentWillReceiveProps(nextProps: ServiceDescriptionProps) { - const { saveServiceStatus } = nextProps - const { push } = nextProps.history - const { applicationId } = this.props.match.params - const { submitting, notified } = this.state - - // Close modal when API successfully finished - if (submitting && !notified) { - const succeeded: boolean = isAPISucceeded(saveServiceStatus) && saveServiceStatus.result - const failed: boolean = (isAPISucceeded(saveServiceStatus) && !saveServiceStatus.result) || - isAPIFailed(saveServiceStatus) - if (succeeded) { - nextProps.addNotification({ color: 'success', message: 'Successfully saved service description' }) - this.setState({ notified: true }) - push(`/applications/${applicationId}`) - } else if (failed) { - nextProps.addNotification({ color: 'error', message: 'Something went wrong. Try again later' }) - this.setState({ notified: true }) - } - } - - } - - componentWillMount() { - const { mode } = this.props - const { serviceId, applicationId } = this.props.match.params - - if (mode === 'edit') { - this.props.fetchServiceDescriptionById( - { - id: serviceId, - applicationId - } - ) - } - } - - render() { - const { fetchServiceDescriptionByIdStatus, mode } = this.props - const targetStatus = { service: fetchServiceDescriptionByIdStatus } - - if (mode === 'edit') { - return( - - ) - } - return this.renderForm({}) - } - - renderForm(params) { - const { onSubmit, onCancel } = this - - return ( - - ) - } - - /** - * Handle cancel button - * - * Reset form and move to application list page - */ - onCancel() { - const { push } = this.props.history - const { applicationId } = this.props.match.params - push(`applications/${applicationId}`) - } - - onSubmit(parameters): Promise { - const { saveServiceDescription, mode } = this.props - const { applicationId, serviceId } = this.props.match.params - - const request = { - ...parameters[mode].service, - mode, - id: serviceId, - applicationId, - saveDescription: true - } - - this.setState({ submitting: true, notified: false }) - return saveServiceDescription(request) - } - -} - -type ServiceDescriptionProps = - StateProps & DispatchProps - & RouteComponentProps<{applicationId: string, serviceId?: string}> - & CustomProps - -interface ServiceDescriptionState { - submitting: boolean - notified: boolean -} - -interface StateProps { - application: APIRequest - saveServiceStatus: APIRequest - fetchServiceDescriptionByIdStatus: APIRequest -} - -interface CustomProps { - mode: string -} - -const mapStateToProps = (state: any, extraProps: CustomProps) => ( - { - application: state.fetchApplicationByIdReducer.applicationById, - saveServiceStatus: state.saveServiceReducer.saveService, - fetchServiceDescriptionByIdStatus: state.fetchServiceDescriptionsReducer.serviceDescriptions, - ...state.form, - ...extraProps - } -) - -export interface DispatchProps { - saveServiceDescription: (params) => Promise - fetchServiceDescriptionById: (params) => Promise - addNotification: (params) => Promise -} - -const mapDispatchToProps = (dispatch): DispatchProps => { - return { - fetchServiceDescriptionById: (params) => fetchServiceDescriptionsDispatcher(dispatch, params), - saveServiceDescription: (params) => saveServiceDispatcher(dispatch, params), - addNotification: (params) => dispatch(addNotification(params)) - } -} - -export default withRouter( - connect & CustomProps>( - mapStateToProps, mapDispatchToProps - )(ServiceDescription) -) diff --git a/frontend/src/components/App/Service/index.tsx b/frontend/src/components/App/Service/index.tsx index 498d703..ac664db 100644 --- a/frontend/src/components/App/Service/index.tsx +++ b/frontend/src/components/App/Service/index.tsx @@ -4,16 +4,18 @@ import { withRouter, RouteComponentProps } from 'react-router-dom' import { Row, Col } from 'reactstrap' import { APIRequest, isAPISucceeded, isAPIFailed } from '@src/apis/Core' -import { Service, Application, UserInfo, UserRole } from '@src/apis' +import { + Service, Application, UserInfo, UserApplicationRole, + FetchApplicationByIdParam, ServiceDeploymentParam, Kubernetes, FetchKubernetesByIdParam +} from '@src/apis' import { fetchApplicationByIdDispatcher, - saveServiceDispatcher, - addNotification + saveServiceDeploymentDispatcher, + addNotification, fetchAllKubernetesDispatcher } from '@src/actions' import { APIRequestResultsRenderer } from '@common/APIRequestResultsRenderer' import ServiceDeployment from './ServiceDeployment' -import ServiceDescription from './ServiceDescription' -import { role } from '../Admin/constants' +import { applicationRole } from '@common/Enum' /** * Page for adding service @@ -30,43 +32,18 @@ class SaveService extends React.Component { } } - componentWillReceiveProps(nextProps: ServiceProps) { - const { saveServiceStatus } = nextProps - const { push } = nextProps.history - const { applicationId } = this.props.match.params - const { submitting, notified } = this.state - - // Close modal when API successfully finished - if (submitting && !notified) { - const succeeded: boolean = isAPISucceeded(saveServiceStatus) && saveServiceStatus.result - const failed: boolean = (isAPISucceeded(saveServiceStatus) && !saveServiceStatus.result) || - isAPIFailed(saveServiceStatus) - if (succeeded) { - nextProps.addNotification({ color: 'success', message: 'Successfully saved service' }) - this.setState({ notified: true }) - push(`/applications/${applicationId}`) - } else if (failed) { - nextProps.addNotification({ color: 'error', message: 'Something went wrong. Try again later' }) - this.setState({ notified: true }) - } - } - } - - componentWillMount() { - const { applicationId } = this.props.match.params - const { fetchApplicationById } = this.props - - fetchApplicationById({id: applicationId}) - } - componentDidMount() { const { userInfoStatus, history } = this.props const { applicationId } = this.props.match.params + + this.props.fetchAllKubernetes(this.props.match.params) + this.props.fetchApplicationById(this.props.match.params) + const userInfo: UserInfo = isAPISucceeded(userInfoStatus) && userInfoStatus.result if (userInfo) { - const canEdit: boolean = userInfo.roles.some((userRole: UserRole) => { + const canEdit: boolean = userInfo.applicationRoles.some((userRole: UserApplicationRole) => { return String(userRole.applicationId) === applicationId && - (userRole.role === role.editor || userRole.role === role.owner) + (userRole.role === applicationRole.editor || userRole.role === applicationRole.admin) }) if (!canEdit) { history.goBack() @@ -74,9 +51,30 @@ class SaveService extends React.Component { } } + static getDerivedStateFromProps(nextProps: ServiceProps, prevState: ServiceState){ + const { saveServiceDeploymentStatus } = nextProps + const { push } = nextProps.history + const { projectId, applicationId } = nextProps.match.params + const { submitting, notified } = prevState + + // Close modal when API successfully finished + if (submitting && !notified) { + const succeeded: boolean = isAPISucceeded(saveServiceDeploymentStatus) && saveServiceDeploymentStatus.result + const failed: boolean = (isAPISucceeded(saveServiceDeploymentStatus) && !saveServiceDeploymentStatus.result) || isAPIFailed(saveServiceDeploymentStatus) + if (succeeded) { + nextProps.addNotification({ color: 'success', message: 'Successfully saved service' }) + push(`/projects/${projectId}/applications/${applicationId}`) + return { notified: true } + } else if (failed) { + nextProps.addNotification({ color: 'error', message: 'Something went wrong with saving service. Try again later' }) + return { notified: true } + } + } + } + render() { - const { application } = this.props - const targetStatus = { application } + const { kuberneteses, application } = this.props + const targetStatus = { kuberneteses, application } return( { } renderForm(result) { - const { mode } = this.props + const { method } = this.props + const kubernetesMode = result.kuberneteses.length > 0 return (

    - {mode === 'edit' ? 'Edit' : 'Add'} Service + {method === 'patch' ? 'Edit' : 'Add'} Service

    - { mode === 'edit' ? : null } - +
    ) @@ -109,7 +104,7 @@ class SaveService extends React.Component { type ServiceProps = StateProps & DispatchProps - & RouteComponentProps<{applicationId: string, serviceId?: string}> + & RouteComponentProps<{projectId: number, applicationId: string, serviceId?: string}> & CustomProps interface ServiceState { @@ -118,21 +113,23 @@ interface ServiceState { } interface StateProps { - service: APIRequest + kuberneteses: APIRequest application: APIRequest - saveServiceStatus: APIRequest + service: APIRequest + saveServiceDeploymentStatus: APIRequest userInfoStatus: APIRequest } interface CustomProps { - mode: string + method: string } const mapStateToProps = (state: any, extraProps: CustomProps) => ( { - application: state.fetchApplicationByIdReducer.applicationById, - service: state.fetchServiceByIdReducer.serviceById, - saveServiceStatus: state.saveServiceReducer.saveService, + kuberneteses: state.fetchAllKubernetesReducer.fetchAllKubernetes, + application: state.fetchApplicationByIdReducer.fetchApplicationById, + service: state.fetchServiceByIdReducer.fetchServiceById, + saveServiceDeploymentStatus: state.saveServiceDeploymentReducer.saveServiceDeployment, userInfoStatus: state.userInfoReducer.userInfo, ...state.form, ...extraProps @@ -140,21 +137,23 @@ const mapStateToProps = (state: any, extraProps: CustomProps) => ( ) export interface DispatchProps { - fetchApplicationById: (params) => Promise - saveService: (params) => Promise + fetchAllKubernetes: (params: FetchKubernetesByIdParam) => Promise + fetchApplicationById: (params: FetchApplicationByIdParam) => Promise + saveServiceDeployment: (params: ServiceDeploymentParam) => Promise addNotification: (params) => Promise } const mapDispatchToProps = (dispatch): DispatchProps => { return { - fetchApplicationById: (params) => fetchApplicationByIdDispatcher(dispatch, params), - saveService: (params) => saveServiceDispatcher(dispatch, params), + fetchAllKubernetes: (params: FetchKubernetesByIdParam) => fetchAllKubernetesDispatcher(dispatch, params), + fetchApplicationById: (params: FetchApplicationByIdParam) => fetchApplicationByIdDispatcher(dispatch, params), + saveServiceDeployment: (params: ServiceDeploymentParam) => saveServiceDeploymentDispatcher(dispatch, params), addNotification: (params) => dispatch(addNotification(params)) } } export default withRouter( - connect & CustomProps>( + connect & CustomProps>( mapStateToProps, mapDispatchToProps )(SaveService) ) From 822beeb487c03d62da2382249f2186fed2c5b05e Mon Sep 17 00:00:00 2001 From: Hattori Keigo Date: Mon, 8 Apr 2019 11:45:27 +0900 Subject: [PATCH 23/92] Renew `Services` --- .../App/Services/ServicesDeleteForm.tsx | 197 +++++++++++------- .../App/Services/ServicesStatusTable.tsx | 154 -------------- .../src/components/App/Services/index.tsx | 188 ++++++++--------- 3 files changed, 219 insertions(+), 320 deletions(-) delete mode 100644 frontend/src/components/App/Services/ServicesStatusTable.tsx diff --git a/frontend/src/components/App/Services/ServicesDeleteForm.tsx b/frontend/src/components/App/Services/ServicesDeleteForm.tsx index ac9c6f3..8396fd7 100644 --- a/frontend/src/components/App/Services/ServicesDeleteForm.tsx +++ b/frontend/src/components/App/Services/ServicesDeleteForm.tsx @@ -1,46 +1,124 @@ import * as React from 'react' import { connect } from 'react-redux' -import { Button } from 'reactstrap' -import { reduxForm, InjectedFormProps } from 'redux-form' +import { Button, Table, Row } from 'reactstrap' +import { Link } from 'react-router-dom' +import { Formik, Form } from 'formik' import { Service } from '@src/apis' -import ServicesStatusTable from './ServicesStatusTable' import { ControlMode } from './index' +import { Checkbox } from '@common/Field' -class ServicesDeleteForm extends React.Component { + +class ServicesDeleteForm extends React.Component { constructor(props, context) { super(props, context) this.handleDiscardChanges = this.handleDiscardChanges.bind(this) - } - - componentWillReceiveProps(nextProps: ServicesDeleteFormProps) { - const { mode, pristine, changeMode } = nextProps - - if (mode === ControlMode.VIEW_SERVICES_STATUS && !pristine) { - changeMode(ControlMode.SELECT_TARGETS) - } else if (mode === ControlMode.SELECT_TARGETS && pristine) { - changeMode(ControlMode.VIEW_SERVICES_STATUS) - } + this.handleModeChanges = this.handleModeChanges.bind(this) } render() { const { - onSubmit, - handleSubmit, + onSubmit } = this.props return ( -
    -
    - {this.renderDiscardButton()} -
    - -
    - {this.renderSubmitButtons()} - + + {({ isInitialValid, isSubmitting }) => ( +
    + {this.handleModeChanges(isInitialValid)} +
    + {this.renderDiscardButton()} +
    + + {this.renderTableHead()} + {this.renderTableBody()} +
    +
    + {this.renderSubmitButtons(isInitialValid, isSubmitting)} +
    + )} +
    + ) + } + + /** + * Render head row of the table + */ + renderTableHead = () => { + return ( + + + Name + Service Level + Version + Model ID + Description + Host:Port + Registered Date + + + ) + } + + /** + * Render body of the table + * + * Render Service names + * Each Service is rendered with a deploy check box on viewing/deleting mode + * @param services Services to be shown (Currently show all, but should be filtered) + */ + renderTableBody = () => { + const { projectId, applicationId, canEdit, services } = this.props + + // Button to delete Service (for deleting k8s services) + const deleteCheckButton = (serviceName: string, serviceId: string) => { + return ( + + { canEdit ? + + : null } + + {serviceName} + + + ) + } + + return ( + + {services.map( + (service: Service, index: number) => ( + + + {deleteCheckButton(service.name, service.serviceId)} + + + {service.serviceLevel} + + + {service.version} + + + {service.modelId} + + + {service.description} + + + {service.insecureHost}:{service.insecurePort} + + + {service.registerDate.toUTCString()} + + + ) + )} + ) } @@ -66,12 +144,8 @@ class ServicesDeleteForm extends React.Component { * Show delete button if selected targets exist * Show save button if editing deploy status */ - renderSubmitButtons(): JSX.Element { - const { - mode, - submitting, - pristine - } = this.props + renderSubmitButtons(isInitialValid, isSubmitting): JSX.Element { + const { mode } = this.props const showSubmitButton: boolean = mode !== ControlMode.VIEW_SERVICES_STATUS @@ -89,7 +163,7 @@ class ServicesDeleteForm extends React.Component {
    ) - return submitting ? submittingLoader : buttons(paramsMap[mode]) - } - - renderSubmitButtonElements() { - const { - submitting, - pristine - } = this.props - - const paramsMap = { - [ControlMode.SELECT_TARGETS]: { color: 'danger', icon: 'trash', text: 'Delete Services' }, - } - - return ( -
    - -
    - ) + return isSubmitting ? submittingLoader : buttons(paramsMap[mode]) } // Handle event methods handleDiscardChanges(event): void { - const { changeMode, reset } = this.props - reset() + const { changeMode } = this.props changeMode(ControlMode.VIEW_SERVICES_STATUS) } + + handleModeChanges(isInitialValid): void { + const { mode, changeMode } = this.props + if (mode === ControlMode.VIEW_SERVICES_STATUS && !isInitialValid) { + changeMode(ControlMode.SELECT_TARGETS) + } else if (mode === ControlMode.SELECT_TARGETS && isInitialValid) { + changeMode(ControlMode.VIEW_SERVICES_STATUS) + } + } } interface ServicesDeleteFormCustomProps { - applicationType: string + projectId applicationId mode: ControlMode services: Service[] canEdit: boolean - onSubmit: (e) => Promise + onSubmit: (e) => void changeMode: (mode: ControlMode) => void } +interface ServiceDeleteFormCustomState {} + interface StateProps { initialValues: { status @@ -162,7 +222,7 @@ const mapStateToProps = (state: any, extraProps: ServicesDeleteFormCustomProps) // Map of service ID to delete flag const initialDeleteStatus: { [x: string]: boolean } = extraProps.services - .map((service) => ({[service.id]: false})) + .map((service) => ({[service.serviceId]: false})) .reduce((l, r) => Object.assign(l, r), {}) return { @@ -179,13 +239,6 @@ const mapDispatchToProps = (dispatch): {} => { return { } } -type ServicesDeleteFormProps - = StateProps & ServicesDeleteFormCustomProps & InjectedFormProps<{}, ServicesDeleteFormCustomProps> +type ServicesDeleteFormProps = StateProps & ServicesDeleteFormCustomProps -export default connect(mapStateToProps, mapDispatchToProps)( - reduxForm<{}, ServicesDeleteFormCustomProps>( - { - form: 'deployStatusForm' - } - )(ServicesDeleteForm) -) +export default connect(mapStateToProps, mapDispatchToProps)(ServicesDeleteForm) diff --git a/frontend/src/components/App/Services/ServicesStatusTable.tsx b/frontend/src/components/App/Services/ServicesStatusTable.tsx deleted file mode 100644 index a08e230..0000000 --- a/frontend/src/components/App/Services/ServicesStatusTable.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import * as React from 'react' -import { connect } from 'react-redux' -import { CustomInput, Table, Row } from 'reactstrap' -import { Field, InjectedFormProps } from 'redux-form' -import { Link } from 'react-router-dom' - -import { Service } from '@src/apis' -import { ControlMode } from './index' - -/** - * Table for showing services status - */ -class ServicesStatusTable extends React.Component { - constructor(props, context) { - super(props, context) - - this.state = { - tooltipOpen: {} - } - } - - render() { - const { services } = this.props - - return ( - - {this.renderTableHead()} - {this.renderTableBody(services)} -
    - ) - } - - toggleTooltip(tag) { - return () => { - const nextTooltipOpen = { - ...this.state.tooltipOpen, - [tag]: !this.state.tooltipOpen[tag] - } - - this.setState({ - tooltipOpen: nextTooltipOpen - }) - } - } - - /** - * Render head row of the table - */ - renderTableHead = () => { - return ( - - - NameService LevelDescriptionHost - - - ) - } - - /** - * Render body of the table - * - * Render Service names - * Each Service is rendered with a deploy check box on viewing/deleting mode - * @param services Services to be shown (Currently show all, but should be filtered) - */ - renderTableBody = (services) => { - const { applicationType, applicationId, canEdit } = this.props - - // Button to delete Service (for deleting k8s services) - const deleteCheckButton = (serviceName: string, serviceId: string) => { - return ( - - { applicationType === 'kubernetes' && canEdit ? - - : null } - - {serviceName} - - - ) - } - - return ( - - {services.map( - (service, index: number) => ( - - - {deleteCheckButton(service.name, service.id)} - - - {service.serviceLevel} - - - {service.description} - - - {service.host} - - - ) - )} - - ) - } - -} - -const CustomCheckBox = (props) => { - const { input, id, label } = props - - return ( - - ) -} - -interface ServicesStatusFormCustomProps { - applicationType: string - applicationId - services: Service[], - mode: ControlMode, - canEdit: boolean -} - -export interface DispatchProps { - dispatchChange -} - -const mapDispatchToProps = (dispatch): DispatchProps => { - return { - dispatchChange: (field, value, changeMethod) => dispatch(changeMethod(field, value)) - } -} - -const mapStateToProps = (state: any, extraProps: ServicesStatusFormCustomProps) => { - return {} -} - -type ServicesStatusProps = DispatchProps & ServicesStatusFormCustomProps & InjectedFormProps<{}, ServicesStatusFormCustomProps> - -export default connect(mapStateToProps, mapDispatchToProps)(ServicesStatusTable) diff --git a/frontend/src/components/App/Services/index.tsx b/frontend/src/components/App/Services/index.tsx index c5e3699..b29f00b 100644 --- a/frontend/src/components/App/Services/index.tsx +++ b/frontend/src/components/App/Services/index.tsx @@ -4,40 +4,53 @@ import { withRouter, RouteComponentProps } from 'react-router' import { Button, Modal, ModalBody, ModalHeader, Row, Col } from 'reactstrap' import { APIRequest, isAPISucceeded, isAPIFailed } from '@src/apis/Core' -import { Service, SynKubernetesStatusParam, Application, UserInfo, UserRole } from '@src/apis' +import { + Kubernetes, Service, SyncKubernetesParam, Application, UserInfo, + FetchApplicationByIdParam, FetchServiceParam, IdParam, FetchKubernetesByIdParam +} from '@src/apis' import { addNotification, + fetchAllKubernetesDispatcher, fetchApplicationByIdDispatcher, fetchAllServicesDispatcher, - deleteKubernetesServicesDispatcher, - syncKubernetesStatusDispatcher + deleteServicesDispatcher, + syncKubernetesDispatcher } from '@src/actions' import { APIRequestResultsRenderer } from '@common/APIRequestResultsRenderer' import ServicesDeleteForm from './ServicesDeleteForm' -import { role } from '../Admin/constants' + export enum ControlMode { VIEW_SERVICES_STATUS, SELECT_TARGETS, } -type ServicesStatusProps = DispatchProps & StateProps & RouteComponentProps<{applicationId: string}> +type ServicesStatusProps = DispatchProps & StateProps & RouteComponentProps<{projectId: number, applicationId: string}> -class Services extends React.Component { +interface ServicesStatusState { + controlMode: ControlMode + isDeleteServicesModalOpen: boolean + selectedData: IdParam[] + submitted: boolean + syncSubmitted: boolean + syncNotified: boolean +} + +class Services extends React.Component { constructor(props, context) { super(props, context) this.state = { controlMode: ControlMode.VIEW_SERVICES_STATUS, isDeleteServicesModalOpen: false, - selectedData: { services: [] }, + selectedData: [], submitted: false, syncSubmitted: false, syncNotified: false } this.onSubmitDelete = this.onSubmitDelete.bind(this) - this.deleteKubernetesServices = this.deleteKubernetesServices.bind(this) + this.deleteServices = this.deleteServices.bind(this) this.toggleDeleteServicesModal = this.toggleDeleteServicesModal.bind(this) this.syncServices = this.syncServices.bind(this) this.renderServices = this.renderServices.bind(this) @@ -45,19 +58,17 @@ class Services extends React.Component { this.complete = this.complete.bind(this) } - componentWillMount() { - const { applicationId } = this.props.match.params - - this.props.fetchApplicationById(applicationId) - this.props.fetchAllServices(applicationId) + componentDidMount() { + this.props.fetchApplicationById(this.props.match.params) + this.props.fetchAllServices(this.props.match.params) } - componentWillReceiveProps(nextProps: ServicesStatusProps) { + static getDerivedStateFromProps(nextProps: ServicesStatusProps, prevState: ServicesStatusState){ const { - deleteKubernetesServicesStatus, - syncKubernetesServicesStatusStatus + deleteServicesStatus, + syncKubernetesStatus } = nextProps - const { controlMode, submitted } = this.state + const { controlMode, submitted, syncSubmitted, syncNotified } = prevState const checkAllApiResultStatus = (result: APIRequest) => @@ -65,35 +76,34 @@ class Services extends React.Component { result.result.reduce((p, c) => (p && c)) if (submitted && controlMode === ControlMode.SELECT_TARGETS) { - if (checkAllApiResultStatus(deleteKubernetesServicesStatus)) { - this.complete({ color: 'success', message: 'Successfully changed deletion' }) + if (checkAllApiResultStatus(deleteServicesStatus)) { + nextProps.addNotification({ color: 'success', message: 'Successfully changed deletion' }) } else { - this.complete({ color: 'error', message: 'Something went wrong, try again later' }) + nextProps.addNotification({ color: 'error', message: 'Something went wrong, try again later' }) + } + nextProps.fetchAllServices(nextProps.match.params) + return { + controlMode: ControlMode.VIEW_SERVICES_STATUS, + submitted: false, + selectedData: [] } } - this.checkAndNotifyAPIResult( - syncKubernetesServicesStatusStatus, - 'syncSubmitted', 'syncNotified', - 'Successfully synced application' - ) - } - - checkAndNotifyAPIResult(status, submitted: string, notified: string, notificationText) { - const submittedFlag: boolean = this.state[submitted] - const notifiedFlag: boolean = this.state[notified] - - if (submittedFlag && !notifiedFlag) { - const succeeded: boolean = isAPISucceeded(status) && status.result - const failed: boolean = (isAPISucceeded(status) && !status.result) || - isAPIFailed(status) + if (syncSubmitted && !syncNotified) { + const succeeded: boolean = isAPISucceeded(syncKubernetesStatus) && syncKubernetesStatus.result + const failed: boolean = (isAPISucceeded(syncKubernetesStatus) && !syncKubernetesStatus.result) || isAPIFailed(syncKubernetesStatus) if (succeeded) { - this.setState({[submitted]: false, [notified]: true}) - this.complete({ color: 'success', message: notificationText }) + nextProps.addNotification({ color: 'success', message: 'Successfully synced application' }) } else if (failed) { - this.setState({[submitted]: false, [notified]: true}) - this.complete({ color: 'error', message: 'Something went wrong. Try again later' }) + nextProps.addNotification({ color: 'error', message: 'Something went wrong. Try again later' }) + } + return { + controlMode: ControlMode.VIEW_SERVICES_STATUS, + submitted: false, + syncSubmitted: false, + syncNotified: true, + selectedData: [] } } } @@ -106,13 +116,11 @@ class Services extends React.Component { if (isAPISucceeded(settings) && settings.result.auth) { statuses.userInfoStatus = userInfoStatus } - if ( this.props.match.params.applicationId === 'add' ) { - return null - } return ( ) @@ -133,8 +141,9 @@ class Services extends React.Component { changeMode } = this - const { kubernetesId, name } = fetchedResults.application - const { applicationId } = this.props.match.params + const kubernetesMode = fetchedResults.kuberneteses.length > 0 + const applicationName = fetchedResults.application.name + const { projectId, applicationId } = this.props.match.params const services: Service[] = fetchedResults.services const onSubmitMap = { @@ -145,7 +154,7 @@ class Services extends React.Component { return ( this.renderContent( { changeMode={changeMode} canEdit={canEdit} />, - name, - kubernetesId, + applicationName, + kubernetesMode, canEdit ) ) } - renderContent = (content: JSX.Element, applicationName, kubernetesId, canEdit: boolean): JSX.Element => { + renderContent = (content: JSX.Element, applicationName, kubernetesMode, canEdit: boolean): JSX.Element => { return (
    - {this.renderTitle(applicationName, kubernetesId, canEdit)} + {this.renderTitle(applicationName, kubernetesMode, canEdit)}

    Services @@ -179,7 +188,7 @@ class Services extends React.Component { ) } - renderTitle = (applicationName, kubernetesId, canEdit: boolean): JSX.Element => { + renderTitle = (applicationName, kubernetesMode, canEdit: boolean): JSX.Element => { return ( @@ -189,23 +198,23 @@ class Services extends React.Component {

    - {kubernetesId && canEdit ? this.renderKubernetesControlButtons(kubernetesId) : null} + {kubernetesMode && canEdit ? this.renderKubernetesControlButtons() : null} ) } - renderKubernetesControlButtons(kubernetesId) { + renderKubernetesControlButtons() { const { push } = this.props.history const { syncServices } = this - const { applicationId } = this.props.match.params + const { projectId, applicationId } = this.props.match.params return ( {' '} - @@ -95,12 +101,11 @@ class ProjectDeploymentFormImpl extends React.Component { } export interface FormCustomProps { - submitting onCancel onSubmit } -type AddProjectFormProps = FormCustomProps +type SaveProjectFormProps = FormCustomProps export const ProjectDeloymentForm = connect( diff --git a/frontend/src/components/App/SaveProject/index.tsx b/frontend/src/components/App/SaveProject/index.tsx index d6567a0..769f186 100644 --- a/frontend/src/components/App/SaveProject/index.tsx +++ b/frontend/src/components/App/SaveProject/index.tsx @@ -13,7 +13,7 @@ import { FormCustomProps, ProjectDeloymentForm } from './ProjectDeploymentForm' * Page for adding project * */ -class AddProject extends React.Component { +class SaveProject extends React.Component { constructor(props, context) { super(props, context) @@ -25,7 +25,7 @@ class AddProject extends React.Component { } } - static getDerivedStateFromProps(nextProps: AddProjectProps, prevState: AddProjectState){ + static getDerivedStateFromProps(nextProps: SaveProjectProps, prevState: SaveProjectState){ const { saveProjectStatus } = nextProps const { submitting, notified } = prevState const { push } = nextProps.history @@ -33,8 +33,7 @@ class AddProject extends React.Component { // Close modal when API successfully finished if (submitting && !notified) { const succeeded: boolean = isAPISucceeded(saveProjectStatus) && saveProjectStatus.result - const failed: boolean = (isAPISucceeded(saveProjectStatus) && !saveProjectStatus.result) || - isAPIFailed(saveProjectStatus) + const failed: boolean = (isAPISucceeded(saveProjectStatus) && !saveProjectStatus.result) || isAPIFailed(saveProjectStatus) if (succeeded) { nextProps.addNotification({ color: 'success', message: 'Successfully added project' }) push('/projects/') @@ -44,6 +43,7 @@ class AddProject extends React.Component { return { notified: true } } } + return null } /** @@ -62,6 +62,7 @@ class AddProject extends React.Component { return saveProject( { + method: 'post', ...parameters, } ) @@ -72,7 +73,6 @@ class AddProject extends React.Component { @@ -82,8 +82,8 @@ class AddProject extends React.Component { } } -type AddProjectProps = StateProps & DispatchProps & RouterProps -interface AddProjectState { +type SaveProjectProps = StateProps & DispatchProps & RouterProps +interface SaveProjectState { submitting: boolean notified: boolean } @@ -114,5 +114,5 @@ const mapDispatchToProps = (dispatch): DispatchProps => { export default withRouter( connect & FormCustomProps>( mapStateToProps, mapDispatchToProps - )(AddProject) + )(SaveProject) ) From f59c7d7948460affb4087bcfb20d3904e59efce5 Mon Sep 17 00:00:00 2001 From: Hattori Keigo Date: Wed, 10 Apr 2019 10:09:16 +0900 Subject: [PATCH 32/92] Fix bugs of `DataServer` --- .../App/DataServer/DataServerForm.tsx | 169 ++++++++++-------- .../src/components/App/DataServer/index.tsx | 19 +- .../components/misc/NavigationBar/index.tsx | 9 +- 3 files changed, 113 insertions(+), 84 deletions(-) diff --git a/frontend/src/components/App/DataServer/DataServerForm.tsx b/frontend/src/components/App/DataServer/DataServerForm.tsx index c42c69b..c21c6fc 100644 --- a/frontend/src/components/App/DataServer/DataServerForm.tsx +++ b/frontend/src/components/App/DataServer/DataServerForm.tsx @@ -4,7 +4,9 @@ import { Card, CardBody, Button, CardTitle, UncontrolledTooltip } from 'reactstr import { dataServerMode } from '@components/Common/Enum' import * as Yup from "yup"; -import { ErrorMessage, Field, Form, Formik } from "formik"; +import { Field, Form, Formik } from "formik"; + +import { FormikInput } from '@common/Field' const DataServerSchema = Yup.object().shape({ @@ -42,23 +44,28 @@ class DataServerFormImpl extends React.Component { - return ( - - ) + return { + value: modeName, + label: modeName + } }) return ( - - {modes} - + ) } @@ -69,15 +76,54 @@ class DataServerFormImpl extends React.Component - - - - - - + + + + + {}} + required /> + ) @@ -85,9 +131,27 @@ class DataServerFormImpl extends React.Component - - - + + + ) @@ -104,57 +168,16 @@ class DataServerFormImpl extends React.Component + onSubmit={onSubmit} + onReset={onCancel}> {({ errors, touched, isSubmitting }) => (
    {this.renderModes()} - {errors.dataServerMode && touched.dataServerMode ? ( -
    {errors.dataServerMode}
    - ) : null} -
    - {fields} - {errors.cephAccessKey && touched.cephAccessKey ? ( -
    {errors.cephAccessKey}
    - ) : null} - - {errors.cephSecretKey && touched.cephSecretKey ? ( -
    {errors.cephSecretKey}
    - ) : null} - - {errors.cephHost && touched.cephHost ? ( -
    {errors.cephHost}
    - ) : null} - - {errors.cephPort && touched.cephPort ? ( -
    {errors.cephPort}
    - ) : null} - - {errors.cephIsSecure && touched.cephIsSecure ? ( -
    {errors.cephIsSecure}
    - ) : null} - - {errors.cephBucketName && touched.cephBucketName ? ( -
    {errors.cephBucketName}
    - ) : null} - - {errors.awsAccessKey && touched.awsAccessKey ? ( -
    {errors.awsAccessKey}
    - ) : null} - - {errors.awsSecretKey && touched.awsSecretKey ? ( -
    {errors.awsSecretKey}
    - ) : null} - - {errors.awsBucketName && touched.awsBucketName ? ( -
    {errors.awsBucketName}
    - ) : null} - - @@ -184,15 +207,15 @@ interface DataServerState { const defaultInitialValues = { dataServerMode: dataServerMode.local.toString(), - cephAccessKey: null, - cephSecretKey: null, - cephHost: null, - cephPort: null, - cephIsSecure: null, - cephBucketName: null, - awsAccessKey: null, - awsSecretKey: null, - awsBucketName: null, + cephAccessKey: '', + cephSecretKey: '', + cephHost: '', + cephPort: 7300, + cephIsSecure: false, + cephBucketName: '', + awsAccessKey: '', + awsSecretKey: '', + awsBucketName: '', } export interface CustomProps { diff --git a/frontend/src/components/App/DataServer/index.tsx b/frontend/src/components/App/DataServer/index.tsx index f921415..c216438 100644 --- a/frontend/src/components/App/DataServer/index.tsx +++ b/frontend/src/components/App/DataServer/index.tsx @@ -4,7 +4,7 @@ import { RouterProps } from 'react-router' import { withRouter, RouteComponentProps } from 'react-router-dom' import { APIRequest, isAPISucceeded, isAPIFailed } from '@src/apis/Core' -import { DataServer, DataServerParam, FetchDataServerByIdParam } from '@src/apis' +import { DataServerParam, FetchDataServerByIdParam } from '@src/apis' import { saveDataServerDispatcher, fetchDataServerDispatcher, addNotification } from '@src/actions' import { DataServerForm } from './DataServerForm' import { APIRequestResultsRenderer } from '@common/APIRequestResultsRenderer' @@ -21,7 +21,7 @@ class DataServerComponent extends React.Component(saveDataServerStatus) && saveDataServerStatus.result - const failed: boolean = (isAPISucceeded(saveDataServerStatus) && !saveDataServerStatus.result) || - isAPIFailed(saveDataServerStatus) + const failed: boolean = (isAPISucceeded(saveDataServerStatus) && !saveDataServerStatus.result) || isAPIFailed(saveDataServerStatus) if (succeeded) { nextProps.fetchDataServer({ projectId: nextProps.match.params.projectId @@ -86,6 +85,7 @@ class DataServerComponent extends React.Component ) @@ -108,13 +108,12 @@ class DataServerComponent extends React.Component ) } diff --git a/frontend/src/components/misc/NavigationBar/index.tsx b/frontend/src/components/misc/NavigationBar/index.tsx index 9424892..487f5aa 100644 --- a/frontend/src/components/misc/NavigationBar/index.tsx +++ b/frontend/src/components/misc/NavigationBar/index.tsx @@ -52,7 +52,8 @@ class NavigationBar extends React.Component { let projectAdmin: React.ReactNode if (projectId) { const adminlink = `/projects/${projectId}/admin` - const kubelink = `/projects/${projectId}/kubernetes/admin` + const kubelink = `/projects/${projectId}/kubernetes` + const dataserverlink = `/projects/${projectId}/data_servers` projectAdmin = ( @@ -67,6 +68,12 @@ class NavigationBar extends React.Component { Kubernetes + + + + Data Servers + + ) } From af5a137e3609d545c82d4c3079abd2dfd13c2d31 Mon Sep 17 00:00:00 2001 From: Hattori Keigo Date: Wed, 10 Apr 2019 10:09:51 +0900 Subject: [PATCH 33/92] Fix bugs of `Kubernetes` --- .../App/Kubernetes/Host/HostForm.tsx | 75 +++++++++++-------- .../components/App/Kubernetes/Host/index.tsx | 1 + .../components/App/Kubernetes/Hosts/index.tsx | 5 +- 3 files changed, 48 insertions(+), 33 deletions(-) diff --git a/frontend/src/components/App/Kubernetes/Host/HostForm.tsx b/frontend/src/components/App/Kubernetes/Host/HostForm.tsx index 1fe73f0..93703bd 100644 --- a/frontend/src/components/App/Kubernetes/Host/HostForm.tsx +++ b/frontend/src/components/App/Kubernetes/Host/HostForm.tsx @@ -3,7 +3,9 @@ import { connect } from 'react-redux' import { Card, CardBody, Button, CardTitle, UncontrolledTooltip } from 'reactstrap' import * as Yup from "yup"; -import { ErrorMessage, Field, Form, Formik } from "formik"; +import { Field, Form, Formik } from "formik"; + +import { FormikInput, FileUpload } from '@common/Field' const AddKubernetesSchema = Yup.object().shape({ @@ -45,48 +47,59 @@ class HostFormImpl extends React.Component { ...this.props.initialValues, configPath: null, } + const isPost = (method === 'post') return (

    - {method === 'post' ? 'Add' : 'Edit'} Kubernetes Host + {isPost ? 'Add' : 'Edit'} Kubernetes Host

    {({ errors, touched, setFieldValue, isSubmitting }) => ( - - {errors.displayName && touched.displayName ? ( -
    {errors.displayName}
    - ) : null} - - { - setFieldValue("configPath", event.currentTarget.files[0]); - }} /> - {errors.configPath && touched.configPath ? ( -
    {errors.configPath}
    - ) : null} - - - {errors.description && touched.description ? ( -
    {errors.description}
    - ) : null} - - - {errors.exposedHost && touched.exposedHost ? ( -
    {errors.exposedHost}
    - ) : null} - - - {errors.exposedPort && touched.exposedPort ? ( -
    {errors.exposedPort}
    - ) : null} - + + + + +
    @@ -94,7 +107,7 @@ class HostFormImpl extends React.Component { {' '} - ) - default: - return null + if (!canEdit) { + return null + } + if (isValid) { + return ( + + ) + } else { + return null } } @@ -124,30 +124,22 @@ class ModelsDeleteForm extends React.Component ( + const buttons = (
    ) @@ -159,34 +151,17 @@ class ModelsDeleteForm extends React.Component ) - return isSubmitting ? submittingLoader : buttons(paramsMap[mode]) - } - - // Handle event methods - - handleDiscardChanges(event): void { - const { changeMode } = this.props - changeMode(ControlMode.VIEW_SERVICES_STATUS) - } - - handleModeChanges(isInitialValid): void { - const { mode, changeMode } = this.props - if (mode === ControlMode.VIEW_SERVICES_STATUS && !isInitialValid) { - changeMode(ControlMode.SELECT_TARGETS) - } else if (mode === ControlMode.SELECT_TARGETS && isInitialValid) { - changeMode(ControlMode.VIEW_SERVICES_STATUS) - } + return isSubmitting ? submittingLoader : buttons } } interface ModelsDeleteFormCustomProps { projectId applicationId - mode: ControlMode models: Model[] canEdit: boolean - onSubmit: (e) => void - changeMode: (mode: ControlMode) => void + onSubmit + onCancel } interface ModelDeleteFormCustomState {} @@ -200,16 +175,11 @@ interface StateProps { const mapStateToProps = (state: any, extraProps: ModelsDeleteFormCustomProps) => { // Map of model ID to delete flag - const initialDeleteStatus: { [x: string]: boolean } = - extraProps.models - .map((model) => ({[model.modelId]: false})) - .reduce((l, r) => Object.assign(l, r), {}) - return { ...state.form, initialValues: { delete: { - models: initialDeleteStatus + models: [] } } } diff --git a/frontend/src/components/App/Models/index.tsx b/frontend/src/components/App/Models/index.tsx index 197ec14..785800e 100644 --- a/frontend/src/components/App/Models/index.tsx +++ b/frontend/src/components/App/Models/index.tsx @@ -5,35 +5,29 @@ import { Button, Modal, ModalBody, ModalHeader, Row, Col } from 'reactstrap' import { APIRequest, isAPISucceeded, isAPIFailed } from '@src/apis/Core' import { - Kubernetes, Model, SyncKubernetesParam, Application, UserInfo, + Model, Application, UserInfo, FetchApplicationByIdParam, FetchModelByIdParam, IdParam, FetchKubernetesByIdParam } from '@src/apis' import { addNotification, - fetchAllKubernetesDispatcher, + fetchIsKubernetesModeDispatcher, fetchApplicationByIdDispatcher, fetchAllModelsDispatcher, - deleteModelsDispatcher, - syncKubernetesDispatcher + deleteModelsDispatcher } from '@src/actions' +import { AddModelFileModal } from '@components/App/Model/Modals/AddModelFileModal' import { APIRequestResultsRenderer } from '@common/APIRequestResultsRenderer' import ModelsDeleteForm from './ModelsDeleteForm' -export enum ControlMode { - VIEW_SERVICES_STATUS, - SELECT_TARGETS, -} - type ModelsStatusProps = DispatchProps & StateProps & RouteComponentProps<{projectId: number, applicationId: string}> interface ModelsStatusState { - controlMode: ControlMode isDeleteModelsModalOpen: boolean + isAddModelFileModalOpen: boolean selectedData: IdParam[] submitted: boolean - syncSubmitted: boolean - syncNotified: boolean + notified: boolean } class Models extends React.Component { @@ -41,78 +35,67 @@ class Models extends React.Component { super(props, context) this.state = { - controlMode: ControlMode.VIEW_SERVICES_STATUS, isDeleteModelsModalOpen: false, + isAddModelFileModalOpen: false, selectedData: [], submitted: false, - syncSubmitted: false, - syncNotified: false + notified: false } this.onSubmitDelete = this.onSubmitDelete.bind(this) + this.onCancel = this.onCancel.bind(this) + this.toggleAddModelFileModalOpen = this.toggleAddModelFileModalOpen.bind(this) this.deleteModels = this.deleteModels.bind(this) this.toggleDeleteModelsModal = this.toggleDeleteModelsModal.bind(this) - this.syncModels = this.syncModels.bind(this) this.renderModels = this.renderModels.bind(this) - this.changeMode = this.changeMode.bind(this) this.complete = this.complete.bind(this) } componentDidMount() { + this.props.fetchIsKubernetesMode(this.props.match.params) this.props.fetchApplicationById(this.props.match.params) this.props.fetchAllModels(this.props.match.params) } static getDerivedStateFromProps(nextProps: ModelsStatusProps, prevState: ModelsStatusState){ - const { - deleteModelsStatus, - syncKubernetesStatus - } = nextProps - const { controlMode, submitted, syncSubmitted, syncNotified } = prevState + const { deleteModelsStatus } = nextProps + const { submitted } = prevState - const checkAllApiResultStatus = + const checkAllApiResultSucceeded = (result: APIRequest) => isAPISucceeded(result) && result.result.reduce((p, c) => (p && c)) + const checkAllApiResultFailed = + (result: APIRequest) => + (isAPISucceeded(result) && !result.result.reduce((p, c) => (p && c))) || isAPIFailed(result) - if (submitted && controlMode === ControlMode.SELECT_TARGETS) { - if (checkAllApiResultStatus(deleteModelsStatus)) { + if (submitted) { + if (checkAllApiResultSucceeded(deleteModelsStatus)) { nextProps.addNotification({ color: 'success', message: 'Successfully changed deletion' }) - } else { + nextProps.fetchAllModels(nextProps.match.params) + return { + submitted: false, + notified: true, + selectedData: [] + } + } else if (checkAllApiResultFailed(deleteModelsStatus)) { nextProps.addNotification({ color: 'error', message: 'Something went wrong, try again later' }) - } - nextProps.fetchAllModels(nextProps.match.params) - return { - controlMode: ControlMode.VIEW_SERVICES_STATUS, - submitted: false, - selectedData: [] - } - } - - if (syncSubmitted && !syncNotified) { - const succeeded: boolean = isAPISucceeded(syncKubernetesStatus) && syncKubernetesStatus.result - const failed: boolean = (isAPISucceeded(syncKubernetesStatus) && !syncKubernetesStatus.result) || isAPIFailed(syncKubernetesStatus) - - if (succeeded) { - nextProps.addNotification({ color: 'success', message: 'Successfully synced application' }) - } else if (failed) { - nextProps.addNotification({ color: 'error', message: 'Something went wrong. Try again later' }) - } - return { - controlMode: ControlMode.VIEW_SERVICES_STATUS, - submitted: false, - syncSubmitted: false, - syncNotified: true, - selectedData: [] + return { + submitted: false, + notified: true, + selectedData: [] + } } } + return null } // Render methods render(): JSX.Element { - const { application, models, userInfoStatus, settings } = this.props - const statuses: any = { application, models } + const { projectId, applicationId } = this.props.match.params + const { kubernetesMode, application, models, userInfoStatus, settings } = this.props + const statuses: any = { kubernetesMode, application, models } if (isAPISucceeded(settings) && settings.result.auth) { statuses.userInfoStatus = userInfoStatus } @@ -120,8 +103,8 @@ class Models extends React.Component { ) } @@ -134,22 +117,12 @@ class Models extends React.Component { * @param canEdit Boolean value of user's editor permission */ renderModels(fetchedResults, canEdit) { - const { controlMode } = this.state - const { - onSubmitNothing, - onSubmitDelete, - changeMode - } = this - - const kubernetesMode = fetchedResults.kuberneteses.length > 0 + const { onSubmitDelete, onCancel } = this + + const kubernetesMode = fetchedResults.kubernetesMode const applicationName = fetchedResults.application.name const { projectId, applicationId } = this.props.match.params - const models: Model[] = fetchedResults.models - const onSubmitMap = { - [ControlMode.VIEW_SERVICES_STATUS]: onSubmitNothing, - [ControlMode.SELECT_TARGETS]: onSubmitDelete, - } return ( this.renderContent( @@ -157,9 +130,8 @@ class Models extends React.Component { projectId={projectId} applicationId={applicationId} models={models} - mode={controlMode} - onSubmit={onSubmitMap[controlMode]} - changeMode={changeMode} + onSubmit={onSubmitDelete} + onCancel={onCancel} canEdit={canEdit} />, applicationName, @@ -173,22 +145,37 @@ class Models extends React.Component { return (
    {this.renderTitle(applicationName, kubernetesMode, canEdit)} +

    Models


    {content} - { - this.state.controlMode === ControlMode.SELECT_TARGETS - ? this.renderConfirmDeleteHostModal() - : null - } + {this.renderConfirmDeleteHostModal()}
    ) } renderTitle = (applicationName, kubernetesMode, canEdit: boolean): JSX.Element => { + const button = ( + + + + ) + return ( @@ -197,55 +184,28 @@ class Models extends React.Component { {applicationName} - - {kubernetesMode && canEdit ? this.renderKubernetesControlButtons() : null} - + {canEdit ? button : null} ) } - renderKubernetesControlButtons() { - const { push } = this.props.history - const { syncModels } = this - const { projectId, applicationId } = this.props.match.params - - return ( - - - {` `} - - - ) - } - renderConfirmDeleteHostModal(): JSX.Element { const { isDeleteModelsModalOpen } = this.state - const cancel = () => { + const executeDeletion = (event) => { + if (this.state.selectedData.length > 0) { + this.deleteModels(this.state.selectedData) + } this.toggleDeleteModelsModal() } - - const executeDeletion = (event) => { - this.deleteModels(this.state.selectedData) + const cancelDeletion = (event) => { this.toggleDeleteModelsModal() + return Promise.resolve() } return ( - - Delete Models + + Delete Models Are you sure to delete? @@ -263,7 +223,7 @@ class Models extends React.Component { color='secondary' size='lg' className='rounded-0 flex-1' - onClick={cancel} + onClick={cancelDeletion} > Cancel @@ -273,11 +233,6 @@ class Models extends React.Component { ) } - syncModels(): void { - this.setState({ syncSubmitted: true, syncNotified: false }) - this.props.syncKubernetes(this.props.match.params) - } - // Event handing methods toggleDeleteModelsModal(): void { this.setState({ @@ -285,7 +240,11 @@ class Models extends React.Component { }) } - onSubmitNothing(params): void {} + toggleAddModelFileModalOpen(): void { + this.setState({ + isAddModelFileModalOpen: !this.state.isAddModelFileModalOpen + }) + } /** * Handle submit and call API to delete models @@ -296,7 +255,13 @@ class Models extends React.Component { onSubmitDelete(params): void { this.setState({ isDeleteModelsModalOpen: true, - selectedData: params + selectedData: params.delete_models + }) + } + + onCancel(): void { + this.setState({ + selectedData: [] }) } @@ -304,36 +269,21 @@ class Models extends React.Component { const { projectId, applicationId } = this.props.match.params const apiParams = - Object.entries(params) - .filter(([key, value]) => (value)) - .map( - ([key, value]) => ( - { - projectId, - applicationId, - modelId: Number(key) - })) - - this.setState({ submitted: true }) + params.map((id) => ( + { + projectId, + applicationId, + modelId: Number(id) + })) + this.setState({ submitted: true, notified: false }) return this.props.deleteModels(apiParams) } - // Utils - changeMode(mode: ControlMode) { - this.setState({ controlMode: mode }) - } - - /** - * Reload models status - * - * Fetch models through API again - */ complete(param) { this.props.addNotification(param) this.props.fetchAllModels(this.props.match.params) this.setState({ - controlMode: ControlMode.VIEW_SERVICES_STATUS, submitted: false, selectedData: [] }) @@ -341,8 +291,7 @@ class Models extends React.Component { } export interface StateProps { - syncKubernetesStatus: APIRequest - kuberneteses: APIRequest + kubernetesMode: APIRequest application: APIRequest models: APIRequest deleteModelsStatus: APIRequest @@ -352,8 +301,7 @@ export interface StateProps { const mapStateToProps = (state): StateProps => { const props = { - syncKubernetesStatus: state.syncKubernetesReducer.syncKubernetes, - kuberneteses: state.fetchAllKubernetesReducer.fetchAllKubernetes, + kubernetesMode: state.fetchIsKubernetesModeReducer.fetchIsKubernetesMode, application: state.fetchApplicationByIdReducer.fetchApplicationById, models: state.fetchAllModelsReducer.fetchAllModels, deleteModelsStatus: state.deleteModelsReducer.deleteModels, @@ -365,8 +313,7 @@ const mapStateToProps = (state): StateProps => { export interface DispatchProps { addNotification - syncKubernetes: (params: SyncKubernetesParam) => Promise - fetchAllKubernetes: (params: FetchKubernetesByIdParam) => Promise + fetchIsKubernetesMode: (params: FetchKubernetesByIdParam) => Promise fetchApplicationById: (params: FetchApplicationByIdParam) => Promise fetchAllModels: (params: FetchModelByIdParam) => Promise deleteModels: (params: IdParam[]) => Promise @@ -375,8 +322,7 @@ export interface DispatchProps { const mapDispatchToProps = (dispatch): DispatchProps => { return { addNotification: (params) => dispatch(addNotification(params)), - syncKubernetes: (params: SyncKubernetesParam) => syncKubernetesDispatcher(dispatch, params), - fetchAllKubernetes: (params: FetchKubernetesByIdParam) => fetchAllKubernetesDispatcher(dispatch, params), + fetchIsKubernetesMode: (params: FetchKubernetesByIdParam) => fetchIsKubernetesModeDispatcher(dispatch, params), fetchApplicationById: (params: FetchApplicationByIdParam) => fetchApplicationByIdDispatcher(dispatch, params), fetchAllModels: (params: FetchModelByIdParam) => fetchAllModelsDispatcher(dispatch, params), deleteModels: (params: IdParam[]) => deleteModelsDispatcher(dispatch, params), From 4309c45826d29345e85e0a55499f51ce3bb517f5 Mon Sep 17 00:00:00 2001 From: Hattori Keigo Date: Fri, 12 Apr 2019 17:30:12 +0900 Subject: [PATCH 43/92] Minor fix --- frontend/src/apis/index.tsx | 5 +- .../App/Dashboard/DashboardStatusForm.tsx | 2 +- .../src/components/App/Dashboard/index.tsx | 52 +++++++++---------- .../App/DataServer/DataServerForm.tsx | 20 +++---- .../src/components/App/DataServer/index.tsx | 6 +++ .../App/Kubernetes/Host/HostForm.tsx | 6 +-- .../ModelDescription/ModelDescriptionForm.tsx | 3 +- .../App/Models/ModelsDeleteForm.tsx | 4 +- .../components/Common/Field/FormikInput.tsx | 7 ++- .../components/misc/NavigationBar/index.tsx | 9 +++- 10 files changed, 64 insertions(+), 50 deletions(-) diff --git a/frontend/src/apis/index.tsx b/frontend/src/apis/index.tsx index c1f521a..2a32c28 100644 --- a/frontend/src/apis/index.tsx +++ b/frontend/src/apis/index.tsx @@ -149,8 +149,8 @@ export interface SingleServiceParam { serviceLevel: string version: string serviceModelAssignment: number - serviceInsecureHost: string - serviceInsecurePort: number + insecureHost: string + insecurePort: number registerDate?: Date method: string } @@ -512,7 +512,6 @@ export async function fetchServiceById(params: FetchServiceByIdParam): Promise ( { - serviceId: result.service_id, name: result.display_name, registerDate: new Date(result.register_date * 1000), ...convertKeys(result, camelize) diff --git a/frontend/src/components/App/Dashboard/DashboardStatusForm.tsx b/frontend/src/components/App/Dashboard/DashboardStatusForm.tsx index d922ec9..d157395 100644 --- a/frontend/src/components/App/Dashboard/DashboardStatusForm.tsx +++ b/frontend/src/components/App/Dashboard/DashboardStatusForm.tsx @@ -125,7 +125,7 @@ class DashboardStatusForm extends React.Component {canEdit && !isSwitchMode ? checkBox : null} - {modelName} + {`${modelId}: ${modelName}`} ) diff --git a/frontend/src/components/App/Dashboard/index.tsx b/frontend/src/components/App/Dashboard/index.tsx index 5e2b7e0..efd468e 100644 --- a/frontend/src/components/App/Dashboard/index.tsx +++ b/frontend/src/components/App/Dashboard/index.tsx @@ -253,6 +253,21 @@ class Dashboard extends React.Component { + const { push } = this.props.history + const { projectId, applicationId} = this.props.match.params + + const kubeSyncButton = ( + + {` `} + + + ) + const buttons = ( + {kubernetesMode ? kubeSyncButton : null} ) + return ( @@ -279,33 +302,6 @@ class Dashboard extends React.Component - - {` `} - - - ) - } - renderConfirmDeleteModal(): JSX.Element { const { isDeleteModalOpen } = this.state diff --git a/frontend/src/components/App/DataServer/DataServerForm.tsx b/frontend/src/components/App/DataServer/DataServerForm.tsx index c21c6fc..a37f083 100644 --- a/frontend/src/components/App/DataServer/DataServerForm.tsx +++ b/frontend/src/components/App/DataServer/DataServerForm.tsx @@ -63,6 +63,7 @@ class DataServerFormImpl extends React.Component @@ -85,28 +86,28 @@ class DataServerFormImpl extends React.Component {}} required /> @@ -122,7 +124,7 @@ class DataServerFormImpl extends React.Component
    @@ -136,21 +138,21 @@ class DataServerFormImpl extends React.Component @@ -206,7 +208,7 @@ interface DataServerState { } const defaultInitialValues = { - dataServerMode: dataServerMode.local.toString(), + dataServerMode: '', cephAccessKey: '', cephSecretKey: '', cephHost: '', diff --git a/frontend/src/components/App/DataServer/index.tsx b/frontend/src/components/App/DataServer/index.tsx index c216438..336bf97 100644 --- a/frontend/src/components/App/DataServer/index.tsx +++ b/frontend/src/components/App/DataServer/index.tsx @@ -63,6 +63,12 @@ class DataServerComponent extends React.Component(fetchDataServerStatus) && !fetchDataServerStatus.result) || isAPIFailed(fetchDataServerStatus) + if (chk && !notified) { + nextProps.addNotification({ color: 'error', message: 'No data server registered. Please register it first.' }) + return {submitting: false, notified: true} + } + // Handling submitted API results if (submitting && !notified) { const succeeded: boolean = isAPISucceeded(saveDataServerStatus) && saveDataServerStatus.result diff --git a/frontend/src/components/App/Kubernetes/Host/HostForm.tsx b/frontend/src/components/App/Kubernetes/Host/HostForm.tsx index 93703bd..175ebf6 100644 --- a/frontend/src/components/App/Kubernetes/Host/HostForm.tsx +++ b/frontend/src/components/App/Kubernetes/Host/HostForm.tsx @@ -84,14 +84,14 @@ class HostFormImpl extends React.Component { label="Exposed Host" component={FormikInput} className="form-control" - placeholder="Exposed Host" + placeholder="Exposed Host such as Gateway IP, Ingress IP and LoadBalancer IP." required={isPost} /> (
    + {`Model ID: ${this.props.initialValues.modelId}`} - Description + ID + Description Registered Date @@ -76,7 +76,7 @@ class ModelsDeleteForm extends React.Component : null } - {modelName} + {`${modelId}: ${modelName}`} ) diff --git a/frontend/src/components/Common/Field/FormikInput.tsx b/frontend/src/components/Common/Field/FormikInput.tsx index aedee33..1667308 100644 --- a/frontend/src/components/Common/Field/FormikInput.tsx +++ b/frontend/src/components/Common/Field/FormikInput.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { FormGroup, Label, Input, FormFeedback } from 'reactstrap' +import { FormGroup, Label, Input, FormFeedback, FormText } from 'reactstrap' /** @@ -11,6 +11,8 @@ export const FormikInput = ( { options = [] as Array<{label: string, value: string, disabled: boolean}>, required, + placeholder, + groupClassName, field: {...fields}, form: {touched, errors, setFieldValue, ...rest}, ...props @@ -26,7 +28,7 @@ export const FormikInput = ( const requiredClass = required ? 'required' : '' return ( - + {options.length > 0 ? renderOptionElements() : null} + {placeholder} {touched[fields.name] && errors[fields.name] ? {errors[fields.name]} : ''} ) diff --git a/frontend/src/components/misc/NavigationBar/index.tsx b/frontend/src/components/misc/NavigationBar/index.tsx index 487f5aa..bba52ff 100644 --- a/frontend/src/components/misc/NavigationBar/index.tsx +++ b/frontend/src/components/misc/NavigationBar/index.tsx @@ -51,11 +51,18 @@ class NavigationBar extends React.Component { let projectAdmin: React.ReactNode if (projectId) { + const applicationslink = `/projects/${projectId}/applications` const adminlink = `/projects/${projectId}/admin` const kubelink = `/projects/${projectId}/kubernetes` const dataserverlink = `/projects/${projectId}/data_servers` projectAdmin = ( + + + + Applications + + @@ -84,7 +91,7 @@ class NavigationBar extends React.Component {