Add Masakari UI to Skyline-Console

Change-Id: I74913bbf1c48823ba4a9b68539a2c26582e62939
This commit is contained in:
omar asim mirza 2023-07-31 12:09:55 +00:00
parent a95c588a5d
commit 0bc19114c4
29 changed files with 1905 additions and 3 deletions

View File

@ -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');

View File

@ -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;

View File

@ -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;

View File

@ -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 },
],
},

19
src/pages/ha/App.jsx Normal file
View File

@ -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;

View File

@ -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 <Link to={this.getRoutePath('masakariSegmentDetail', { id: row.failover_segment_id })}>{row.failover_segment.name}</Link>
}
},
{
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));

View File

@ -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));

View File

@ -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 });
};
}

View File

@ -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));

View File

@ -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;

View File

@ -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 <Link to={path}>{value}</Link>;
}
},
{
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 <Link to={this.getRoutePath('masakariSegmentDetail', { id: row.failover_segment_id })}>{row.failover_segment.name}</Link>
}
}
];
}
export default inject('rootStore')(observer(Hosts));

View File

@ -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));

View File

@ -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));

View File

@ -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 <Link to={path}>{value}</Link>
},
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 =><React.Fragment><div>{it}: {value[it]}</div></React.Fragment>)
},
];
}
export default inject('rootStore')(observer(Notifications));

View File

@ -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));

View File

@ -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 <Link to={path}>{value}</Link>;
}
},
{
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 <Link to={this.getRoutePath('masakariSegmentDetail', { id: row.failover_segment_id })}>{row.failover_segment.name}</Link>
}
}
];
return columns;
};
}
export default inject('rootStore')(observer(HostDetail));

View File

@ -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));

View File

@ -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));

View File

@ -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 });
};
}

View File

@ -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) => (
<Switch
checked={reserved}
onChange={(checked) => {
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) => (
<Input
required={true}
defaultValue={type}
onChange={(e) => {
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) => (
<Input
defaultValue={control_attribute}
required={true}
onChange={(e) => {
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) => (
<Switch
checked={maintain}
onChange={(checked) => {
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));

View File

@ -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));

View File

@ -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 (
<Button type="primary" onClick={() => this.next()} loading={this.state.btnIsLoading}>
{`${t('Next')}: ${title}`}
</Button>
);
}
getPrevBtn() {
const { current } = this.state;
if (current === 0) {
return null;
}
const preTitle = this.steps[current - 1].title;
return (
<Button style={{ margin: '0 8px' }} onClick={() => this.prev()}>
{`${t('Previous')}: ${preTitle}`}
</Button>
);
}
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: <QuestionCircleFilled className={stylesConfirm.warn} />,
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));

View File

@ -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));

View File

@ -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;

View File

@ -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));

View File

@ -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 },
],
},
];

View File

@ -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;

View File

@ -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;

View File

@ -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;