Skip to content

Commit 1f4cfbf

Browse files
committed
Adding deployment-replication dashboard
1 parent 995975f commit 1f4cfbf

File tree

12 files changed

+560
-52
lines changed

12 files changed

+560
-52
lines changed

dashboard/assets.go

Lines changed: 44 additions & 44 deletions
Large diffs are not rendered by default.

dashboard/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"devDependencies": {
1717
"react-scripts": "1.1.4"
1818
},
19-
"proxy": "https://192.168.140.211:8528",
19+
"proxy": "https://192.168.140.212:8528",
2020
"scripts": {
2121
"start": "react-scripts start",
2222
"build": "react-scripts build",

dashboard/src/App.js

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import React, { Component } from 'react';
22
import ReactTimeout from 'react-timeout';
3-
import DeploymentOperator from './deployment/DeploymentOperator.js';
4-
import StorageOperator from './storage/StorageOperator.js';
5-
import NoOperator from './NoOperator.js';
6-
import Loading from './util/Loading.js';
7-
import api, { IsUnauthorized } from './api/api.js';
3+
import DeploymentOperator from './deployment/DeploymentOperator';
4+
import DeploymentReplicationOperator from './replication/DeploymentReplicationOperator';
5+
import StorageOperator from './storage/StorageOperator';
6+
import NoOperator from './NoOperator';
7+
import Loading from './util/Loading';
8+
import api, { IsUnauthorized } from './api/api';
89
import { Container, Segment, Message } from 'semantic-ui-react';
9-
import { withAuth } from './auth/Auth.js';
10+
import { withAuth } from './auth/Auth';
1011

1112
const PodInfoView = ({pod, namespace}) => (
1213
<Segment basic>
@@ -17,11 +18,14 @@ const PodInfoView = ({pod, namespace}) => (
1718
</Segment>
1819
);
1920

20-
const OperatorsView = ({error, deployment, storage, pod, namespace}) => {
21+
const OperatorsView = ({error, deployment, deploymentReplication, storage, pod, namespace}) => {
2122
const podInfoView = (<PodInfoView pod={pod} namespace={namespace}/>);
2223
if (deployment) {
2324
return (<DeploymentOperator podInfoView={podInfoView} error={error}/>);
2425
}
26+
if (deploymentReplication) {
27+
return (<DeploymentReplicationOperator podInfoView={podInfoView} error={error}/>);
28+
}
2529
if (storage) {
2630
return (<StorageOperator podInfoView={podInfoView} error={error}/>);
2731
}
@@ -67,6 +71,7 @@ class App extends Component {
6771
return <OperatorsView
6872
error={this.state.error}
6973
deployment={this.state.operators.deployment}
74+
deploymentReplication={this.state.operators.deployment_replication}
7075
storage={this.state.operators.storage}
7176
pod={this.state.operators.pod}
7277
namespace={this.state.operators.namespace}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import ReactTimeout from 'react-timeout';
2+
import React, { Component } from 'react';
3+
import api, { IsUnauthorized } from '../api/api.js';
4+
import Loading from '../util/Loading.js';
5+
import styled from 'react-emotion';
6+
import { Loader } from 'semantic-ui-react';
7+
import { withAuth } from '../auth/Auth.js';
8+
9+
const LoaderBox = styled('span')`
10+
float: right;
11+
width: 0;
12+
padding-right: 1em;
13+
margin-right: 1em;
14+
margin-top: 1em;
15+
max-width: 0;
16+
display: inline-block;
17+
`;
18+
19+
class DeploymentReplicationDetails extends Component {
20+
state = {
21+
loading: true,
22+
error: undefined
23+
};
24+
25+
componentDidMount() {
26+
this.reloadDeploymentReplications();
27+
}
28+
29+
reloadDeploymentReplications = async() => {
30+
try {
31+
this.setState({
32+
loading: true
33+
});
34+
const result = await api.get(`/api/deployment-replication/${this.props.name}`);
35+
this.setState({
36+
replication: result,
37+
loading: false,
38+
error: undefined
39+
});
40+
} catch (e) {
41+
this.setState({
42+
loading: false,
43+
error: e.message
44+
});
45+
if (IsUnauthorized(e)) {
46+
this.props.doLogout();
47+
return;
48+
}
49+
}
50+
this.props.setTimeout(this.reloadDeploymentReplications, 5000);
51+
}
52+
53+
render() {
54+
const dr = this.state.replication;
55+
if (!dr) {
56+
return (<Loading/>);
57+
}
58+
return (
59+
<div>
60+
<LoaderBox><Loader size="mini" active={this.state.loading} inline/></LoaderBox>
61+
<div>TODO</div>
62+
</div>
63+
);
64+
}
65+
}
66+
67+
export default ReactTimeout(withAuth(DeploymentReplicationDetails));
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { Icon, Loader, Popup, Table } from 'semantic-ui-react';
2+
import { Link } from "react-router-dom";
3+
import api, { IsUnauthorized } from '../api/api.js';
4+
import CommandInstruction from '../util/CommandInstruction.js';
5+
import Loading from '../util/Loading.js';
6+
import React, { Component } from 'react';
7+
import ReactTimeout from 'react-timeout';
8+
import styled from 'react-emotion';
9+
import { withAuth } from '../auth/Auth.js';
10+
11+
const LoaderBox = styled('span')`
12+
float: right;
13+
width: 0;
14+
padding-right: 1em;
15+
max-width: 0;
16+
display: inline-block;
17+
`;
18+
19+
const HeaderView = ({loading}) => (
20+
<Table.Header>
21+
<Table.Row>
22+
<Table.HeaderCell>State</Table.HeaderCell>
23+
<Table.HeaderCell>Name</Table.HeaderCell>
24+
<Table.HeaderCell>
25+
Actions
26+
<LoaderBox><Loader size="mini" active={loading} inline/></LoaderBox>
27+
</Table.HeaderCell>
28+
</Table.Row>
29+
</Table.Header>
30+
);
31+
32+
const RowView = ({name, mode, stateColor, deleteCommand, describeCommand}) => (
33+
<Table.Row>
34+
<Table.Cell>
35+
<Popup trigger={<Icon name={(stateColor==="green") ? "check" : "bell"} color={stateColor}/>}>
36+
{getStateColorDescription(stateColor)}
37+
</Popup>
38+
</Table.Cell>
39+
<Table.Cell>
40+
<Link to={`/deployment-replication/${name}`}>
41+
{name}
42+
</Link>
43+
</Table.Cell>
44+
<Table.Cell>
45+
<CommandInstruction
46+
trigger={<Icon link name="zoom"/>}
47+
command={describeCommand}
48+
title="Describe deployment replication"
49+
description="To get more information on the state of this deployment replication, run:"
50+
/>
51+
<span style={{"float":"right"}}>
52+
<CommandInstruction
53+
trigger={<Icon link name="trash"/>}
54+
command={deleteCommand}
55+
title="Delete deployment replication"
56+
description="To delete this deployment replication, run:"
57+
/>
58+
</span>
59+
</Table.Cell>
60+
</Table.Row>
61+
);
62+
63+
const ListView = ({items, loading}) => (
64+
<Table striped celled>
65+
<HeaderView loading={loading}/>
66+
<Table.Body>
67+
{
68+
(items) ? items.map((item) =>
69+
<RowView
70+
key={item.name}
71+
name={item.name}
72+
namespace={item.namespace}
73+
stateColor={item.state_color}
74+
deleteCommand={createDeleteCommand(item.name, item.namespace)}
75+
describeCommand={createDescribeCommand(item.name, item.namespace)}
76+
/>) : <p>No items</p>
77+
}
78+
</Table.Body>
79+
</Table>
80+
);
81+
82+
const EmptyView = () => (<div>No deployment replications</div>);
83+
84+
function createDeleteCommand(name, namespace) {
85+
return `kubectl delete ArangoDeploymentReplication -n ${namespace} ${name}`;
86+
}
87+
88+
function createDescribeCommand(name, namespace) {
89+
return `kubectl describe ArangoDeploymentReplication -n ${namespace} ${name}`;
90+
}
91+
92+
function getStateColorDescription(stateColor) {
93+
switch (stateColor) {
94+
case "green":
95+
return "Replication has been configured.";
96+
case "yellow":
97+
return "Replication is being configured.";
98+
case "red":
99+
return "The replication is in a bad state and manual intervention is likely needed.";
100+
default:
101+
return "State is not known.";
102+
}
103+
}
104+
105+
class DeploymentReplicationList extends Component {
106+
state = {
107+
items: null,
108+
error: null,
109+
loading: true
110+
};
111+
112+
componentDidMount() {
113+
this.reloadDeploymentReplications();
114+
}
115+
116+
reloadDeploymentReplications = async() => {
117+
try {
118+
this.setState({loading: true});
119+
const result = await api.get('/api/deployment-replication');
120+
this.setState({
121+
items: result.replications,
122+
loading: false,
123+
error: null
124+
});
125+
} catch (e) {
126+
this.setState({error: e.message, loading: false});
127+
if (IsUnauthorized(e)) {
128+
this.props.doLogout();
129+
return;
130+
}
131+
}
132+
this.props.setTimeout(this.reloadDeploymentReplications, 5000);
133+
}
134+
135+
render() {
136+
const items = this.state.items;
137+
if (!items) {
138+
return (<Loading/>);
139+
}
140+
if (items.length === 0) {
141+
return (<EmptyView/>);
142+
}
143+
return (<ListView items={items} loading={this.state.loading}/>);
144+
}
145+
}
146+
147+
export default ReactTimeout(withAuth(DeploymentReplicationList));
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import React, { Component } from 'react';
2+
import LogoutContext from '../auth/LogoutContext.js';
3+
import DeploymentReplicationDetails from './DeploymentReplicationDetails.js';
4+
import DeploymentReplicationList from './DeploymentReplicationList.js';
5+
import { Header, Menu, Message, Segment } from 'semantic-ui-react';
6+
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
7+
import styled from 'react-emotion';
8+
9+
const StyledMenu = styled(Menu)`
10+
width: 15rem !important;
11+
@media (max-width: 768px) {
12+
width: 10rem !important;
13+
}
14+
`;
15+
16+
const StyledContentBox = styled('div')`
17+
margin-left: 15rem;
18+
@media (max-width: 768px) {
19+
margin-left: 10rem;
20+
}
21+
`;
22+
23+
const ListView = () => (
24+
<div>
25+
<Header dividing>
26+
ArangoDeploymentReplication resources
27+
</Header>
28+
<DeploymentReplicationList/>
29+
</div>
30+
);
31+
32+
const DetailView = ({match}) => (
33+
<div>
34+
<Header dividing>
35+
ArangoDeploymentReplication {match.params.name}
36+
</Header>
37+
<DeploymentReplicationDetails name={match.params.name}/>
38+
</div>
39+
);
40+
41+
class DeploymentReplicationOperator extends Component {
42+
render() {
43+
return (
44+
<Router>
45+
<div>
46+
<LogoutContext.Consumer>
47+
{doLogout =>
48+
<StyledMenu fixed="left" vertical>
49+
<Menu.Item>
50+
<Link to="/">Deployment replications</Link>
51+
</Menu.Item>
52+
<Menu.Item position="right" onClick={() => doLogout()}>
53+
Logout
54+
</Menu.Item>
55+
</StyledMenu>
56+
}
57+
</LogoutContext.Consumer>
58+
<StyledContentBox>
59+
<Segment basic clearing>
60+
<div>
61+
<Route exact path="/" component={ListView} />
62+
<Route path="/deployment-replication/:name" component={DetailView} />
63+
</div>
64+
</Segment>
65+
{this.props.podInfoView}
66+
{(this.props.error) ? <Segment basic><Message error content={this.props.error}/></Segment> : null}
67+
</StyledContentBox>
68+
</div>
69+
</Router>
70+
);
71+
}
72+
}
73+
74+
export default DeploymentReplicationOperator;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
apiVersion: v1
2+
kind: Service
3+
metadata:
4+
name: {{ .DeploymentReplication.OperatorDeploymentName }}
5+
namespace: {{ .DeploymentReplication.Operator.Namespace }}
6+
labels:
7+
name: {{ .DeploymentReplication.OperatorDeploymentName }}
8+
app: arango-deployment-replication-operator
9+
spec:
10+
ports:
11+
- name: server
12+
port: 8528
13+
protocol: TCP
14+
targetPort: 8528
15+
selector:
16+
name: {{ .DeploymentReplication.OperatorDeploymentName }}
17+
app: arango-deployment-replication-operator
18+
role: leader
19+
type: {{ .DeploymentReplication.Operator.ServiceType }}

0 commit comments

Comments
 (0)