diff --git a/config/webpack.common.js b/config/webpack.common.js
index ce1e68ab..82b26ccc 100644
--- a/config/webpack.common.js
+++ b/config/webpack.common.js
@@ -127,6 +127,7 @@ module.exports = {
locales: root('src/locales'),
styles: root('src/styles'),
resources: root('src/resources'),
+ client: root('src/client'),
},
},
plugins: [
diff --git a/package.json b/package.json
index 7dda56c7..97cd403c 100644
--- a/package.json
+++ b/package.json
@@ -81,7 +81,8 @@
"react-highcharts": "^16.0.2",
"react-router": "^4.3.1",
"react-router-dom": "^4.3.1",
- "react-sortable-hoc": "1.11.0"
+ "react-sortable-hoc": "1.11.0",
+ "uuid": "^8.3.2"
},
"devDependencies": {
"@babel/core": "^7.14.3",
diff --git a/src/api/cinder/base.js b/src/api/cinder/base.js
index a232d23b..441ba487 100644
--- a/src/api/cinder/base.js
+++ b/src/api/cinder/base.js
@@ -16,7 +16,7 @@
* @param {String} key api url
* @returns {String}
*/
-import { cinderBase } from 'utils/constants';
+import { cinderBase } from 'client/client/constants';
const getCinderBaseUrl = (key) => `${cinderBase()}/${key}`;
diff --git a/src/api/glance/base.js b/src/api/glance/base.js
index 485ed93a..06de3c80 100644
--- a/src/api/glance/base.js
+++ b/src/api/glance/base.js
@@ -16,7 +16,7 @@
* @param {String} key api url
* @returns {String}
*/
-import { glanceBase } from 'utils/constants';
+import { glanceBase } from 'client/client/constants';
const getGlanceBaseUrl = (key) => `${glanceBase()}/${key}`;
diff --git a/src/api/gocron/base.js b/src/api/gocron/base.js
index 47e29752..fbee73db 100644
--- a/src/api/gocron/base.js
+++ b/src/api/gocron/base.js
@@ -16,7 +16,7 @@
* @param {String} key api url
* @returns {String}
*/
-import { gocronBase } from 'utils/constants';
+import { gocronBase } from 'client/client/constants';
const getGocronBaseUrl = (key) => `${gocronBase()}/${key}`;
diff --git a/src/api/heat/base.js b/src/api/heat/base.js
index aee33519..a6fcd3c4 100644
--- a/src/api/heat/base.js
+++ b/src/api/heat/base.js
@@ -16,7 +16,7 @@
* @param {String} key api url
* @returns {String}
*/
-import { heatBase } from 'utils/constants';
+import { heatBase } from 'client/client/constants';
const getHeatBaseUrl = (key) => `${heatBase()}/${key}`;
diff --git a/src/api/ironic-inspector/base.js b/src/api/ironic-inspector/base.js
index 61d557ba..296d6328 100644
--- a/src/api/ironic-inspector/base.js
+++ b/src/api/ironic-inspector/base.js
@@ -16,7 +16,7 @@
* @param {String} key api url
* @returns {String}
*/
-import { ironicInspectorBase } from 'utils/constants';
+import { ironicInspectorBase } from 'client/client/constants';
const getIronicInspectorBaseUrl = (key) => `${ironicInspectorBase()}/${key}`;
diff --git a/src/api/ironic/base.js b/src/api/ironic/base.js
index bc909b08..efe92619 100644
--- a/src/api/ironic/base.js
+++ b/src/api/ironic/base.js
@@ -16,7 +16,7 @@
* @param {String} key api url
* @returns {String}
*/
-import { ironicBase } from 'utils/constants';
+import { ironicBase } from 'client/client/constants';
const getIronicBaseUrl = (key) => `${ironicBase()}/${key}`;
diff --git a/src/api/keystone/base.js b/src/api/keystone/base.js
index cb7cad0e..df7a5a01 100644
--- a/src/api/keystone/base.js
+++ b/src/api/keystone/base.js
@@ -16,7 +16,7 @@
* @param {String} key api url
* @returns {String}
*/
-import { keystoneBase } from 'utils/constants';
+import { keystoneBase } from 'client/client/constants';
const getKeystoneBaseUrl = (key) => `${keystoneBase()}/${key}`;
diff --git a/src/api/neutron/base.js b/src/api/neutron/base.js
index d30feb56..dacb44c7 100644
--- a/src/api/neutron/base.js
+++ b/src/api/neutron/base.js
@@ -16,7 +16,7 @@
* @param {String} key api url
* @returns {String}
*/
-import { neutronBase } from 'utils/constants';
+import { neutronBase } from 'client/client/constants';
const getNeutronBaseUrl = (key) => `${neutronBase()}/${key}`;
diff --git a/src/api/nova/base.js b/src/api/nova/base.js
index 0d4a3366..0f29be21 100644
--- a/src/api/nova/base.js
+++ b/src/api/nova/base.js
@@ -16,7 +16,7 @@
* @param {String} key api url
* @returns {String}
*/
-import { novaBase } from 'utils/constants';
+import { novaBase } from 'client/client/constants';
const getNovaBaseUrl = (key) => `${novaBase()}/${key}`;
diff --git a/src/api/octavia/base.js b/src/api/octavia/base.js
index 85f0e052..942537f0 100644
--- a/src/api/octavia/base.js
+++ b/src/api/octavia/base.js
@@ -16,7 +16,7 @@
* @param {String} key api url
* @returns {String}
*/
-import { octaviaBase } from 'utils/constants';
+import { octaviaBase } from 'client/client/constants';
const getOctaviaBaseUrl = (key) => `${octaviaBase()}/${key}`;
diff --git a/src/api/panko/base.js b/src/api/panko/base.js
index e3b159d3..3e2ab460 100644
--- a/src/api/panko/base.js
+++ b/src/api/panko/base.js
@@ -16,7 +16,7 @@
* @param {String} key api url
* @returns {String}
*/
-import { pankoBase } from 'utils/constants';
+import { pankoBase } from 'client/client/constants';
const getPankoBaseUrl = (key) => `${pankoBase()}/${key}`;
diff --git a/src/api/placement/base.js b/src/api/placement/base.js
index 30017b79..1d5d9f55 100644
--- a/src/api/placement/base.js
+++ b/src/api/placement/base.js
@@ -16,7 +16,7 @@
* @param {String} key api url
* @returns {String}
*/
-import { placementBase } from 'utils/constants';
+import { placementBase } from 'client/client/constants';
const getPlacementBaseUrl = (key) => `${placementBase()}/${key}`;
diff --git a/src/api/skyline/base.js b/src/api/skyline/base.js
index 19765c92..3c52be07 100644
--- a/src/api/skyline/base.js
+++ b/src/api/skyline/base.js
@@ -16,7 +16,7 @@
* @param {String} key api url
* @returns {String}
*/
-import { skylineBase } from 'utils/constants';
+import { skylineBase } from 'client/client/constants';
const getSkylineBaseUrl = (key) => `${skylineBase()}/${key}`;
diff --git a/src/api/swift/base.js b/src/api/swift/base.js
index 1b78587a..b7a0ee4e 100644
--- a/src/api/swift/base.js
+++ b/src/api/swift/base.js
@@ -16,7 +16,7 @@
* @param {String} key api url
* @returns {String}
*/
-import { swiftBase } from 'utils/constants';
+import { swiftBase } from 'client/client/constants';
const getSwiftBaseUrl = (key) => `${swiftBase()}/${key}`;
diff --git a/src/client/cinder/index.js b/src/client/cinder/index.js
new file mode 100644
index 00000000..578b81cf
--- /dev/null
+++ b/src/client/cinder/index.js
@@ -0,0 +1,155 @@
+// Copyright 2021 99cloud
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import Base from '../client/base';
+import { cinderBase } from '../client/constants';
+
+class CinderClient extends Base {
+ get baseUrl() {
+ return cinderBase();
+ }
+
+ get projectInUrl() {
+ return true;
+ }
+
+ get resources() {
+ return [
+ {
+ key: 'volumes',
+ responseKey: 'volume',
+ extendOperations: [
+ {
+ key: 'action',
+ method: 'post',
+ },
+ ],
+ },
+ {
+ key: 'types',
+ responseKey: 'volume_type',
+ extendOperations: [
+ {
+ key: 'action',
+ method: 'post',
+ },
+ {
+ name: 'getAccess',
+ key: 'os-volume-type-access',
+ },
+ ],
+ subResources: [
+ {
+ name: 'extraSpecs',
+ key: 'extra_specs',
+ responseKey: 'extra_spec',
+ },
+ {
+ key: 'encryption',
+ },
+ ],
+ },
+ {
+ key: 'snapshots',
+ responseKey: 'snapshot',
+ },
+ {
+ key: 'backups',
+ responseKey: 'backup',
+ extendOperations: [
+ {
+ key: 'restore',
+ isDetail: true,
+ method: 'post',
+ },
+ ],
+ },
+ {
+ name: 'backupChains',
+ key: 'backup_chains',
+ responseKey: 'backup_chain',
+ extendOperations: [
+ {
+ key: 'restore',
+ isDetail: true,
+ method: 'post',
+ },
+ ],
+ },
+ {
+ name: 'pools',
+ key: 'scheduler-stats/get_pools',
+ responseKey: 'pool',
+ },
+ {
+ name: 'qosSpecs',
+ key: 'qos-specs',
+ responseKey: 'qos_spec',
+ extendOperations: [
+ {
+ name: 'deleteKeys',
+ key: 'delete_keys',
+ method: 'put',
+ },
+ {
+ key: 'associate',
+ },
+ {
+ key: 'disassociate',
+ },
+ ],
+ },
+ {
+ name: 'services',
+ key: 'os-services',
+ responseKey: 'service',
+ extendOperations: [
+ {
+ key: 'enable',
+ isDetail: false,
+ method: 'put',
+ },
+ {
+ name: 'reason',
+ key: 'disable-log-reason',
+ isDetail: false,
+ method: 'put',
+ },
+ ],
+ },
+ {
+ name: 'quotaSets',
+ key: 'os-quota-sets',
+ responseKey: 'quota_set',
+ },
+ {
+ name: 'azones',
+ key: 'os-availability-zone',
+ },
+ {
+ name: 'volumeTransfers',
+ key: 'volume-transfers',
+ extendOperations: [
+ {
+ key: 'accept',
+ method: 'post',
+ },
+ ],
+ },
+ ];
+ }
+}
+
+const cinderClient = new CinderClient();
+export default cinderClient;
diff --git a/src/client/client/base.js b/src/client/client/base.js
new file mode 100644
index 00000000..b6e0e3f6
--- /dev/null
+++ b/src/client/client/base.js
@@ -0,0 +1,420 @@
+// Copyright 2021 99cloud
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import globalRootStore from 'stores/root';
+import clientRequest from './request';
+
+export default class BaseClient {
+ constructor() {
+ this.generateAll();
+ }
+
+ getUrl = (url) => {
+ if (this.projectInUrl) {
+ return `${this.baseUrl}/${this.project}/${url}`;
+ }
+ return `${this.baseUrl}/${url}`;
+ };
+
+ get request() {
+ const { request } = clientRequest;
+ return {
+ get: (url, params, conf) => request.get(this.getUrl(url), params, conf),
+ post: (url, data, params, conf) =>
+ request.post(this.getUrl(url), data, params, conf),
+ put: (url, data, params, conf) =>
+ request.put(this.getUrl(url), data, params, conf),
+ delete: (url, data, params, conf) =>
+ request.delete(this.getUrl(url), data, params, conf),
+ patch: (url, data, params, conf) =>
+ request.patch(this.getUrl(url), data, params, conf),
+ head: (url, params, conf) => request.head(this.getUrl(url), params, conf),
+ };
+ }
+
+ get originRequest() {
+ const { request } = clientRequest;
+ return request;
+ }
+
+ get params() {
+ return [];
+ }
+
+ get baseUrl() {
+ return '';
+ }
+
+ get projectInUrl() {
+ return false;
+ }
+
+ get project() {
+ if (!this.projectInUrl) {
+ return '';
+ }
+ const { project: { id } = {} } = globalRootStore.user || {};
+ return id || '';
+ }
+
+ get enabled() {
+ return true;
+ }
+
+ get resources() {
+ return [];
+ }
+
+ getListUrl(resourceName) {
+ return resourceName;
+ }
+
+ getDetailUrl(resourceName, id) {
+ if (!id) {
+ return resourceName;
+ }
+ if (resourceName[resourceName.length - 1] === '/') {
+ return `${resourceName.substr(0, resourceName.length - 1)}/${id}`;
+ }
+ return `${resourceName}/${id}`;
+ }
+
+ getSubResourceUrl(resourceName, subResourceName) {
+ if (!resourceName) {
+ return subResourceName;
+ }
+ if (resourceName[resourceName.length - 1] === '/') {
+ return `${resourceName.substr(
+ 0,
+ resourceName.length - 1
+ )}/${subResourceName}`;
+ }
+ return `${resourceName}/${subResourceName}`;
+ }
+
+ getSubResourceUrlById(resourceName, subResourceName, id) {
+ return `${this.getDetailUrl(resourceName, id)}/${subResourceName}`;
+ }
+
+ getSubResourceUrlBySubId(resourceName, subResourceName, id, subId) {
+ return `${this.getSubResourceUrlById(
+ resourceName,
+ subResourceName,
+ id
+ )}/${subId}`;
+ }
+
+ getSubSubResourceListUrl(
+ resourceName,
+ subResourceName,
+ subSubResourceName,
+ id,
+ subId
+ ) {
+ return `${this.getSubResourceUrlBySubId(
+ resourceName,
+ subResourceName,
+ id,
+ subId
+ )}/${subSubResourceName}`;
+ }
+
+ getSubSubResourceDetailUrl(
+ resourceName,
+ subResourceName,
+ subSubResourceName,
+ id,
+ subId,
+ subSubId
+ ) {
+ return `${this.getSubSubResourceListUrl(
+ resourceName,
+ subResourceName,
+ subSubResourceName,
+ id,
+ subId
+ )}/${subSubId}`;
+ }
+
+ generateResource = (resourceName, responseKey, enabled = true) => {
+ const listUrl = this.getListUrl(resourceName);
+ return {
+ list: (params, conf) => this.request.get(listUrl, params, conf),
+ listDetail: (params, conf) =>
+ this.request.get(`${listUrl}/detail`, params, conf),
+ show: (id, params, conf) => {
+ return this.request.get(
+ this.getDetailUrl(resourceName, id),
+ params,
+ conf
+ );
+ },
+ create: (data, ...args) => this.request.post(listUrl, data, ...args),
+ update: (id, data, ...args) =>
+ this.request.put(this.getDetailUrl(resourceName, id), data, ...args),
+ patch: (id, data, ...args) =>
+ this.request.patch(this.getDetailUrl(resourceName, id), data, ...args),
+ delete: (id, ...args) =>
+ this.request.delete(this.getDetailUrl(resourceName, id), ...args),
+ responseKey,
+ enabled,
+ };
+ };
+
+ generateSubResource = (
+ resourceName,
+ subResourceName,
+ responseKey,
+ enabled
+ ) => ({
+ list: (id, params, ...args) =>
+ this.request.get(
+ this.getSubResourceUrlById(resourceName, subResourceName, id),
+ params,
+ ...args
+ ),
+ listDetail: (id, params, ...args) =>
+ this.request.get(
+ `${this.getSubResourceUrlById(
+ resourceName,
+ subResourceName,
+ id
+ )}/detail`,
+ params,
+ ...args
+ ),
+ show: (id, subId, params, ...args) =>
+ this.request.get(
+ this.getSubResourceUrlBySubId(resourceName, subResourceName, id, subId),
+ params,
+ ...args
+ ),
+ create: (id, data, ...args) =>
+ this.request.post(
+ this.getSubResourceUrlById(resourceName, subResourceName, id),
+ data,
+ ...args
+ ),
+ update: (id, subId, data, ...args) =>
+ this.request.put(
+ this.getSubResourceUrlBySubId(resourceName, subResourceName, id, subId),
+ data,
+ ...args
+ ),
+ patch: (id, subId, data, ...args) =>
+ this.request.patch(
+ this.getSubResourceUrlBySubId(resourceName, subResourceName, id, subId),
+ data,
+ ...args
+ ),
+ delete: (id, subId, ...args) =>
+ this.request.delete(
+ this.getSubResourceUrlBySubId(resourceName, subResourceName, id, subId),
+ ...args
+ ),
+ responseKey,
+ enabled,
+ });
+
+ generateSubSonResource = (
+ resourceName,
+ subResourceName,
+ subSubResonseName,
+ responseKey
+ ) => ({
+ list: (id, subId, params, ...args) =>
+ this.request.get(
+ this.getSubSubResourceListUrl(
+ resourceName,
+ subResourceName,
+ subSubResonseName,
+ id,
+ subId
+ ),
+ params,
+ ...args
+ ),
+ show: (id, subId, subSubId, params, ...args) =>
+ this.request.get(
+ this.getSubSubResourceDetailUrl(
+ resourceName,
+ subResourceName,
+ subSubResonseName,
+ id,
+ subId,
+ subSubId
+ ),
+ params,
+ ...args
+ ),
+ create: (id, subId, data, ...args) =>
+ this.request.post(
+ this.getSubSubResourceListUrl(
+ resourceName,
+ subResourceName,
+ subSubResonseName,
+ id,
+ subId
+ ),
+ data,
+ ...args
+ ),
+ update: (id, subId, subSubId, data, ...args) =>
+ this.request.put(
+ this.getSubSubResourceDetailUrl(
+ resourceName,
+ subResourceName,
+ subSubResonseName,
+ id,
+ subId,
+ subSubId
+ ),
+ data,
+ ...args
+ ),
+ patch: (id, subId, subSubId, data, ...args) =>
+ this.request.patch(
+ this.getSubSubResourceDetailUrl(
+ resourceName,
+ subResourceName,
+ subSubResonseName,
+ id,
+ subId,
+ subSubId
+ ),
+ data,
+ ...args
+ ),
+ delete: (id, subId, subSubId, ...args) =>
+ this.request.delete(
+ this.getSubSubResourceDetailUrl(
+ resourceName,
+ subResourceName,
+ subSubResonseName,
+ id,
+ subId,
+ subSubId
+ ),
+ ...args
+ ),
+ responseKey,
+ });
+
+ setRequest = (url, method, ...restArgs) => {
+ const lowerMethod = method.toLowerCase();
+ return this.request[lowerMethod](url, ...restArgs);
+ };
+
+ generateAll = () => {
+ this.resources.forEach((resource) => {
+ const {
+ name,
+ key,
+ responseKey,
+ enabled,
+ subResources = [],
+ isResource = true,
+ extendOperations = [],
+ } = resource;
+ const result = isResource
+ ? this.generateResource(key, responseKey, enabled)
+ : {};
+ const realName = name || key;
+ extendOperations.forEach((other) => {
+ const {
+ name: otherName,
+ key: otherKey,
+ method = 'get',
+ isDetail,
+ generate,
+ url,
+ } = other;
+ const otherRealName = otherName || otherKey;
+ const otherUrl = url && url();
+ const otherIsDetail = isResource
+ ? isDetail === undefined
+ ? true
+ : isDetail
+ : isDetail === undefined
+ ? false
+ : isDetail;
+ if (generate) {
+ result[otherRealName] = generate;
+ } else if (otherIsDetail) {
+ result[otherRealName] = (id, ...args) => {
+ return this.setRequest(
+ otherUrl || this.getSubResourceUrlById(key, otherKey, id),
+ method,
+ ...args
+ );
+ };
+ } else {
+ result[otherRealName] = (...args) => {
+ return this.setRequest(
+ otherUrl || this.getSubResourceUrl(key, otherKey),
+ method,
+ ...args
+ );
+ };
+ }
+ });
+ subResources.forEach((sub) => {
+ let subResult = {};
+ const {
+ name: subName,
+ key: subKey,
+ responseKey: subResponseKey,
+ method: subMethod,
+ enabled: subEnabled,
+ subResources: subSubResources = [],
+ } = sub;
+ const subRealName = subName || subKey;
+ if (!subMethod) {
+ subResult = this.generateSubResource(
+ key,
+ subKey,
+ subResponseKey,
+ subEnabled
+ );
+ } else {
+ subResult = (id, ...args) => {
+ const url = this.getSubResourceUrlById(key, subKey, id);
+ return this.setRequest(url, subMethod, ...args);
+ };
+ }
+ subSubResources.forEach((son) => {
+ const {
+ key: sonKey,
+ name: sonName,
+ responseKey: sonResponseKey,
+ } = son;
+ subResult[sonName || sonKey] = this.generateSubSonResource(
+ key,
+ subKey,
+ sonKey,
+ sonResponseKey
+ );
+ });
+ result[subRealName] = subResult;
+ });
+ if (realName) {
+ this[realName] = result;
+ } else {
+ Object.keys(result).forEach((resultKey) => {
+ this[resultKey] = result[resultKey];
+ });
+ }
+ });
+ };
+}
diff --git a/src/client/client/constants.js b/src/client/client/constants.js
new file mode 100644
index 00000000..14392b27
--- /dev/null
+++ b/src/client/client/constants.js
@@ -0,0 +1,125 @@
+// Copyright 2021 99cloud
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import globalRootStore from 'stores/root';
+import { toJS } from 'mobx';
+
+export const groupNameVersionMap = {
+ core: 'v1',
+ system: 'v1',
+};
+
+const endpointVersionMap = {
+ keystone: 'v3',
+ nova: 'v2.1',
+ cinder: 'v3',
+ glance: 'v2',
+ neutron: 'v2.0',
+ ironic: 'v1',
+ ironicInspector: 'v1',
+ heat: 'v1',
+ swift: 'v1',
+ octavia: 'v2',
+ courier: 'v1',
+ prometheus: 'api/v1',
+ prometheus_sidecar: 'api/v1',
+ gocron: 'api',
+ panko: 'v2',
+ billing_system: 'api/core.io/v1',
+ workflow: 'api/core.io/v1',
+};
+
+const endpointsDefault = {
+ ironic: '/api/openstack/ironic',
+ ironicInspector: '/api/openstack/ironic-inspector',
+ swift: '/api/openstack/swift/swift',
+ octavia: '/api/openstack/octavia',
+};
+
+export const getOpenstackEndpoint = (key) => {
+ const { endpoints = {} } = globalRootStore || {};
+ const version = endpointVersionMap[key];
+ const endpoint = endpoints[key] || endpointsDefault[key] || '';
+ return version ? `${endpoint}/${version}` : endpoint;
+};
+
+export const getOriginEndpoint = (key) => {
+ const endpoints = toJS((globalRootStore && globalRootStore.endpoints) || {});
+ return endpoints[key];
+};
+
+export const skylineBase = () => '/api/openstack/skyline/api/v1';
+export const keystoneBase = () => getOpenstackEndpoint('keystone');
+export const novaBase = () => getOpenstackEndpoint('nova');
+export const cinderBase = () => getOpenstackEndpoint('cinder');
+export const glanceBase = () => getOpenstackEndpoint('glance');
+export const neutronBase = () => getOpenstackEndpoint('neutron');
+export const ironicBase = () => getOpenstackEndpoint('ironic');
+export const ironicInspectorBase = () =>
+ getOpenstackEndpoint('ironicInspector');
+export const placementBase = () => getOpenstackEndpoint('placement');
+export const heatBase = () => getOpenstackEndpoint('heat');
+export const swiftBase = () => getOpenstackEndpoint('swift');
+export const octaviaBase = () => getOpenstackEndpoint('octavia');
+export const alertmanagerBase = () => getOpenstackEndpoint('alertmanager');
+export const prometheusBase = () => getOpenstackEndpoint('prometheus');
+export const prometheusSidecarBase = () =>
+ getOpenstackEndpoint('prometheus_sidecar');
+export const courierBase = () => getOpenstackEndpoint('courier');
+export const gocronBase = () => getOpenstackEndpoint('gocron');
+export const pankoBase = () => getOpenstackEndpoint('panko');
+export const s3Base = () => getOpenstackEndpoint('s3');
+export const billingBase = () => getOpenstackEndpoint('billing_system');
+export const workflowBase = () => getOpenstackEndpoint('workflow');
+
+export const ironicOriginEndpoint = () => getOriginEndpoint('ironic');
+export const s3OriginEndpoint = () => getOriginEndpoint('s3');
+export const billingEndpoint = () => getOriginEndpoint('billing_system');
+export const firewallEndpoint = () => getOriginEndpoint('neutron_firewall');
+export const vpnEndpoint = () => getOriginEndpoint('neutron_vpn');
+export const lbEndpoint = () => getOriginEndpoint('octavia');
+
+export const apiVersionMaps = {
+ nova: {
+ key: 'Openstack-Api-Version',
+ value: 'compute 2.79',
+ },
+ placement: {
+ key: 'Openstack-Api-Version',
+ value: 'placement 1.28',
+ },
+ cinder: {
+ key: 'Openstack-Api-Version',
+ value: 'volume 3.59',
+ },
+ ironic: {
+ key: 'X-Openstack-Ironic-Api-Version',
+ value: '1.58',
+ },
+ 'ironic-inspect': {
+ key: 'X-OpenStack-Ironic-Inspector-API-Version',
+ value: '1.15',
+ },
+};
+
+export const getOpenstackApiVersion = (url) => {
+ const key = Object.keys(apiVersionMaps).find((it) => url.indexOf(it) > -1);
+ if (!key) {
+ return null;
+ }
+ return apiVersionMaps[key];
+};
+
+export const getK8sTypeEndpoint = (groupName, baseUrl) =>
+ `${baseUrl}/${groupName}/${groupNameVersionMap[groupName]}`;
diff --git a/src/client/client/request.js b/src/client/client/request.js
new file mode 100644
index 00000000..88943b75
--- /dev/null
+++ b/src/client/client/request.js
@@ -0,0 +1,181 @@
+// Copyright 2021 99cloud
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import Axios from 'axios';
+import { getLocalStorageItem } from 'utils/local-storage';
+import { isEqual } from 'lodash';
+import qs from 'qs';
+import globalRootStore from 'stores/root';
+import { v4 as uuidv4 } from 'uuid';
+import { getOpenstackApiVersion } from './constants';
+
+const METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD'];
+/**
+ * @class HttpRequest
+ * request with axios
+ */
+export class HttpRequest {
+ constructor() {
+ this.request = {};
+ }
+
+ /**
+ * @param instance instance of axios
+ * @param url request url
+ * interceptors includes request & response
+ * @returns {void}
+ */
+ interceptors(instance, url) {
+ instance.interceptors.request.use(
+ (config) => {
+ const uuid = uuidv4();
+ config.headers['X-Openstack-Request-Id'] = `req-${uuid}`;
+ const keystoneToken = getLocalStorageItem('keystone_token') || '';
+ const apiVersionMap = getOpenstackApiVersion(url);
+ if (keystoneToken) {
+ config.headers['X-Auth-Token'] = keystoneToken;
+ }
+ if (apiVersionMap) {
+ config.headers[apiVersionMap.key] = apiVersionMap.value;
+ }
+ const { options: { headers, isFormData, ...rest } = {} } = config;
+ if (!isEqual(headers)) {
+ config.headers = {
+ ...config.headers,
+ ...headers,
+ };
+ }
+ if (isFormData) {
+ delete config.headers['Content-Type'];
+ }
+ Object.keys(rest).forEach((key) => {
+ config[key] = rest[key];
+ });
+ return config;
+ },
+ (err) => Promise.reject(err)
+ );
+
+ instance.interceptors.response.use(
+ (response) => {
+ // request is finished
+ const { data, status } = response;
+ const disposition = response.headers['content-disposition'] || '';
+ const contentType = response.headers['content-type'] || '';
+ if (contentType.includes('application/octet-stream')) {
+ return response;
+ }
+ if (disposition.includes('attachment')) {
+ return response;
+ }
+ if (status < 200 || status >= 300) {
+ return Promise.reject(data);
+ }
+ return data;
+ },
+ (error) => {
+ // request is finished
+ // eslint-disable-next-line no-console
+ console.log('error.response', error.response, error);
+ if (error.response) {
+ const { status } = error.response;
+ if (status === 401) {
+ const currentPath = window.location.pathname;
+ if (currentPath.indexOf('login') < 0) {
+ globalRootStore.gotoLoginPage(currentPath);
+ }
+ }
+ }
+ return Promise.reject(error);
+ }
+ );
+ }
+
+ /**
+ * create a new instance of axios with a custom config
+ */
+ create() {
+ const conf = {
+ baseURL: '/',
+ headers: {
+ 'Content-Type': 'application/json;charset=utf-8',
+ 'cache-control': 'no-cache',
+ pragma: 'no-cache',
+ },
+ };
+ return Axios.create(conf);
+ }
+
+ /**
+ * @param {Object} obj translated object
+ * @returns {Object} trim undefined & null
+ */
+ omitNil(obj) {
+ if (typeof obj !== 'object') return obj;
+ return Object.keys(obj).reduce((acc, v) => {
+ if (obj[v] !== undefined && obj[v] !== null && obj[v] !== '')
+ acc[v] = obj[v];
+ return acc;
+ }, {});
+ }
+
+ /**
+ * build request
+ * @param {Object} config requests config
+ * @returns {Promise} axios instance return promise
+ */
+ buildRequest(config) {
+ const method = config.method ? config.method.toLowerCase() : 'get';
+ const options = { ...config };
+ // Only get and head, we need to use null for some posts requests
+ if (options.params && ['get', 'head'].includes(method)) {
+ options.params = this.omitNil(options.params);
+ options.paramsSerializer = (p) =>
+ qs.stringify(p, { arrayFormat: 'repeat' });
+ }
+ const instance = this.create();
+ this.interceptors(instance, options.url);
+ return instance(options);
+ }
+
+ generateRequestMap = () => {
+ METHODS.forEach((method) => {
+ const lowerMethod = method.toLowerCase();
+ if (lowerMethod === 'get' || lowerMethod === 'head') {
+ this.request[lowerMethod] = (url, params = {}, options) => {
+ return this.buildRequest({
+ method: lowerMethod,
+ url,
+ params,
+ options,
+ });
+ };
+ } else {
+ this.request[lowerMethod] = (url, data, params, options) => {
+ return this.buildRequest({
+ method: lowerMethod,
+ url,
+ data,
+ params,
+ options,
+ });
+ };
+ }
+ });
+ };
+}
+
+const httpRequest = new HttpRequest();
+httpRequest.generateRequestMap();
+export default httpRequest;
diff --git a/src/client/glance/index.js b/src/client/glance/index.js
new file mode 100644
index 00000000..8ac50522
--- /dev/null
+++ b/src/client/glance/index.js
@@ -0,0 +1,87 @@
+// Copyright 2021 99cloud
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import Base from '../client/base';
+import { glanceBase } from '../client/constants';
+
+class GlanceClient extends Base {
+ get baseUrl() {
+ return glanceBase();
+ }
+
+ get resources() {
+ return [
+ {
+ key: 'images',
+ responseKey: 'image',
+ extendOperations: [
+ {
+ key: 'count',
+ isDetail: false,
+ },
+ {
+ key: 'uploadFile',
+ generate: (id, body, conf = {}) => {
+ return this.request.put(
+ `${this.getDetailUrl('images', id)}/file`,
+ body,
+ null,
+ {
+ headers: {
+ 'content-type': 'application/octet-stream',
+ },
+ ...conf,
+ }
+ );
+ },
+ },
+ {
+ key: 'patch',
+ generate: (id, data) =>
+ this.request.patch(this.getDetailUrl('images', id), data, null, {
+ headers: {
+ 'content-type':
+ 'application/openstack-images-v2.1-json-patch',
+ },
+ }),
+ },
+ ],
+ subResources: [
+ {
+ key: 'members',
+ },
+ ],
+ },
+ {
+ name: 'namespaces',
+ key: 'metadefs/namespaces',
+ responseKey: 'namespace',
+ subResources: [
+ {
+ name: 'resourceTypes',
+ key: 'resource_types',
+ },
+ ],
+ },
+ {
+ name: 'resourceTypes',
+ key: 'metadefs/resource_types',
+ responseKey: 'resource_type',
+ },
+ ];
+ }
+}
+
+const glanceClient = new GlanceClient();
+export default glanceClient;
diff --git a/src/client/heat/index.js b/src/client/heat/index.js
new file mode 100644
index 00000000..20c1470a
--- /dev/null
+++ b/src/client/heat/index.js
@@ -0,0 +1,93 @@
+// Copyright 2021 99cloud
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import Base from '../client/base';
+import { heatBase } from '../client/constants';
+
+class HeatClient extends Base {
+ get baseUrl() {
+ return heatBase();
+ }
+
+ get projectInUrl() {
+ return true;
+ }
+
+ getDetailUrlForStack = ({ id, name }) => `stacks/${name}/${id}`;
+
+ get resources() {
+ return [
+ {
+ key: 'stacks',
+ responseKey: 'stack',
+ extendOperations: [
+ {
+ key: 'show',
+ generate: ({ id, name }, params) => {
+ return this.request.get(
+ this.getDetailUrlForStack({ id, name }),
+ params
+ );
+ },
+ },
+ {
+ key: 'update',
+ generate: ({ id, name }, data) =>
+ this.request.put(this.getDetailUrlForStack({ id, name }), data),
+ },
+ {
+ key: 'delete',
+ generate: ({ id, name }) =>
+ this.request.delete(this.getDetailUrlForStack({ id, name })),
+ },
+ {
+ key: 'abandon',
+ generate: ({ id, name }) =>
+ this.request.delete(
+ `${this.getDetailUrlForStack({ id, name })}/abandon`
+ ),
+ },
+ {
+ key: 'template',
+ generate: ({ id, name }) =>
+ this.request.get(
+ `${this.getDetailUrlForStack({ id, name })}/template`
+ ),
+ },
+ {
+ key: 'events',
+ generate: ({ id, name }) =>
+ this.request.get(
+ `${this.getDetailUrlForStack({ id, name })}/events`
+ ),
+ },
+ {
+ key: 'resources',
+ generate: ({ id, name }) =>
+ this.request.get(
+ `${this.getDetailUrlForStack({ id, name })}/resources`
+ ),
+ },
+ ],
+ },
+ {
+ key: 'services',
+ responseKey: 'service',
+ },
+ ];
+ }
+}
+
+const heatClient = new HeatClient();
+export default heatClient;
diff --git a/src/client/index.js b/src/client/index.js
new file mode 100644
index 00000000..e5e6b59d
--- /dev/null
+++ b/src/client/index.js
@@ -0,0 +1,41 @@
+// Copyright 2021 99cloud
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import skyline from './skyline';
+import nova from './nova';
+import cinder from './cinder';
+import glance from './glance';
+import neutron from './neutron';
+import keystone from './keystone';
+import heat from './heat';
+import octavia from './octavia';
+import placement from './placement';
+import ironic from './ironic';
+
+const client = {
+ skyline,
+ nova,
+ cinder,
+ glance,
+ neutron,
+ keystone,
+ heat,
+ octavia,
+ placement,
+ ironic,
+};
+
+window.client = client;
+
+export default client;
diff --git a/src/client/ironic/index.js b/src/client/ironic/index.js
new file mode 100644
index 00000000..b54adf71
--- /dev/null
+++ b/src/client/ironic/index.js
@@ -0,0 +1,96 @@
+// Copyright 2021 99cloud
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import Base from '../client/base';
+import { ironicBase } from '../client/constants';
+
+class IronicClient extends Base {
+ get baseUrl() {
+ return ironicBase();
+ }
+
+ get resources() {
+ return [
+ {
+ key: 'nodes',
+ responseKey: 'node',
+ extendOperations: [
+ {
+ name: 'updateStatesProvision',
+ key: 'states/provision',
+ method: 'put',
+ },
+ {
+ name: 'UpdateStatesPower',
+ key: 'states/power',
+ method: 'put',
+ },
+ {
+ name: 'updateMaintenance',
+ key: 'maintenance',
+ method: 'put',
+ },
+ {
+ name: 'deleteMaintenance',
+ key: 'maintenance',
+ method: 'delete',
+ },
+ {
+ name: 'getManagementBootDevice',
+ key: 'management/boot_device',
+ },
+ {
+ name: 'updateManagementBootDevice',
+ key: 'management/boot_device',
+ method: 'put',
+ },
+ {
+ name: 'getManagementBootDeviceSupported',
+ key: 'management/boot_device/supported',
+ },
+ {
+ key: 'updateTraits',
+ method: 'put',
+ },
+ ],
+ subResources: [
+ {
+ key: 'states',
+ },
+ {
+ key: 'validate',
+ },
+ {
+ key: 'ports',
+ },
+ {
+ key: 'portgroups',
+ responseKey: 'portgroup',
+ },
+ ],
+ },
+ {
+ key: 'ports',
+ responseKey: 'port',
+ },
+ {
+ key: 'portgroups',
+ responseKey: 'portgroup',
+ },
+ ];
+ }
+}
+
+const ironicClient = new IronicClient();
+export default ironicClient;
diff --git a/src/client/keystone/index.js b/src/client/keystone/index.js
new file mode 100644
index 00000000..6578223c
--- /dev/null
+++ b/src/client/keystone/index.js
@@ -0,0 +1,149 @@
+// Copyright 2021 99cloud
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import Base from '../client/base';
+import { keystoneBase } from '../client/constants';
+
+class KeystoneClient extends Base {
+ get baseUrl() {
+ return keystoneBase();
+ }
+
+ get resources() {
+ return [
+ {
+ name: 'catalog',
+ key: 'auth/catalog',
+ responseKey: 'catalog',
+ },
+ {
+ key: 'projects',
+ responseKey: 'project',
+ extendOperations: [
+ {
+ name: 'updateTags',
+ key: 'tags',
+ method: 'put',
+ },
+ ],
+ subResources: [
+ {
+ key: 'tags',
+ responseKey: 'tag',
+ },
+ {
+ key: 'groups',
+ subResources: [
+ {
+ key: 'roles',
+ },
+ ],
+ },
+ {
+ key: 'users',
+ subResources: [
+ {
+ key: 'roles',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ key: 'domains',
+ responseKey: 'domain',
+ subResources: [
+ {
+ key: 'groups',
+ subResources: [
+ {
+ key: 'roles',
+ },
+ ],
+ },
+ {
+ key: 'users',
+ subResources: [
+ {
+ key: 'roles',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ key: 'roles',
+ responseKey: 'role',
+ subResources: [
+ {
+ key: 'implies',
+ },
+ ],
+ },
+ {
+ name: 'roleAssignments',
+ key: 'role_assignments',
+ },
+ {
+ key: 'users',
+ responseKey: 'user',
+ subResources: [
+ {
+ key: 'projects',
+ },
+ {
+ key: 'groups',
+ },
+ ],
+ extendOperations: [
+ {
+ name: 'updatePassword',
+ key: 'password',
+ method: 'post',
+ },
+ ],
+ },
+ {
+ key: 'groups',
+ responseKey: 'group',
+ subResources: [
+ {
+ key: 'users',
+ },
+ ],
+ },
+ {
+ name: 'systemGroups',
+ key: 'system/groups',
+ subResources: [
+ {
+ key: 'roles',
+ },
+ ],
+ },
+ {
+ name: 'systemUsers',
+ key: 'system/users',
+ subResources: [
+ {
+ key: 'roles',
+ },
+ ],
+ },
+ ];
+ }
+}
+
+const keystoneClient = new KeystoneClient();
+export default keystoneClient;
diff --git a/src/client/neutron/index.js b/src/client/neutron/index.js
new file mode 100644
index 00000000..1d7c085d
--- /dev/null
+++ b/src/client/neutron/index.js
@@ -0,0 +1,201 @@
+// Copyright 2021 99cloud
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import Base from '../client/base';
+import { neutronBase } from '../client/constants';
+
+class NeutronClient extends Base {
+ get baseUrl() {
+ return neutronBase();
+ }
+
+ get resources() {
+ return [
+ {
+ key: 'networks',
+ responseKey: 'network',
+ subResources: [
+ {
+ name: 'dhcpAgents',
+ key: 'dhcp-agents',
+ },
+ ],
+ },
+ {
+ key: 'subnets',
+ responseKey: 'subnet',
+ },
+ {
+ key: 'extensions',
+ },
+ {
+ key: 'ports',
+ responseKey: 'port',
+ },
+ {
+ key: 'routers',
+ responseKey: 'router',
+ extendOperations: [
+ {
+ name: 'addRouterInterface',
+ key: 'add_router_interface',
+ method: 'put',
+ },
+ {
+ name: 'removeRouterInterface',
+ key: 'remove_router_interface',
+ method: 'put',
+ },
+ {
+ name: 'addExtraRoutes',
+ key: 'add_extraroutes',
+ method: 'put',
+ },
+ {
+ name: 'removeExtraRoutes',
+ key: 'remove_extraroutes',
+ method: 'put',
+ },
+ ],
+ },
+ {
+ key: 'floatingips',
+ responseKey: 'floatingip',
+ subResources: [
+ {
+ name: 'portForwardings',
+ key: 'port_forwardings',
+ responseKey: 'port_forwarding',
+ },
+ ],
+ },
+ {
+ key: 'agents',
+ responseKey: 'agent',
+ subResources: [
+ {
+ name: 'dhcpNetworks',
+ key: 'dhcp-networks',
+ responseKey: 'network',
+ },
+ {
+ name: 'l3Routers',
+ key: 'l3-routers',
+ responseKey: 'router',
+ },
+ ],
+ },
+ {
+ name: 'firewalls',
+ key: 'fwaas/firewall_groups',
+ responseKey: 'firewall_group',
+ },
+ {
+ name: 'firewallPolicies',
+ key: 'fwaas/firewall_policies',
+ responseKey: 'firewall_policy',
+ extendOperations: [
+ {
+ name: 'insertRule',
+ key: 'insert_rule',
+ method: 'put',
+ },
+ {
+ name: 'removeRule',
+ key: 'remove_rule',
+ method: 'put',
+ },
+ ],
+ },
+ {
+ name: 'firewallRules',
+ key: 'fwaas/firewall_rules',
+ responseKey: 'firewall_rule',
+ },
+ {
+ name: 'networkIpAvailabilities',
+ key: 'network-ip-availabilities',
+ },
+ {
+ name: 'azones',
+ key: 'availability_zones',
+ },
+ {
+ name: 'qosPolicies',
+ key: 'qos/policies',
+ responseKey: 'policy',
+ subResources: [
+ {
+ name: 'bandwidthLimitRules',
+ key: 'bandwidth_limit_rules',
+ },
+ {
+ name: 'dscpMarkingRules',
+ key: 'dscp_marking_rules',
+ },
+ ],
+ },
+ {
+ name: 'securityGroups',
+ key: 'security-groups',
+ responseKey: 'security_group',
+ },
+ {
+ name: 'securityGroupRules',
+ key: 'security-group-rules',
+ responseKey: 'security_group_rule',
+ },
+ {
+ key: 'subnets',
+ responseKey: 'subnet',
+ },
+ {
+ name: 'endpointGroups',
+ key: 'endpoint-groups',
+ responseKey: 'endpoint_group',
+ },
+ {
+ name: 'ikePolicies',
+ key: 'ikepolicies',
+ responseKey: 'ikepolicy',
+ },
+ {
+ name: 'ipsecPolicies',
+ key: 'ipsecpolicies',
+ responseKey: 'ipsecpolicy',
+ },
+ {
+ name: 'ipsecSiteConnections',
+ key: 'ipsec_site_connections',
+ responseKey: 'ipsec_site_connection',
+ },
+ {
+ key: 'vpnservices',
+ responseKey: 'vpnservice',
+ },
+ {
+ key: 'quotas',
+ responseKey: 'quota',
+ extendOperations: [
+ {
+ key: 'details',
+ },
+ ],
+ },
+ ];
+ }
+}
+
+const neutronClient = new NeutronClient();
+export default neutronClient;
diff --git a/src/client/nova/index.js b/src/client/nova/index.js
new file mode 100644
index 00000000..f05ef111
--- /dev/null
+++ b/src/client/nova/index.js
@@ -0,0 +1,134 @@
+// Copyright 2021 99cloud
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import Base from '../client/base';
+import { novaBase } from '../client/constants';
+
+class NovaClient extends Base {
+ get baseUrl() {
+ return novaBase();
+ }
+
+ get resources() {
+ return [
+ {
+ key: 'servers',
+ responseKey: 'server',
+ subResources: [
+ {
+ name: 'interfaces',
+ key: 'os-interface',
+ },
+ {
+ name: 'volumeAttachments',
+ key: 'os-volume_attachments',
+ responseKey: 'volumeAttachment',
+ },
+ {
+ name: 'instanceActions',
+ key: 'os-instance-actions',
+ responseKey: 'instanceAction',
+ },
+ ],
+ extendOperations: [
+ {
+ name: 'createConsole',
+ key: 'remote-consoles',
+ method: 'post',
+ },
+ {
+ key: 'action',
+ method: 'post',
+ },
+ ],
+ },
+ {
+ name: 'zone',
+ key: 'os-availability-zone',
+ responseKey: 'availabilityZoneInfo',
+ },
+ {
+ key: 'flavors',
+ responseKey: 'flavor',
+ extendOperations: [
+ {
+ name: 'action',
+ key: 'action',
+ method: 'post',
+ },
+ ],
+ subResources: [
+ {
+ name: 'access',
+ key: 'os-flavor-access',
+ },
+ {
+ name: 'extraSpecs',
+ key: 'os-extra_specs',
+ },
+ ],
+ },
+ {
+ name: 'keypairs',
+ key: 'os-keypairs',
+ responseKey: 'keypair',
+ },
+ {
+ name: 'serverGroups',
+ key: 'os-server-groups',
+ responseKey: 'server_group',
+ },
+ {
+ name: 'aggregates',
+ key: 'os-aggregates',
+ responseKey: 'aggregate',
+ extendOperations: [
+ {
+ name: 'action',
+ key: 'action',
+ method: 'post',
+ },
+ ],
+ },
+ {
+ name: 'services',
+ key: 'os-services',
+ responseKey: 'service',
+ },
+ {
+ name: 'quotaSets',
+ key: 'os-quota-sets',
+ responseKey: 'quota_set',
+ extendOperations: [
+ {
+ key: 'detail',
+ },
+ ],
+ },
+ {
+ name: 'hypervisors',
+ key: 'os-hypervisors',
+ responseKey: 'hypervisor',
+ },
+ {
+ name: 'pciDevices',
+ key: 'os-pci-devices',
+ responseKey: 'pci_device',
+ },
+ ];
+ }
+}
+
+const novaClient = new NovaClient();
+export default novaClient;
diff --git a/src/client/octavia/index.js b/src/client/octavia/index.js
new file mode 100644
index 00000000..b10e789d
--- /dev/null
+++ b/src/client/octavia/index.js
@@ -0,0 +1,62 @@
+// Copyright 2021 99cloud
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import Base from '../client/base';
+import { octaviaBase } from '../client/constants';
+
+class OctaviaClient extends Base {
+ get baseUrl() {
+ return octaviaBase();
+ }
+
+ get resources() {
+ return [
+ {
+ name: 'healthMonitors',
+ key: 'lbaas/healthmonitors',
+ responseKey: 'healthmonitor',
+ },
+ {
+ name: 'listeners',
+ key: 'lbaas/listeners',
+ responseKey: 'listener',
+ },
+ {
+ name: 'loadbalancers',
+ key: 'lbaas/loadbalancers',
+ responseKey: 'loadbalancer',
+ },
+ {
+ name: 'pools',
+ key: 'lbaas/pools',
+ responseKey: 'pool',
+ extendOperations: [
+ {
+ name: 'batchUpdateMembers',
+ key: 'members',
+ method: 'put',
+ },
+ ],
+ subResources: [
+ {
+ key: 'members',
+ },
+ ],
+ },
+ ];
+ }
+}
+
+const octaviaClient = new OctaviaClient();
+export default octaviaClient;
diff --git a/src/client/placement/index.js b/src/client/placement/index.js
new file mode 100644
index 00000000..7e522830
--- /dev/null
+++ b/src/client/placement/index.js
@@ -0,0 +1,42 @@
+// Copyright 2021 99cloud
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import Base from '../client/base';
+import { placementBase } from '../client/constants';
+
+class PlacementClient extends Base {
+ get baseUrl() {
+ return placementBase();
+ }
+
+ get resources() {
+ return [
+ {
+ name: 'resourceProviders',
+ key: 'resource_providers',
+ subResources: [
+ {
+ key: 'inventories',
+ },
+ ],
+ },
+ {
+ key: 'traits',
+ },
+ ];
+ }
+}
+
+const placementClient = new PlacementClient();
+export default placementClient;
diff --git a/src/client/skyline/index.js b/src/client/skyline/index.js
new file mode 100644
index 00000000..9d4055cb
--- /dev/null
+++ b/src/client/skyline/index.js
@@ -0,0 +1,123 @@
+// Copyright 2021 99cloud
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import Base from '../client/base';
+import { skylineBase } from '../client/constants';
+
+class SkylineClient extends Base {
+ get baseUrl() {
+ return skylineBase();
+ }
+
+ get resources() {
+ return [
+ {
+ key: 'contrib',
+ isResource: false,
+ extendOperations: [
+ {
+ key: 'domains',
+ },
+ {
+ key: 'regions',
+ },
+ {
+ name: 'keystoneEndpoints',
+ key: 'keystone_endpoints',
+ },
+ ],
+ },
+ {
+ key: 'extension',
+ isResource: false,
+ extendOperations: [
+ {
+ key: 'servers',
+ },
+ {
+ name: 'recycleServers',
+ key: 'recycle_servers',
+ },
+ {
+ key: 'volumes',
+ },
+ {
+ name: 'volumeSnapshots',
+ key: 'volume_snapshots',
+ },
+ {
+ key: 'ports',
+ },
+ ],
+ },
+ {
+ name: 'policies',
+ key: 'policies',
+ extendOperations: [
+ {
+ name: 'check',
+ key: 'check',
+ isDetail: false,
+ },
+ ],
+ },
+ {
+ name: '',
+ key: '',
+ isResource: false,
+ extendOperations: [
+ {
+ key: 'login',
+ method: 'post',
+ },
+ {
+ key: 'logout',
+ method: 'post',
+ },
+ {
+ key: 'profile',
+ },
+ {
+ name: 'switchProject',
+ method: 'post',
+ generate: (projectId, domainId) => {
+ const url = `switch_project/${projectId}`;
+ const data = {
+ project_id: projectId,
+ project_domain_id: domainId,
+ };
+ const params = {
+ project_domain_id: domainId,
+ };
+ return this.request.post(url, data, params);
+ },
+ },
+ ],
+ },
+ {
+ key: 'setting',
+ responseKey: 'setting',
+ extendOperations: [
+ {
+ key: 'list',
+ url: () => 'settings',
+ },
+ ],
+ },
+ ];
+ }
+}
+
+const skylineClient = new SkylineClient();
+export default skylineClient;
diff --git a/src/components/Form/index.jsx b/src/components/Form/index.jsx
index e90950b2..a28234eb 100644
--- a/src/components/Form/index.jsx
+++ b/src/components/Form/index.jsx
@@ -104,6 +104,11 @@ export default class BaseForm extends React.Component {
return '';
}
+ get currentUser() {
+ const { user } = this.props.rootStore || {};
+ return user || {};
+ }
+
get isAdminPage() {
const { pathname = '' } = this.props.location || {};
return isAdminPage(pathname);
@@ -114,11 +119,11 @@ export default class BaseForm extends React.Component {
}
get currentProjectId() {
- return globals.user.project.id;
+ return this.props.rootStore.projectId;
}
get currentProjectName() {
- return globals.user.project.name;
+ return this.props.rootStore.projectName;
}
getUrl(path, adminStr) {
@@ -258,7 +263,7 @@ export default class BaseForm extends React.Component {
this.responseError = err;
this.showNotice && Notify.errorWithDetail(err, this.errorText);
// eslint-disable-next-line no-console
- console.log(err);
+ console.log('err', err);
if (callback && isFunction(callback)) {
callback(false, true);
}
diff --git a/src/components/FormItem/NetworkSelectTable/index.jsx b/src/components/FormItem/NetworkSelectTable/index.jsx
index 9ac5d90f..d6926e91 100644
--- a/src/components/FormItem/NetworkSelectTable/index.jsx
+++ b/src/components/FormItem/NetworkSelectTable/index.jsx
@@ -44,7 +44,7 @@ export default class NetworkSelectTable extends Component {
}
get currentProjectId() {
- return globals.user.project.id;
+ return this.props.rootStore.projectId;
}
get hasAdminRole() {
diff --git a/src/components/FormItem/VolumeSelectTable/index.jsx b/src/components/FormItem/VolumeSelectTable/index.jsx
index 9e17f95e..75020075 100644
--- a/src/components/FormItem/VolumeSelectTable/index.jsx
+++ b/src/components/FormItem/VolumeSelectTable/index.jsx
@@ -34,7 +34,7 @@ export default class VolumeSelectTable extends Component {
}
get currentProjectId() {
- return globals.user.project.id;
+ return this.props.rootStore.projectId;
}
get hasAdminRole() {
diff --git a/src/components/Layout/GlobalHeader/AvatarDropdown.jsx b/src/components/Layout/GlobalHeader/AvatarDropdown.jsx
index 1acff3fc..dfb42d6b 100644
--- a/src/components/Layout/GlobalHeader/AvatarDropdown.jsx
+++ b/src/components/Layout/GlobalHeader/AvatarDropdown.jsx
@@ -126,7 +126,7 @@ class AvatarDropdown extends React.Component {
diff --git a/src/components/Layout/GlobalHeader/RightContent.jsx b/src/components/Layout/GlobalHeader/RightContent.jsx
index 08ad311c..db87ee6f 100644
--- a/src/components/Layout/GlobalHeader/RightContent.jsx
+++ b/src/components/Layout/GlobalHeader/RightContent.jsx
@@ -28,7 +28,7 @@ const gotoConsole = (type, props) => {
};
const GlobalHeaderRight = (props) => {
- const { isAdminPage = false, rootStore: { hasAdminRole = false } = {} } =
+ const { isAdminPage = false, rootStore: { hasAdminPageRole = false } = {} } =
props;
let linkRender = null;
if (isAdminPage) {
@@ -41,7 +41,7 @@ const GlobalHeaderRight = (props) => {
{t('Console')}
);
- } else if (hasAdminRole) {
+ } else if (hasAdminPageRole) {
linkRender = (