diff --git a/src/client/client/constants.js b/src/client/client/constants.js
index ec061be8..b92757dd 100644
--- a/src/client/client/constants.js
+++ b/src/client/client/constants.js
@@ -36,6 +36,7 @@ export const endpointVersionMap = {
zun: 'v1',
magnum: 'v1',
designate: 'v2',
+ masakari: 'v1',
};
export const endpointsDefault = {
@@ -77,6 +78,7 @@ export const barbicanBase = () => getOpenstackEndpoint('barbican');
export const zunBase = () => getOpenstackEndpoint('zun');
export const magnumBase = () => getOpenstackEndpoint('magnum');
export const designateBase = () => getOpenstackEndpoint('designate');
+export const masakariBase = () => getOpenstackEndpoint('masakari');
export const ironicOriginEndpoint = () => getOriginEndpoint('ironic');
export const vpnEndpoint = () => getOriginEndpoint('neutron_vpn');
diff --git a/src/client/index.js b/src/client/index.js
index 5155ac5f..137f5894 100644
--- a/src/client/index.js
+++ b/src/client/index.js
@@ -28,6 +28,7 @@ import manila from './manila';
import barbican from './barbican';
import zun from './zun';
import magnum from './magnum';
+import masakari from './masakari';
const client = {
skyline,
@@ -46,6 +47,7 @@ const client = {
barbican,
zun,
magnum,
+ masakari,
};
window.client = client;
diff --git a/src/client/masakari/index.js b/src/client/masakari/index.js
new file mode 100644
index 00000000..11153da3
--- /dev/null
+++ b/src/client/masakari/index.js
@@ -0,0 +1,45 @@
+// 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 { masakariBase } from '../client/constants';
+
+export class MasakariClient extends Base {
+ get baseUrl() {
+ return masakariBase();
+ }
+
+ get resources() {
+ return [
+ {
+ name: 'segments',
+ key: 'segments',
+ responseKey: 'segment',
+ subResources: [
+ {
+ key: 'hosts',
+ responseKey: 'host',
+ },
+ ],
+ },
+ {
+ name: 'notifications',
+ key: 'notifications',
+ responseKey: 'notification',
+ },
+ ];
+ }
+}
+
+const masakariClient = new MasakariClient();
+export default masakariClient;
+
diff --git a/src/pages/basic/routes/index.js b/src/pages/basic/routes/index.js
index ff58ee7e..5efa288f 100644
--- a/src/pages/basic/routes/index.js
+++ b/src/pages/basic/routes/index.js
@@ -51,13 +51,16 @@ const Share = lazy(() =>
import(/* webpackChunkName: "share" */ 'pages/share/App')
);
const ContainerInfra = lazy(() =>
- import(/* webpackChunkName: "container-infra" */ 'pages/container-infra/App')
+import(/* webpackChunkName: "container-infra" */ 'pages/container-infra/App')
);
const ContainerService = lazy(() =>
- import(/* webpackChunkName: "Container" */ 'pages/container-service/App')
+import(/* webpackChunkName: "Container" */ 'pages/container-service/App')
);
const E404 = lazy(() =>
- import(/* webpackChunkName: "E404" */ 'pages/base/containers/404')
+import(/* webpackChunkName: "E404" */ 'pages/base/containers/404')
+);
+const InstanceHA = lazy(() =>
+ import(/* webpackChunkName: "Inctance-HA" */ 'pages/ha/App')
);
const PATH = '/';
@@ -116,6 +119,10 @@ export default [
path: `/container-service`,
component: ContainerService,
},
+ {
+ path: `/ha`,
+ component: InstanceHA,
+ },
{ path: '*', component: E404 },
],
},
diff --git a/src/pages/ha/App.jsx b/src/pages/ha/App.jsx
new file mode 100644
index 00000000..5bf31315
--- /dev/null
+++ b/src/pages/ha/App.jsx
@@ -0,0 +1,19 @@
+// 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 renderRoutes from 'utils/RouterConfig';
+
+import routes from './routes';
+
+const App = (props) => renderRoutes(routes, props);
+
+export default App;
diff --git a/src/pages/ha/containers/Hosts/Detail/BaseDetail.jsx b/src/pages/ha/containers/Hosts/Detail/BaseDetail.jsx
new file mode 100644
index 00000000..4e997a35
--- /dev/null
+++ b/src/pages/ha/containers/Hosts/Detail/BaseDetail.jsx
@@ -0,0 +1,64 @@
+// 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 React from 'react';
+import Base from 'containers/BaseDetail';
+import { inject, observer } from 'mobx-react';
+import { Link } from 'react-router-dom';
+
+export class BaseDetail extends Base {
+ get leftCards() {
+ const cards = [this.baseInfoCard];
+ return cards;
+ }
+
+ get baseInfoCard() {
+ const options = [
+ {
+ label: t('UUID'),
+ dataIndex: 'uuid',
+ },
+ {
+ label: t('Failover Segment'),
+ dataIndex: 'failover_segment_id',
+ render: (value, row) => {
+ return {row.failover_segment.name}
+ }
+ },
+ {
+ label: t('Reserved'),
+ dataIndex: 'reserved',
+ valueRender: 'yesNo'
+ },
+ {
+ label: t('On Maintenance'),
+ dataIndex: 'on_maintenance',
+ valueRender: 'yesNo'
+ },
+ {
+ label: t('Type'),
+ dataIndex: 'type',
+ },
+ {
+ label: t('Control Attribute'),
+ dataIndex: 'control_attributes',
+ },
+ ];
+
+ return {
+ title: t('Host Detail'),
+ options,
+ };
+ }
+}
+
+export default inject('rootStore')(observer(BaseDetail));
diff --git a/src/pages/ha/containers/Hosts/Detail/index.jsx b/src/pages/ha/containers/Hosts/Detail/index.jsx
new file mode 100644
index 00000000..2653b87a
--- /dev/null
+++ b/src/pages/ha/containers/Hosts/Detail/index.jsx
@@ -0,0 +1,73 @@
+// 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 { parse } from 'qs';
+import { inject, observer } from 'mobx-react';
+import Base from 'containers/TabDetail';
+import globalHostStore from 'src/stores/masakari/hosts';
+import BaseDetail from './BaseDetail';
+import actionConfigs from '../actions';
+
+export class HostsDetail extends Base {
+ init() {
+ this.store = globalHostStore;
+ }
+
+ get name() {
+ return t('Host Detail');
+ }
+
+ get listUrl() {
+ return this.getRoutePath('masakariHosts');
+ }
+
+ get policy() {
+ return 'capsule:get_one_all_projects';
+ }
+
+ get actionConfigs() {
+ return actionConfigs;
+ }
+
+ get titleValue() {
+ return parse(this.routing.location.search.slice(1)).uuid;
+ }
+
+ get detailInfos() {
+ return [
+ {
+ title: t('Name'),
+ dataIndex: 'name',
+ },
+ ];
+ }
+
+ updateFetchParams = (params) => {
+ const hostId = parse(this.routing.location.search.slice(1));
+ return {
+ id: params.id,
+ uuid: hostId.uuid
+ };
+ };
+
+ get tabs() {
+ return [
+ {
+ title: t('Detail'),
+ key: 'general_info',
+ component: BaseDetail,
+ },
+ ];
+ }
+}
+
+export default inject('rootStore')(observer(HostsDetail));
diff --git a/src/pages/ha/containers/Hosts/actions/Delete.jsx b/src/pages/ha/containers/Hosts/actions/Delete.jsx
new file mode 100644
index 00000000..835766f8
--- /dev/null
+++ b/src/pages/ha/containers/Hosts/actions/Delete.jsx
@@ -0,0 +1,53 @@
+// 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 { ConfirmAction } from 'containers/Action';
+import globalHostStore from 'src/stores/masakari/hosts';
+
+export default class Delete extends ConfirmAction {
+ get id() {
+ return 'delete';
+ }
+
+ get title() {
+ return t('Delete');
+ }
+
+ get actionName() {
+ return t('delete host');
+ }
+
+ get isDanger() {
+ return true;
+ }
+
+ get isAsyncAction() {
+ return true;
+ }
+
+ policy = 'instance:delete';
+
+ allowedCheckFunction = () => true;
+
+ confirmContext = (data) => {
+ const name = this.getName(data);
+ return t('Are you sure to {action} (Host: {name})?', {
+ action: this.actionNameDisplay || this.title,
+ name,
+ });
+ };
+
+ onSubmit = (item) => {
+ const { uuid, failover_segment_id } = item || this.item;
+ return globalHostStore.delete({ segment_id: failover_segment_id, host_id: uuid });
+ };
+}
diff --git a/src/pages/ha/containers/Hosts/actions/Update.jsx b/src/pages/ha/containers/Hosts/actions/Update.jsx
new file mode 100644
index 00000000..44012849
--- /dev/null
+++ b/src/pages/ha/containers/Hosts/actions/Update.jsx
@@ -0,0 +1,83 @@
+// 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 { inject, observer } from 'mobx-react';
+import { ModalAction } from "src/containers/Action";
+import globalHostStore from 'src/stores/masakari/hosts';
+
+export class Update extends ModalAction {
+ init() {
+ this.store = globalHostStore;
+ }
+
+ static id = 'UpdateHost';
+
+ static title = t('Update');
+
+ get name() {
+ return t('Update');
+ }
+
+ static policy = 'baremetal:port:Update';
+
+ static allowed = () => Promise.resolve(true);
+
+ get defaultValue() {
+ return {
+ ...this.item
+ };
+ }
+
+ get formItems() {
+ return [
+ {
+ name: 'name',
+ label: t('Host Name'),
+ type: 'input',
+ disabled: true,
+ required: true
+ },
+ {
+ name: 'reserved',
+ label: t('Reserved'),
+ type: 'switch',
+ checkedText: '',
+ uncheckedText: ''
+ },
+ {
+ name: 'type',
+ label: t('Type'),
+ type: 'input',
+ required: true
+ },
+ {
+ name: 'control_attributes',
+ label: t('Control Attribute'),
+ type: 'input',
+ required: true
+ },
+ {
+ name: 'on_maintenance',
+ label: t('On Maintenance'),
+ type: 'switch',
+ checkedText: '',
+ uncheckedText: ''
+ }
+ ]
+ }
+
+ onSubmit = (values) => {
+ return this.store.update(this.item.failover_segment_id, this.item.uuid, { 'host': values });
+ }
+}
+
+export default inject('rootStore')(observer(Update));
\ No newline at end of file
diff --git a/src/pages/ha/containers/Hosts/actions/index.jsx b/src/pages/ha/containers/Hosts/actions/index.jsx
new file mode 100644
index 00000000..b397b58d
--- /dev/null
+++ b/src/pages/ha/containers/Hosts/actions/index.jsx
@@ -0,0 +1,28 @@
+// 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 Update from './Update';
+import Delete from './Delete';
+
+const actionConfigs = {
+ rowActions: {
+ firstAction: Update,
+ moreActions: [
+ {
+ action: Delete,
+ },
+ ],
+ },
+ batchActions: [Delete]
+};
+
+export default actionConfigs;
diff --git a/src/pages/ha/containers/Hosts/index.jsx b/src/pages/ha/containers/Hosts/index.jsx
new file mode 100644
index 00000000..07aa986b
--- /dev/null
+++ b/src/pages/ha/containers/Hosts/index.jsx
@@ -0,0 +1,126 @@
+// 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 React from 'react';
+import { observer, inject } from 'mobx-react';
+import Base from 'containers/List';
+import actionConfigs from './actions';
+import globalHostStore, { HostStore } from 'src/stores/masakari/hosts';
+import { Link } from 'react-router-dom';
+
+export class Hosts extends Base {
+ init() {
+ this.store = globalHostStore;
+ this.downloadStore = new HostStore();
+ }
+
+ get policy() {
+ if (this.isAdminPage) {
+ return 'os_compute_api:servers:index:get_all_tenants';
+ }
+ return 'os_compute_api:servers:index';
+ }
+
+ get name() {
+ return t('hosts');
+ }
+
+ get defaultSortKey() {
+ return 'updated_at';
+ }
+
+ get actionConfigs() {
+ return actionConfigs;
+ }
+
+ get rowKey() {
+ return 'uuid';
+ }
+
+ get searchFilters() {
+ return [
+ {
+ label: t('Segment ID'),
+ name: 'id',
+ },
+ {
+ label: t('Type'),
+ name: 'type',
+ },
+ {
+ label: t('On Maintenance'),
+ name: 'on_maintenance',
+ },
+ {
+ label: t('Reserved'),
+ name: 'reserved',
+ },
+ ...(this.isAdminPage
+ ? [
+ {
+ label: t('Project Name'),
+ name: 'project_name',
+ },
+ ]
+ : []),
+ ];
+ }
+
+ getColumns = () => [
+ {
+ title: t('Name'),
+ dataIndex: 'name',
+ render: (value, row) => {
+ const path = this.getRoutePath('masakariHostDetail', { id: row.failover_segment_id }, { uuid: row.uuid });
+ return {value};
+ }
+ },
+ {
+ title: t('UUID'),
+ dataIndex: 'uuid',
+ isHideable: true,
+ },
+ {
+ title: t('Reserved'),
+ dataIndex: 'reserved',
+ isHideable: true,
+ valueRender: 'yesNo'
+ },
+ {
+ title: t('Type'),
+ dataIndex: 'type',
+ isHideable: true,
+ },
+ {
+ title: t('Control Attribute'),
+ dataIndex: 'control_attributes',
+ isHideable: true
+ },
+ {
+ title: t('On Maintenance'),
+ dataIndex: 'on_maintenance',
+ isHideable: true,
+ valueRender: 'yesNo'
+ },
+ {
+ title: t('Failover Segment'),
+ dataIndex: 'failover_segment',
+ isHideable: true,
+ render: (value, row) => {
+ return {row.failover_segment.name}
+ }
+ }
+ ];
+
+}
+
+export default inject('rootStore')(observer(Hosts));
diff --git a/src/pages/ha/containers/Notifications/Detail/BaseDetail.jsx b/src/pages/ha/containers/Notifications/Detail/BaseDetail.jsx
new file mode 100644
index 00000000..3f9c2c07
--- /dev/null
+++ b/src/pages/ha/containers/Notifications/Detail/BaseDetail.jsx
@@ -0,0 +1,80 @@
+// 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 'containers/BaseDetail';
+import { inject, observer } from 'mobx-react';
+
+export class BaseDetail extends Base {
+ get leftCards() {
+ const cards = [this.baseInfoCard, this.payloadCard];
+ return cards;
+ }
+
+ get baseInfoCard() {
+ const options = [
+ {
+ label: t('ID'),
+ dataIndex: 'id',
+ },
+ {
+ label: t('Host'),
+ dataIndex: 'source_host_uuid',
+ copyable: true
+ },
+ {
+ label: t('Generated Time'),
+ dataIndex: 'generated_time',
+ valueRender: 'toLocalTime'
+ },
+ {
+ label: t('Created At'),
+ dataIndex: 'created_at',
+ valueRender: 'toLocalTime'
+ },
+ {
+ label: t('Updated At'),
+ dataIndex: 'updated_at',
+ valueRender: 'toLocalTime'
+ },
+ ];
+
+ return {
+ title: t('Notification Detail'),
+ options,
+ };
+ }
+
+ get payloadCard() {
+ const options = [
+ {
+ label: t('Event'),
+ dataIndex: 'event'
+ },
+ {
+ label: t('Instance UUID'),
+ dataIndex: 'instance_uuid'
+ },
+ {
+ label: t('VIR Domain Event'),
+ dataIndex: 'vir_domain_event'
+ }
+ ];
+
+ return {
+ title: t('Payload'),
+ sourceData: this.detailData.payload,
+ options
+ }
+ }
+}
+
+export default inject('rootStore')(observer(BaseDetail));
diff --git a/src/pages/ha/containers/Notifications/Detail/index.jsx b/src/pages/ha/containers/Notifications/Detail/index.jsx
new file mode 100644
index 00000000..e2b1aa8f
--- /dev/null
+++ b/src/pages/ha/containers/Notifications/Detail/index.jsx
@@ -0,0 +1,59 @@
+// 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 { inject, observer } from 'mobx-react';
+import Base from 'containers/TabDetail';
+import BaseDetail from './BaseDetail';
+import globalNotificationStore from 'stores/masakari/notifications';
+
+export class NotificationsDetail extends Base {
+ init() {
+ this.store = globalNotificationStore;
+ }
+
+ get name() {
+ return t('Host Detail');
+ }
+
+ get listUrl() {
+ return this.getRoutePath('masakariNotifications');
+ }
+
+ get policy() {
+ return 'capsule:get_one_all_projects';
+ }
+
+ get detailInfos() {
+ return [
+ {
+ title: t('Type'),
+ dataIndex: 'type',
+ },
+ {
+ title: t('Status'),
+ dataIndex: 'status',
+ },
+ ];
+ }
+
+ get tabs() {
+ return [
+ {
+ title: t('Detail'),
+ key: 'baseDetail',
+ component: BaseDetail,
+ },
+ ];
+ }
+}
+
+export default inject('rootStore')(observer(NotificationsDetail));
diff --git a/src/pages/ha/containers/Notifications/index.jsx b/src/pages/ha/containers/Notifications/index.jsx
new file mode 100644
index 00000000..10b7e8d0
--- /dev/null
+++ b/src/pages/ha/containers/Notifications/index.jsx
@@ -0,0 +1,87 @@
+// 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 React from 'react';
+import { observer, inject } from 'mobx-react';
+import Base from 'containers/List';
+import globalNotificationStore, { NotificationStore } from 'stores/masakari/notifications';
+import { Link } from 'react-router-dom';
+
+export class Notifications extends Base {
+ init() {
+ this.store = globalNotificationStore;
+ this.downloadStore = new NotificationStore();
+ }
+
+ get policy() {
+ if (this.isAdminPage) {
+ return 'os_compute_api:servers:index:get_all_tenants';
+ }
+ return 'os_compute_api:servers:index';
+ }
+
+ get name() {
+ return t('segments');
+ }
+
+ get defaultSortKey() {
+ return 'updated_at';
+ }
+
+ get searchFilters() {
+ return [
+ {
+ label: t('Host'),
+ name: 'source_host_uuid',
+ },
+ {
+ label: t('UUID'),
+ name: 'notification_uuid',
+ },
+ ]
+ }
+
+ getColumns = () => [
+ {
+ title: t('UUID'),
+ dataIndex: 'notification_uuid',
+ render: (value) => {
+ const path = this.getRoutePath("masakariNotificationDetail", {id: value});
+ return {value}
+ },
+ isHideable: true
+ },
+ {
+ title: t('Host'),
+ dataIndex: 'source_host_uuid',
+ isHideable: true,
+ },
+ {
+ title: t('Type'),
+ dataIndex: 'type',
+ isHideable: true,
+ },
+ {
+ title: t('Status'),
+ dataIndex: 'status',
+ isHideable: true
+ },
+ {
+ title: t('Payload'),
+ dataIndex: 'payload',
+ isHideable: true,
+ render: (value) => Object.keys(value).map(it =>{it}: {value[it]}
)
+ },
+ ];
+}
+
+export default inject('rootStore')(observer(Notifications));
diff --git a/src/pages/ha/containers/Segments/Detail/BaseDetail.jsx b/src/pages/ha/containers/Segments/Detail/BaseDetail.jsx
new file mode 100644
index 00000000..6352eb8f
--- /dev/null
+++ b/src/pages/ha/containers/Segments/Detail/BaseDetail.jsx
@@ -0,0 +1,56 @@
+// 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 'containers/BaseDetail';
+import { inject, observer } from 'mobx-react';
+
+export class BaseDetail extends Base {
+ get leftCards() {
+ const cards = [this.baseInfoCard];
+ return cards;
+ }
+
+ get baseInfoCard() {
+ const options = [
+ {
+ label: t('Recovery Method'),
+ dataIndex: 'recovery_method',
+ },
+ {
+ label: t('Service Type'),
+ dataIndex: 'service_type',
+ },
+ {
+ label: t('Enabled'),
+ dataIndex: 'enabled',
+ valueRender: 'yesNo'
+ },
+ {
+ label: t('Created At'),
+ dataIndex: 'created_at',
+ valueRender: 'toLocalTime',
+ },
+ {
+ label: t('Updated At'),
+ dataIndex: 'updated_at',
+ valueRender: 'toLocalTime',
+ },
+ ];
+
+ return {
+ title: t('Capsule Type'),
+ options,
+ };
+ }
+}
+
+export default inject('rootStore')(observer(BaseDetail));
diff --git a/src/pages/ha/containers/Segments/Detail/HostDetail.jsx b/src/pages/ha/containers/Segments/Detail/HostDetail.jsx
new file mode 100644
index 00000000..46228aea
--- /dev/null
+++ b/src/pages/ha/containers/Segments/Detail/HostDetail.jsx
@@ -0,0 +1,85 @@
+// 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 React from 'react';
+import Base from 'containers/List';
+import { inject, observer } from 'mobx-react';
+import globalHostStore, { HostStore } from 'stores/masakari/hosts';
+import { Link } from 'react-router-dom';
+
+export class HostDetail extends Base {
+ init() {
+ this.store = globalHostStore;
+ this.downloadStore = new HostStore();
+ }
+
+ get policy() {
+ return 'volume:get_all';
+ }
+
+ get name() {
+ return t('Host');
+ }
+
+ getColumns = () => {
+ const columns = [
+ {
+ title: t('Name'),
+ dataIndex: 'name',
+ render: (value, row) => {
+ const path = this.getRoutePath('masakariHostDetail', { id: row.failover_segment_id }, { uuid: row.uuid });
+ return {value};
+ }
+ },
+ {
+ title: t('UUID'),
+ dataIndex: 'uuid',
+ isHideable: true,
+ },
+ {
+ title: t('Reserved'),
+ dataIndex: 'reserved',
+ isHideable: true,
+ valueRender: 'yesNo'
+ },
+ {
+ title: t('Type'),
+ dataIndex: 'type',
+ isHideable: true,
+ },
+ {
+ title: t('Control Attribute'),
+ dataIndex: 'control_attributes',
+ isHideable: true
+ },
+ {
+ title: t('On Maintenance'),
+ dataIndex: 'on_maintenance',
+ isHideable: true,
+ valueRender: 'yesNo'
+ }
+ ,
+ {
+ title: t('Failover Segment'),
+ dataIndex: 'failover_segment',
+ isHideable: true,
+ render: (value, row) => {
+ return {row.failover_segment.name}
+ }
+ }
+ ];
+ return columns;
+ };
+
+}
+
+export default inject('rootStore')(observer(HostDetail));
diff --git a/src/pages/ha/containers/Segments/Detail/index.jsx b/src/pages/ha/containers/Segments/Detail/index.jsx
new file mode 100644
index 00000000..619e4949
--- /dev/null
+++ b/src/pages/ha/containers/Segments/Detail/index.jsx
@@ -0,0 +1,70 @@
+// 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 { inject, observer } from 'mobx-react';
+import Base from 'containers/TabDetail';
+import { SegmentStore } from 'src/stores/masakari/segments';
+import BaseDetail from './BaseDetail';
+import actionConfigs from '../actions';
+import HostDetail from '../../Hosts';
+
+export class SegmentsDetail extends Base {
+ init() {
+ this.store = new SegmentStore;
+ }
+
+ get name() {
+ return t('Segment Detail');
+ }
+
+ get listUrl() {
+ return this.getRoutePath('masakariSegments');
+ }
+
+ get policy() {
+ return 'capsule:get_one_all_projects';
+ }
+
+ get actionConfigs() {
+ return actionConfigs;
+ }
+
+ get detailInfos() {
+ return [
+ {
+ title: t('Name'),
+ dataIndex: 'name',
+ },
+ {
+ title: t('Description'),
+ dataIndex: 'description',
+ },
+ ];
+ }
+
+ get tabs() {
+ return [
+ {
+ title: t('Detail'),
+ key: 'general_info',
+ component: BaseDetail,
+ },
+ {
+ title: t('Hosts'),
+ key: 'host',
+ component: HostDetail,
+ },
+ ];
+ }
+}
+
+export default inject('rootStore')(observer(SegmentsDetail));
diff --git a/src/pages/ha/containers/Segments/actions/AddHost.jsx b/src/pages/ha/containers/Segments/actions/AddHost.jsx
new file mode 100644
index 00000000..8d52ad02
--- /dev/null
+++ b/src/pages/ha/containers/Segments/actions/AddHost.jsx
@@ -0,0 +1,131 @@
+// 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 { inject, observer } from 'mobx-react';
+import { ModalAction } from "src/containers/Action";
+import globalHostStore from 'src/stores/masakari/hosts';
+import globalComputeHostStore from 'src/stores/nova/compute-host';
+
+export class AddHost extends ModalAction {
+ init() {
+ this.store = globalHostStore;
+ this.state = {
+ host: []
+ };
+ this.getHostList();
+ }
+
+ static id = 'AddHost';
+
+ static title = t('Add Host');
+
+ get name() {
+ return t('Add Host');
+ }
+
+ static policy = 'baremetal:port:create';
+
+ static allowed = () => Promise.resolve(true);
+
+ async getHostList() {
+ const response = await globalComputeHostStore.fetchList({ binary: 'nova-compute' });
+ const hostList = await globalHostStore.fetchList();
+ let flag = false;
+
+ if (hostList.length < 1) {
+ this.setState({
+ host: response
+ });
+ }
+ else {
+ response.forEach(newHost => {
+ for (let i = 0; i < hostList.length; i++) {
+ if (hostList[i].name === newHost.host) {
+ flag = true;
+ }
+ }
+ if (!flag) {
+ this.setState({
+ host: [...this.state.host, newHost]
+ });
+ }
+ flag = false;
+ });
+ }
+ }
+
+ get getHostName() {
+ return (this.state.host || []).map((it) => ({
+ value: it.host,
+ label: it.host,
+ }));
+ }
+
+ get defaultValue() {
+ return {
+ segment_name: this.item.name,
+ reserved: false,
+ on_maintenance: false
+ };
+ }
+
+ get formItems() {
+ return [
+ {
+ name: 'segment_name',
+ label: t('Segment Name'),
+ type: 'input',
+ disabled: true
+ },
+ {
+ name: 'name',
+ label: t('Host Name'),
+ type: 'select',
+ options: this.getHostName,
+ required: true
+ },
+ {
+ name: 'reserved',
+ label: t('Reserved'),
+ type: 'switch',
+ checkedText: '',
+ uncheckedText: ''
+ },
+ {
+ name: 'type',
+ label: t('Type'),
+ type: 'input',
+ required: true
+ },
+ {
+ name: 'control_attributes',
+ label: t('Control Attributes'),
+ type: 'input',
+ required: true
+ },
+ {
+ name: 'on_maintenance',
+ label: t('On Maintenance'),
+ type: 'switch',
+ checkedText: '',
+ uncheckedText: ''
+ },
+ ]
+ }
+
+ onSubmit = (values) => {
+ const { segment_name, ...submitData } = values;
+ return this.store.create(this.item.uuid, { 'host': { ...submitData } });
+ }
+}
+
+export default inject('rootStore')(observer(AddHost));
\ No newline at end of file
diff --git a/src/pages/ha/containers/Segments/actions/Delete.jsx b/src/pages/ha/containers/Segments/actions/Delete.jsx
new file mode 100644
index 00000000..dfa8b1af
--- /dev/null
+++ b/src/pages/ha/containers/Segments/actions/Delete.jsx
@@ -0,0 +1,54 @@
+// 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 { ConfirmAction } from 'containers/Action';
+import globalSegmentStore from 'src/stores/masakari/segments';
+
+export default class Delete extends ConfirmAction {
+ get id() {
+ return 'delete';
+ }
+
+ get title() {
+ return t('Delete');
+ }
+
+ get actionName() {
+ return t('delete segments');
+ }
+
+ get isDanger() {
+ return true;
+ }
+
+ get isAsyncAction() {
+ return true;
+ }
+
+ policy = 'os_compute_api:os-deferred-delete:force';
+
+ allowedCheckFunction = () => true;
+
+ confirmContext = (data) => {
+ const name = this.getName(data);
+ return t('Are you sure to {action} (Segment: {name})?', {
+ action: this.actionNameDisplay || this.title,
+ name,
+ });
+ };
+
+ onSubmit = (item) => {
+ const { uuid } = item || this.item;
+ let id = uuid;
+ return globalSegmentStore.delete({ id });
+ };
+}
diff --git a/src/pages/ha/containers/Segments/actions/StepCreate/StepHost.jsx b/src/pages/ha/containers/Segments/actions/StepCreate/StepHost.jsx
new file mode 100644
index 00000000..6e89c275
--- /dev/null
+++ b/src/pages/ha/containers/Segments/actions/StepCreate/StepHost.jsx
@@ -0,0 +1,189 @@
+// 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 React from 'react';
+import { inject, observer } from 'mobx-react';
+import Base from 'components/Form';
+import globalHostStore from 'src/stores/masakari/hosts';
+import globalComputeHostStore from 'src/stores/nova/compute-host';
+import { Input, Switch } from 'antd';
+
+export class StepHost extends Base {
+ init() {
+ this.store = globalHostStore;
+ this.state = {
+ host: [],
+ hostLoading: true,
+ ...this.state
+ };
+
+ this.getHostList();
+ }
+
+ get title() {
+ return 'StepHost';
+ }
+
+ get name() {
+ return 'StepHost';
+ }
+
+ get isStep() {
+ return true;
+ }
+
+ allowed = () => Promise.resolve();
+
+ async getHostList() {
+
+ const response = await globalComputeHostStore.fetchList({ binary: 'nova-compute' });
+ const hostList = await globalHostStore.fetchList();
+ let flag = false;
+
+ if (hostList.length < 1) {
+ this.setState({
+ host: response
+ });
+ }
+ else {
+ response.forEach(newHost => {
+ for (let i = 0; i < hostList.length; i++) {
+ if (hostList[i].name === newHost.host) {
+ flag = true;
+ }
+ }
+ if (!flag) {
+ this.setState({ host: [...this.state.host, newHost] });
+ }
+ flag = false;
+ });
+ }
+
+ const hostMap = Object.fromEntries(
+ this.state.host.map(host => [host.id, host])
+ )
+ this.setState({ hostMap: hostMap, hostLoading: false });
+ }
+
+ get getHostName() {
+ return (this.state.host || []).map((it) => ({
+ value: it.host,
+ label: it.host,
+ }));
+ }
+
+ get formItems() {
+ const columns = [
+ { title: t('Name'), dataIndex: 'host' },
+ { title: t('Zone'), dataIndex: 'zone' },
+ {
+ title: t('Updated'),
+ dataIndex: 'updated_at',
+ valueRender: 'toLocalTime'
+ },
+ {
+ name: 'reserved',
+ title: t('Reserved'),
+ dataIndex: 'reserved',
+ required: true,
+ render: (reserved, row) => (
+ {
+ this.setState(prevState => {
+ const host = prevState.hostMap
+ host[row.id].reserved = checked
+ return { hostMap: host }
+ })
+ }}
+ />
+ )
+ },
+ {
+ name: 'type',
+ title: t('Type'),
+ dataIndex: 'type',
+ required: true,
+ render: (type, row) => (
+ {
+ const { value } = e.target
+ this.setState(prevState => {
+ const host = prevState.hostMap
+ host[row.id].type = value
+ return { hostMap: host }
+ })
+ }}
+ />
+ )
+ },
+ {
+ name: 'control_attributes',
+ title: t('Control Attributes'),
+ dataIndex: 'control_attributes',
+ render: (control_attribute, row) => (
+ {
+ const { value } = e.target
+ this.setState(prevState => {
+ const host = prevState.hostMap
+ host[row.id].control_attributes = value
+ return { hostMap: host }
+ })
+ }}
+ />
+ )
+ },
+ {
+ name: 'on_maintenance',
+ title: t('On Maintenance'),
+ dataIndex: 'on_maintenance',
+ render: (maintain, row) => (
+ {
+ this.setState(prevState => {
+ const host = prevState.hostMap
+ host[row.id].on_maintenance = checked
+ return { hostMap: host }
+ })
+ }}
+ />
+ )
+ }
+ ]
+
+ return [
+ {
+ name: 'name',
+ label: t('Host Name'),
+ type: 'select-table',
+ required: true,
+ data: this.state.host,
+ isMulti: true,
+ onRow: () => { },
+ columns: columns,
+ isLoading: this.state.hostLoading,
+ filterParams: [
+ { label: t('Name'), name: 'host' },
+ { label: t('Zone'), name: 'zone' },
+ ]
+ }
+ ]
+ }
+}
+
+export default inject('rootStore')(observer(StepHost));
diff --git a/src/pages/ha/containers/Segments/actions/StepCreate/StepSegment.jsx b/src/pages/ha/containers/Segments/actions/StepCreate/StepSegment.jsx
new file mode 100644
index 00000000..758bd2d8
--- /dev/null
+++ b/src/pages/ha/containers/Segments/actions/StepCreate/StepSegment.jsx
@@ -0,0 +1,76 @@
+// 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 { inject, observer } from 'mobx-react';
+import Base from 'components/Form';
+
+export class StepSegment extends Base {
+
+ get title() {
+ return 'StepSegment';
+ }
+
+ get name() {
+ return 'StepSegment';
+ }
+
+ get isStep() {
+ return true;
+ }
+
+ get defaultValue() {
+ return {
+ recovery_method: 'auto',
+ service_type: 'compute',
+ };
+ }
+
+ allowed = () => Promise.resolve();
+
+ get formItems() {
+ return [
+ {
+ name: 'segment_name',
+ label: t('Segment Name'),
+ type: 'input',
+ required: true
+ },
+ {
+ name: 'recovery_method',
+ label: t('Recovery Method'),
+ type: 'select',
+ options: [
+ { label: t('auto'), value: 'auto' },
+ { label: t('auto_priority'), value: 'auto_priority' },
+ { label: t('reserved_host'), value: 'reserved_host' },
+ { label: t('rh_priority'), value: 'rh_priority' },
+ ],
+ required: true
+ },
+ {
+ name: 'service_type',
+ label: t('Service Type'),
+ type: 'input',
+ required: true,
+ disabled: true
+ },
+ {
+ name: 'description',
+ label: t('Description'),
+ type: 'textarea',
+ rows: 4
+ },
+ ];
+ }
+}
+
+export default inject('rootStore')(observer(StepSegment));
diff --git a/src/pages/ha/containers/Segments/actions/StepCreate/index.jsx b/src/pages/ha/containers/Segments/actions/StepCreate/index.jsx
new file mode 100644
index 00000000..652a24dd
--- /dev/null
+++ b/src/pages/ha/containers/Segments/actions/StepCreate/index.jsx
@@ -0,0 +1,151 @@
+// 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 { inject, observer } from 'mobx-react';
+import { StepAction } from 'containers/Action';
+import StepSegment from './StepSegment';
+import globalSegmentStore from 'src/stores/masakari/segments';
+import StepHost from './StepHost';
+import React from 'react';
+import { Button, Modal } from 'antd';
+import { toJS } from 'mobx';
+import { QuestionCircleFilled } from '@ant-design/icons';
+import stylesConfirm from 'src/components/Confirm/index.less';
+import globalHostStore from 'src/stores/masakari/hosts';
+import Notify from 'src/components/Notify';
+
+export class StepCreate extends StepAction {
+ static id = 'instance-ha-create';
+
+ static title = t('Create Segment');
+
+ static path = '/ha/segments-admin/create-step-admin';
+
+ init() {
+ this.store = globalHostStore;
+ this.state = { btnIsLoading: false, ...this.state };
+ }
+
+ static policy = 'get_images';
+
+ static allowed() {
+ return Promise.resolve(true);
+ }
+
+ get name() {
+ return t('Create Segment');
+ }
+
+ get listUrl() {
+ return this.getRoutePath('masakariSegments');
+ }
+
+ get hasConfirmStep() {
+ return false;
+ }
+
+ next() {
+ this.currentRef.current.wrappedInstance.checkFormInput((values) => {
+ this.updateData(values);
+
+ if (this.state.current === 0) {
+ this.setState({ btnIsLoading: true })
+ const { segment_name, recovery_method, service_type, description } = this.state.data;
+
+ globalSegmentStore.create({ segment: { name: segment_name, recovery_method, service_type, description } }).then(item => {
+ this.setState({ extra: toJS({ createdSegmentId: item.segment.uuid }) }, () => {
+ this.setState((prev) => ({ current: prev.current + 1 }));
+ })
+ }, (err) => {
+ this.responseError = err;
+ const { response: { data: responseData } = {} } = err;
+ Notify.errorWithDetail(responseData, this.errorText);
+ }
+ ).finally(() => {
+ this.setState({ btnIsLoading: false })
+ });
+ }
+
+ }, () => this.setState({ btnIsLoading: false }));
+ }
+
+ getNextBtn() {
+ const { current } = this.state;
+ if (current >= this.steps.length - 1) {
+ return null;
+ }
+ const { title } = this.steps[current + 1];
+ return (
+
+ );
+ }
+
+ getPrevBtn() {
+ const { current } = this.state;
+ if (current === 0) {
+ return null;
+ }
+ const preTitle = this.steps[current - 1].title;
+ return (
+
+ );
+ }
+
+ prev() {
+ this.currentRef.current.wrappedInstance.checkFormInput(
+ this.updateDataOnPrev,
+ this.updateDataOnPrev
+ );
+ globalSegmentStore.delete({ id: this.state.extra.createdSegmentId });
+ }
+
+ onClickCancel = () => {
+ if (this.state.current !== 0) {
+ Modal.confirm({
+ title: 'Confirm',
+ icon: ,
+ content: 'Segment will be deleted. Are you sure want to cancel this created segment?',
+ okText: 'Confirm',
+ cancelText: 'Cancel',
+ loading: true,
+ onOk: () => {
+ return globalSegmentStore.delete({ id: this.state.extra.createdSegmentId }).finally(() => this.routing.push(this.listUrl));
+ }
+ });
+ } else {
+ this.routing.push(this.listUrl);
+ }
+ }
+
+ get steps() {
+ return [
+ { title: t('Create Segment'), component: StepSegment },
+ { title: t('Add Host'), component: StepHost }
+ ];
+ }
+
+ onSubmit = (values) => {
+ const { name } = values;
+ return Promise.resolve(
+ name.selectedRows.forEach(item => {
+ const { binary, forced_down, host, id, state, status, updated_at, zone, ...hostData } = item;
+ this.store.create(this.state.extra.createdSegmentId, { 'host': { name: host, ...hostData } })
+ })
+ );
+ };
+}
+
+export default inject('rootStore')(observer(StepCreate));
diff --git a/src/pages/ha/containers/Segments/actions/Update.jsx b/src/pages/ha/containers/Segments/actions/Update.jsx
new file mode 100644
index 00000000..c83eeb96
--- /dev/null
+++ b/src/pages/ha/containers/Segments/actions/Update.jsx
@@ -0,0 +1,74 @@
+// 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 { inject, observer } from 'mobx-react';
+import { ModalAction } from "src/containers/Action";
+import globalSegmentStore from 'src/stores/masakari/segments';
+
+export class Update extends ModalAction {
+ init() {
+ this.store = globalSegmentStore;
+ }
+
+ static id = 'UpdateSegment';
+
+ static title = t('Update');
+
+ get name() {
+ return t('Update Segment');
+ }
+
+ static policy = 'baremetal:port:Update';
+
+ static allowed = () => Promise.resolve(true);
+
+ get defaultValue() {
+ return {
+ ...this.item
+ };
+ }
+
+ get formItems() {
+ return [
+ {
+ name: 'name',
+ label: t('Segment Name'),
+ type: 'input',
+ required: true
+ },
+ {
+ name: 'recovery_method',
+ label: t('Recovery Method'),
+ type: 'select',
+ options: [
+ { label: t('auto'), value: 'auto' },
+ { label: t('auto_priority'), value: 'auto_priority' },
+ { label: t('reserved_host'), value: 'reserved_host' },
+ { label: t('rh_priority'), value: 'rh_priority' },
+ ],
+ required: true
+ },
+ {
+ name: 'description',
+ label: t('Description'),
+ type: 'textarea',
+ rows: 4
+ },
+ ]
+ }
+
+ onSubmit = (values) => {
+ return this.store.update(this.item.uuid, { 'segment': values });
+ }
+}
+
+export default inject('rootStore')(observer(Update));
\ No newline at end of file
diff --git a/src/pages/ha/containers/Segments/actions/index.jsx b/src/pages/ha/containers/Segments/actions/index.jsx
new file mode 100644
index 00000000..0a3c6fc7
--- /dev/null
+++ b/src/pages/ha/containers/Segments/actions/index.jsx
@@ -0,0 +1,34 @@
+// 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 StepCreate from './StepCreate';
+import Update from './Update';
+import Delete from './Delete';
+import AddHost from './AddHost';
+
+const actionConfigs = {
+ rowActions: {
+ firstAction: Update,
+ moreActions: [
+ {
+ action: AddHost,
+ },
+ {
+ action: Delete,
+ },
+ ],
+ },
+ batchActions: [Delete],
+ primaryActions: [StepCreate]
+};
+
+export default actionConfigs;
diff --git a/src/pages/ha/containers/Segments/index.jsx b/src/pages/ha/containers/Segments/index.jsx
new file mode 100644
index 00000000..e3b9ed2d
--- /dev/null
+++ b/src/pages/ha/containers/Segments/index.jsx
@@ -0,0 +1,97 @@
+// 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 { observer, inject } from 'mobx-react';
+import Base from 'containers/List';
+import actionConfigs from './actions';
+import globalSegmentStore, { SegmentStore } from 'stores/masakari/segments';
+
+export class Segments extends Base {
+ init() {
+ this.store = globalSegmentStore;
+ this.downloadStore = new SegmentStore();
+ }
+
+ get policy() {
+ if (this.isAdminPage) {
+ return 'os_compute_api:servers:index:get_all_tenants';
+ }
+ return 'os_compute_api:servers:index';
+ }
+
+ get name() {
+ return t('segments');
+ }
+
+ get defaultSortKey() {
+ return 'updated_at';
+ }
+
+ get actionConfigs() {
+ return actionConfigs;
+ }
+
+ get searchFilters() {
+ return [
+ {
+ label: t('Recovery Method'),
+ name: 'recovery_method',
+ },
+ {
+ label: t('Service Type'),
+ name: 'service_type',
+ },
+ ...(this.isAdminPage
+ ? [
+ {
+ label: t('Project Name'),
+ name: 'project_name',
+ },
+ ]
+ : []),
+ ];
+ }
+
+ get rowKey() {
+ return 'uuid';
+ }
+
+ getColumns = () => [
+ {
+ title: t('Name'),
+ dataIndex: 'name',
+ routeName: this.getRouteName('masakariSegmentDetail'),
+ },
+ {
+ title: t('UUID'),
+ dataIndex: 'uuid',
+ isHideable: true,
+ },
+ {
+ title: t('Recovery Method'),
+ dataIndex: 'recovery_method',
+ isHideable: true,
+ },
+ {
+ title: t('Service Type'),
+ dataIndex: 'service_type',
+ isHideable: true,
+ },
+ {
+ title: t('Description'),
+ dataIndex: 'description',
+ isHideable: true
+ }
+ ];
+}
+
+export default inject('rootStore')(observer(Segments));
diff --git a/src/pages/ha/routes/index.js b/src/pages/ha/routes/index.js
new file mode 100644
index 00000000..5fe1bd78
--- /dev/null
+++ b/src/pages/ha/routes/index.js
@@ -0,0 +1,40 @@
+// 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 BaseLayout from 'layouts/Basic';
+import E404 from 'pages/base/containers/404';
+import Segments from '../containers/Segments';
+import Hosts from '../containers/Hosts';
+import Notifications from '../containers/Notifications';
+import SegmentsDetail from '../containers/Segments/Detail';
+import HostsDetail from '../containers/Hosts/Detail';
+import NotificationsDetail from '../containers/Notifications/Detail';
+import StepCreate from '../containers/Segments/actions/StepCreate';
+
+const PATH = '/ha';
+export default [
+ {
+ path: PATH,
+ component: BaseLayout,
+ routes: [
+ { path: `${PATH}/segments-admin`, component: Segments, exact: true },
+ { path: `${PATH}/segments-admin`, component: Segments, exact: true },
+ { path: `${PATH}/segments-admin/create-step-admin`, component: StepCreate, exact: true },
+ { path: `${PATH}/segments-admin/detail/:id`, component: SegmentsDetail, exact: true },
+ { path: `${PATH}/hosts-admin`, component: Hosts, exact: true },
+ { path: `${PATH}/hosts-admin/detail/:id`, component: HostsDetail, exact: true, },
+ { path: `${PATH}/notifications-admin`, component: Notifications, exact: true },
+ { path: `${PATH}/notifications-admin/detail/:id`, component: NotificationsDetail, exact: true },
+ { path: '*', component: E404 },
+ ],
+ },
+];
diff --git a/src/stores/masakari/hosts.js b/src/stores/masakari/hosts.js
new file mode 100644
index 00000000..0c64d55b
--- /dev/null
+++ b/src/stores/masakari/hosts.js
@@ -0,0 +1,68 @@
+import Base from 'stores/base';
+import client from 'client';
+import { action } from 'mobx';
+
+export class HostStore extends Base {
+
+ get client() {
+ return client.masakari.segments.hosts;
+ }
+
+ get segmentClient() {
+ return client.masakari.segments;
+ }
+
+ get isSubResource() {
+ return true;
+ }
+
+ detailFetchByClient(resourceParams) {
+ return this.client.show(resourceParams.id, resourceParams.uuid);
+ }
+
+ get paramsFunc() {
+ return (params) => {
+ const { id } = params;
+
+ return { segment_id: id };
+ };
+ }
+
+ async listFetchByClient(params) {
+ const result = [];
+
+ if (params.segment_id) {
+ await this.client.list(params.segment_id).then(response => {
+ response.hosts.map(item => result.push(item))
+ });
+ } else {
+ await this.segmentClient.list().then(async segmentList => {
+ const segmentHosts = segmentList.segments.map((it) => this.client.list(it.uuid).then(getHost => getHost.hosts))
+ await Promise.all(segmentHosts).then(hostList => {
+ hostList.forEach(host => {
+ host.forEach(item => {
+ result.push(item);
+ })
+ })
+ });
+ });
+ }
+ return { hosts: result }
+ }
+
+ @action
+ async create(segment_id, newbody) {
+ return this.client.create(segment_id, newbody);
+ }
+
+ @action
+ delete = ({ segment_id, host_id }) => this.submitting(this.client.delete(segment_id, host_id));
+
+ @action
+ update(segmentId, id, body) {
+ return this.submitting(this.client.update(segmentId, id, body));
+ }
+}
+
+const globalHostStore = new HostStore();
+export default globalHostStore;
diff --git a/src/stores/masakari/notifications.js b/src/stores/masakari/notifications.js
new file mode 100644
index 00000000..6910c2eb
--- /dev/null
+++ b/src/stores/masakari/notifications.js
@@ -0,0 +1,22 @@
+import Base from 'stores/base';
+import client from 'client';
+import { action } from 'mobx';
+
+export class NotificationStore extends Base {
+ get client() {
+ return client.masakari.notifications;
+ }
+
+ @action
+ async create(newbody) {
+ return this.client.create(newbody);
+ }
+
+ @action
+ async delete({ params }, newbody) {
+ return this.client.delete(params, newbody);
+ }
+}
+
+const globalNotificationStore = new NotificationStore();
+export default globalNotificationStore;
diff --git a/src/stores/masakari/segments.js b/src/stores/masakari/segments.js
new file mode 100644
index 00000000..ca402b40
--- /dev/null
+++ b/src/stores/masakari/segments.js
@@ -0,0 +1,27 @@
+import Base from 'stores/base';
+import client from 'client';
+import { action } from 'mobx';
+
+export class SegmentStore extends Base {
+ get client() {
+ return client.masakari.segments;
+ }
+
+ @action
+ async create(newbody) {
+ return this.client.create(newbody);
+ }
+
+ @action
+ async delete({ id }) {
+ return this.client.delete(id);
+ }
+
+ @action
+ update(id, body) {
+ return this.submitting(this.client.update(id, body));
+ }
+}
+
+const globalSegmentStore = new SegmentStore();
+export default globalSegmentStore;