1
0
Fork 0
mirror of https://github.com/arangodb/kube-arangodb.git synced 2024-12-14 11:57:37 +00:00

Adding deployment-replication dashboard

This commit is contained in:
Ewout Prangsma 2018-07-10 12:01:09 +02:00
parent 995975f20f
commit 1f4cfbf5eb
No known key found for this signature in database
GPG key ID: 4DBAD380D93D0698
12 changed files with 560 additions and 52 deletions

File diff suppressed because one or more lines are too long

View file

@ -16,7 +16,7 @@
"devDependencies": {
"react-scripts": "1.1.4"
},
"proxy": "https://192.168.140.211:8528",
"proxy": "https://192.168.140.212:8528",
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",

View file

@ -1,12 +1,13 @@
import React, { Component } from 'react';
import ReactTimeout from 'react-timeout';
import DeploymentOperator from './deployment/DeploymentOperator.js';
import StorageOperator from './storage/StorageOperator.js';
import NoOperator from './NoOperator.js';
import Loading from './util/Loading.js';
import api, { IsUnauthorized } from './api/api.js';
import DeploymentOperator from './deployment/DeploymentOperator';
import DeploymentReplicationOperator from './replication/DeploymentReplicationOperator';
import StorageOperator from './storage/StorageOperator';
import NoOperator from './NoOperator';
import Loading from './util/Loading';
import api, { IsUnauthorized } from './api/api';
import { Container, Segment, Message } from 'semantic-ui-react';
import { withAuth } from './auth/Auth.js';
import { withAuth } from './auth/Auth';
const PodInfoView = ({pod, namespace}) => (
<Segment basic>
@ -17,11 +18,14 @@ const PodInfoView = ({pod, namespace}) => (
</Segment>
);
const OperatorsView = ({error, deployment, storage, pod, namespace}) => {
const OperatorsView = ({error, deployment, deploymentReplication, storage, pod, namespace}) => {
const podInfoView = (<PodInfoView pod={pod} namespace={namespace}/>);
if (deployment) {
return (<DeploymentOperator podInfoView={podInfoView} error={error}/>);
}
if (deploymentReplication) {
return (<DeploymentReplicationOperator podInfoView={podInfoView} error={error}/>);
}
if (storage) {
return (<StorageOperator podInfoView={podInfoView} error={error}/>);
}
@ -67,6 +71,7 @@ class App extends Component {
return <OperatorsView
error={this.state.error}
deployment={this.state.operators.deployment}
deploymentReplication={this.state.operators.deployment_replication}
storage={this.state.operators.storage}
pod={this.state.operators.pod}
namespace={this.state.operators.namespace}

View file

@ -0,0 +1,67 @@
import ReactTimeout from 'react-timeout';
import React, { Component } from 'react';
import api, { IsUnauthorized } from '../api/api.js';
import Loading from '../util/Loading.js';
import styled from 'react-emotion';
import { Loader } from 'semantic-ui-react';
import { withAuth } from '../auth/Auth.js';
const LoaderBox = styled('span')`
float: right;
width: 0;
padding-right: 1em;
margin-right: 1em;
margin-top: 1em;
max-width: 0;
display: inline-block;
`;
class DeploymentReplicationDetails extends Component {
state = {
loading: true,
error: undefined
};
componentDidMount() {
this.reloadDeploymentReplications();
}
reloadDeploymentReplications = async() => {
try {
this.setState({
loading: true
});
const result = await api.get(`/api/deployment-replication/${this.props.name}`);
this.setState({
replication: result,
loading: false,
error: undefined
});
} catch (e) {
this.setState({
loading: false,
error: e.message
});
if (IsUnauthorized(e)) {
this.props.doLogout();
return;
}
}
this.props.setTimeout(this.reloadDeploymentReplications, 5000);
}
render() {
const dr = this.state.replication;
if (!dr) {
return (<Loading/>);
}
return (
<div>
<LoaderBox><Loader size="mini" active={this.state.loading} inline/></LoaderBox>
<div>TODO</div>
</div>
);
}
}
export default ReactTimeout(withAuth(DeploymentReplicationDetails));

View file

@ -0,0 +1,147 @@
import { Icon, Loader, Popup, Table } from 'semantic-ui-react';
import { Link } from "react-router-dom";
import api, { IsUnauthorized } from '../api/api.js';
import CommandInstruction from '../util/CommandInstruction.js';
import Loading from '../util/Loading.js';
import React, { Component } from 'react';
import ReactTimeout from 'react-timeout';
import styled from 'react-emotion';
import { withAuth } from '../auth/Auth.js';
const LoaderBox = styled('span')`
float: right;
width: 0;
padding-right: 1em;
max-width: 0;
display: inline-block;
`;
const HeaderView = ({loading}) => (
<Table.Header>
<Table.Row>
<Table.HeaderCell>State</Table.HeaderCell>
<Table.HeaderCell>Name</Table.HeaderCell>
<Table.HeaderCell>
Actions
<LoaderBox><Loader size="mini" active={loading} inline/></LoaderBox>
</Table.HeaderCell>
</Table.Row>
</Table.Header>
);
const RowView = ({name, mode, stateColor, deleteCommand, describeCommand}) => (
<Table.Row>
<Table.Cell>
<Popup trigger={<Icon name={(stateColor==="green") ? "check" : "bell"} color={stateColor}/>}>
{getStateColorDescription(stateColor)}
</Popup>
</Table.Cell>
<Table.Cell>
<Link to={`/deployment-replication/${name}`}>
{name}
</Link>
</Table.Cell>
<Table.Cell>
<CommandInstruction
trigger={<Icon link name="zoom"/>}
command={describeCommand}
title="Describe deployment replication"
description="To get more information on the state of this deployment replication, run:"
/>
<span style={{"float":"right"}}>
<CommandInstruction
trigger={<Icon link name="trash"/>}
command={deleteCommand}
title="Delete deployment replication"
description="To delete this deployment replication, run:"
/>
</span>
</Table.Cell>
</Table.Row>
);
const ListView = ({items, loading}) => (
<Table striped celled>
<HeaderView loading={loading}/>
<Table.Body>
{
(items) ? items.map((item) =>
<RowView
key={item.name}
name={item.name}
namespace={item.namespace}
stateColor={item.state_color}
deleteCommand={createDeleteCommand(item.name, item.namespace)}
describeCommand={createDescribeCommand(item.name, item.namespace)}
/>) : <p>No items</p>
}
</Table.Body>
</Table>
);
const EmptyView = () => (<div>No deployment replications</div>);
function createDeleteCommand(name, namespace) {
return `kubectl delete ArangoDeploymentReplication -n ${namespace} ${name}`;
}
function createDescribeCommand(name, namespace) {
return `kubectl describe ArangoDeploymentReplication -n ${namespace} ${name}`;
}
function getStateColorDescription(stateColor) {
switch (stateColor) {
case "green":
return "Replication has been configured.";
case "yellow":
return "Replication is being configured.";
case "red":
return "The replication is in a bad state and manual intervention is likely needed.";
default:
return "State is not known.";
}
}
class DeploymentReplicationList extends Component {
state = {
items: null,
error: null,
loading: true
};
componentDidMount() {
this.reloadDeploymentReplications();
}
reloadDeploymentReplications = async() => {
try {
this.setState({loading: true});
const result = await api.get('/api/deployment-replication');
this.setState({
items: result.replications,
loading: false,
error: null
});
} catch (e) {
this.setState({error: e.message, loading: false});
if (IsUnauthorized(e)) {
this.props.doLogout();
return;
}
}
this.props.setTimeout(this.reloadDeploymentReplications, 5000);
}
render() {
const items = this.state.items;
if (!items) {
return (<Loading/>);
}
if (items.length === 0) {
return (<EmptyView/>);
}
return (<ListView items={items} loading={this.state.loading}/>);
}
}
export default ReactTimeout(withAuth(DeploymentReplicationList));

View file

@ -0,0 +1,74 @@
import React, { Component } from 'react';
import LogoutContext from '../auth/LogoutContext.js';
import DeploymentReplicationDetails from './DeploymentReplicationDetails.js';
import DeploymentReplicationList from './DeploymentReplicationList.js';
import { Header, Menu, Message, Segment } from 'semantic-ui-react';
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
import styled from 'react-emotion';
const StyledMenu = styled(Menu)`
width: 15rem !important;
@media (max-width: 768px) {
width: 10rem !important;
}
`;
const StyledContentBox = styled('div')`
margin-left: 15rem;
@media (max-width: 768px) {
margin-left: 10rem;
}
`;
const ListView = () => (
<div>
<Header dividing>
ArangoDeploymentReplication resources
</Header>
<DeploymentReplicationList/>
</div>
);
const DetailView = ({match}) => (
<div>
<Header dividing>
ArangoDeploymentReplication {match.params.name}
</Header>
<DeploymentReplicationDetails name={match.params.name}/>
</div>
);
class DeploymentReplicationOperator extends Component {
render() {
return (
<Router>
<div>
<LogoutContext.Consumer>
{doLogout =>
<StyledMenu fixed="left" vertical>
<Menu.Item>
<Link to="/">Deployment replications</Link>
</Menu.Item>
<Menu.Item position="right" onClick={() => doLogout()}>
Logout
</Menu.Item>
</StyledMenu>
}
</LogoutContext.Consumer>
<StyledContentBox>
<Segment basic clearing>
<div>
<Route exact path="/" component={ListView} />
<Route path="/deployment-replication/:name" component={DetailView} />
</div>
</Segment>
{this.props.podInfoView}
{(this.props.error) ? <Segment basic><Message error content={this.props.error}/></Segment> : null}
</StyledContentBox>
</div>
</Router>
);
}
}
export default DeploymentReplicationOperator;

View file

@ -0,0 +1,19 @@
apiVersion: v1
kind: Service
metadata:
name: {{ .DeploymentReplication.OperatorDeploymentName }}
namespace: {{ .DeploymentReplication.Operator.Namespace }}
labels:
name: {{ .DeploymentReplication.OperatorDeploymentName }}
app: arango-deployment-replication-operator
spec:
ports:
- name: server
port: 8528
protocol: TCP
targetPort: 8528
selector:
name: {{ .DeploymentReplication.OperatorDeploymentName }}
app: arango-deployment-replication-operator
role: leader
type: {{ .DeploymentReplication.Operator.ServiceType }}

View file

@ -61,6 +61,39 @@ func (o *Operator) GetDeployment(name string) (server.Deployment, error) {
return nil, maskAny(server.NotFoundError)
}
// DeploymentReplicationOperator provides access to the deployment replication operator.
func (o *Operator) DeploymentReplicationOperator() server.DeploymentReplicationOperator {
return o
}
// GetDeploymentReplications returns all current deployments
func (o *Operator) GetDeploymentReplications() ([]server.DeploymentReplication, error) {
o.Dependencies.LivenessProbe.Lock()
defer o.Dependencies.LivenessProbe.Unlock()
result := make([]server.DeploymentReplication, 0, len(o.deploymentReplications))
for _, d := range o.deploymentReplications {
result = append(result, d)
}
sort.Slice(result, func(i, j int) bool {
return result[i].Name() < result[j].Name()
})
return result, nil
}
// GetDeploymentReplication returns detailed information for a deployment replication, managed by the operator, with given name
func (o *Operator) GetDeploymentReplication(name string) (server.DeploymentReplication, error) {
o.Dependencies.LivenessProbe.Lock()
defer o.Dependencies.LivenessProbe.Unlock()
for _, d := range o.deploymentReplications {
if d.Name() == name {
return d, nil
}
}
return nil, maskAny(server.NotFoundError)
}
// StorageOperator provides the local storage operator (if any)
func (o *Operator) StorageOperator() server.StorageOperator {
return o

View file

@ -0,0 +1,50 @@
//
// DISCLAIMER
//
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
//
// 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.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
// Author Ewout Prangsma
//
package replication
import (
api "github.com/arangodb/kube-arangodb/pkg/apis/replication/v1alpha"
"github.com/arangodb/kube-arangodb/pkg/server"
)
// Name returns the name of the deployment.
func (dr *DeploymentReplication) Name() string {
return dr.apiObject.Name
}
// Namespace returns the namespace that contains the deployment.
func (dr *DeploymentReplication) Namespace() string {
return dr.apiObject.Namespace
}
// StateColor determinates the state of the deployment in color codes.
func (dr *DeploymentReplication) StateColor() server.StateColor {
switch dr.status.Phase {
case api.DeploymentReplicationPhaseFailed:
return server.StateRed
}
if dr.status.Conditions.IsTrue(api.ConditionTypeConfigured) {
return server.StateGreen
}
return server.StateYellow
}

View file

@ -0,0 +1,106 @@
//
// DISCLAIMER
//
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
//
// 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.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//
// Author Ewout Prangsma
//
package server
import (
"net/http"
"github.com/gin-gonic/gin"
)
// DeploymentReplication is the API implemented by an ArangoDeploymentReplication.
type DeploymentReplication interface {
Name() string
Namespace() string
StateColor() StateColor
}
// DeploymentReplicationOperator is the API implemented by the deployment operator.
type DeploymentReplicationOperator interface {
// GetDeploymentReplications returns basic information for all deployment replications managed by the operator
GetDeploymentReplications() ([]DeploymentReplication, error)
// GetDeploymentReplication returns detailed information for a deployment replication, managed by the operator, with given name
GetDeploymentReplication(name string) (DeploymentReplication, error)
}
// DeploymentReplicationInfo is the information returned per deployment replication.
type DeploymentReplicationInfo struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
StateColor StateColor `json:"state_color"`
}
// newDeploymentReplicationInfo initializes a DeploymentReplicationInfo for the given deployment replication.
func newDeploymentReplicationInfo(dr DeploymentReplication) DeploymentReplicationInfo {
return DeploymentReplicationInfo{
Name: dr.Name(),
Namespace: dr.Namespace(),
StateColor: dr.StateColor(),
}
}
// DeploymentReplicationInfoDetails is the detailed information returned per deployment replication.
type DeploymentReplicationInfoDetails struct {
DeploymentReplicationInfo
}
// newDeploymentReplicationInfoDetails initializes a DeploymentReplicationInfoDetails for the given deployment replication.
func newDeploymentReplicationInfoDetails(dr DeploymentReplication) DeploymentReplicationInfoDetails {
result := DeploymentReplicationInfoDetails{
DeploymentReplicationInfo: newDeploymentReplicationInfo(dr),
}
return result
}
// Handle a GET /api/deployment-replication request
func (s *Server) handleGetDeploymentReplications(c *gin.Context) {
if do := s.deps.Operators.DeploymentReplicationOperator(); do != nil {
// Fetch deployment replications
repls, err := do.GetDeploymentReplications()
if err != nil {
sendError(c, err)
} else {
result := make([]DeploymentReplicationInfo, len(repls))
for i, dr := range repls {
result[i] = newDeploymentReplicationInfo(dr)
}
c.JSON(http.StatusOK, gin.H{
"replications": result,
})
}
}
}
// Handle a GET /api/deployment-replication/:name request
func (s *Server) handleGetDeploymentReplicationDetails(c *gin.Context) {
if do := s.deps.Operators.DeploymentReplicationOperator(); do != nil {
// Fetch deployments
dr, err := do.GetDeploymentReplication(c.Params.ByName("name"))
if err != nil {
sendError(c, err)
} else {
result := newDeploymentReplicationInfoDetails(dr)
c.JSON(http.StatusOK, result)
}
}
}

View file

@ -69,6 +69,8 @@ type Dependencies struct {
type Operators interface {
// Return the deployment operator (if any)
DeploymentOperator() DeploymentOperator
// Return the deployment replication operator (if any)
DeploymentReplicationOperator() DeploymentReplicationOperator
// Return the local storage operator (if any)
StorageOperator() StorageOperator
}
@ -156,6 +158,10 @@ func NewServer(cli corev1.CoreV1Interface, cfg Config, deps Dependencies) (*Serv
api.GET("/deployment", s.handleGetDeployments)
api.GET("/deployment/:name", s.handleGetDeploymentDetails)
// Deployment replication operator
api.GET("/deployment-replication", s.handleGetDeploymentReplications)
api.GET("/deployment-replication/:name", s.handleGetDeploymentReplicationDetails)
// Local storage operator
api.GET("/storage", s.handleGetLocalStorages)
api.GET("/storage/:name", s.handleGetLocalStorageDetails)

View file

@ -59,6 +59,7 @@ var (
deploymentReplicationTemplateNames = []string{
"rbac.yaml",
"deployment-replication.yaml",
"service.yaml",
}
storageTemplateNames = []string{
"rbac.yaml",