diff --git a/frontend/src/components/header/index.js b/frontend/src/components/header/index.js
index 2909b8ad..4af09fa0 100644
--- a/frontend/src/components/header/index.js
+++ b/frontend/src/components/header/index.js
@@ -8,7 +8,7 @@ import { remcalc } from '@ui/shared/functions';
import Logo from '../../resources/logo.svg';
import PropTypes from '@root/prop-types';
import Row from '@ui/components/row';
-import Tooltip from '@ui/components/tooltip';
+import Tooltip, { TooltipButton } from '@ui/components/tooltip';
import { pseudoEl, typography } from '@ui/shared/composers';
import { colors } from '@ui/shared/constants';
@@ -46,12 +46,6 @@ const StyledAvatarWrapper = styled.div`
}
`;
-const StyledTooltipWrapper = styled.div`
- right: ${remcalc(-18)};
- bottom: ${remcalc(-140)};
- position: absolute;
-`;
-
const StyledName = styled.span`
color: ${colors.base.secondaryDark};
font-size: ${remcalc(16)};
@@ -76,7 +70,7 @@ const StyledAvatar = styled(Avatar)`
const arrowPosition = {
bottom: '100%',
- right: '10%'
+ right: 18
};
const Header = ({
@@ -96,19 +90,21 @@ const Header = ({
};
const tooltipComponent = !tooltip ? null : (
-
-
-
- My Account
-
-
- Settings
-
-
- About
-
-
-
+
+
+ My Account
+
+
+ Settings
+
+
+ About
+
+
);
return (
diff --git a/frontend/src/components/service/item.js b/frontend/src/components/service/item.js
index 859f1a6f..3a8beae2 100644
--- a/frontend/src/components/service/item.js
+++ b/frontend/src/components/service/item.js
@@ -30,6 +30,7 @@ const TitleInnerContainer = styled.div`
`;
const ServiceItem = ({
+ onQuickActions=() => {},
org = '',
project = '',
service = {}
@@ -72,6 +73,10 @@ const ServiceItem = ({
Flags
);
+ const onOptionsClick = (evt) => {
+ onQuickActions(evt, service.uuid);
+ };
+
const header = isChild ? null : (
@@ -79,7 +84,9 @@ const ServiceItem = ({
{subtitle}
{description}
- …
+
+ …
+
);
@@ -113,6 +120,7 @@ const ServiceItem = ({
};
ServiceItem.propTypes = {
+ onQuickActions: React.PropTypes.func,
org: React.PropTypes.string,
project: React.PropTypes.string,
service: PropTypes.service
diff --git a/frontend/src/components/services/tooltip.js b/frontend/src/components/services/tooltip.js
new file mode 100644
index 00000000..25d7facd
--- /dev/null
+++ b/frontend/src/components/services/tooltip.js
@@ -0,0 +1,41 @@
+import React from 'react';
+import Tooltip, { TooltipButton, TooltipDivider } from '@ui/components/tooltip';
+
+const ServicesTooltip = ({
+ show,
+ position
+}) => {
+ return show ? (
+
+
+ Scale
+
+
+ Rollback
+
+
+ Reprovision
+
+
+ Transfer
+
+
+ Setup metrics
+
+
+
+ Stop
+
+
+ Delete
+
+
+ ) : null;
+};
+
+ServicesTooltip.propTypes = {
+ position: React.PropTypes.object,
+ show: React.PropTypes.bool
+};
+
+export default ServicesTooltip;
diff --git a/frontend/src/containers/services/list.js b/frontend/src/containers/services/list.js
index 84beab99..89afc75b 100644
--- a/frontend/src/containers/services/list.js
+++ b/frontend/src/containers/services/list.js
@@ -1,44 +1,93 @@
import React from 'react';
+import styled from 'styled-components';
import { connect } from 'react-redux';
import PropTypes from '@root/prop-types';
import ServiceItem from '@components/service/item';
import UnmanagedInstances from '@components/services/unmanaged-instances';
+import { toggleTooltip } from '@state/actions';
+import ServicesTooltip from '@components/services/tooltip';
import {
orgByIdSelector,
projectByIdSelector,
- servicesByProjectIdSelector
+ servicesByProjectIdSelector,
+ serviceUiTooltipSelector
} from '@state/selectors';
-const Services = (props) => {
- const {
- org = {},
- project = {},
- services = []
- } = props;
+const StyledContainer = styled.div`
+ position: relative;
+`;
- const instances = 5;
- const serviceList = services.map((service) => (
-
- ));
+class Services extends React.Component {
- return (
-
- { serviceList }
- { instances && }
-
- );
-};
+ ref(name) {
+ this._refs = this._refs || {};
+
+ return (el) => {
+ this._refs[name] = el;
+ };
+ }
+
+ render() {
+ const {
+ org = {},
+ project = {},
+ services = [],
+ toggleTooltip = (() => {}),
+ uiTooltip = {}
+ } = this.props;
+
+ const onQuickActions = (evt, service) => {
+ const list = this._refs.container;
+ const listRect = list.getBoundingClientRect();
+ const button = evt.currentTarget;
+ const buttonRect = button.getBoundingClientRect();
+
+ const position = {
+ left: buttonRect.left - listRect.left
+ + (buttonRect.right - buttonRect.left)/2,
+ top: buttonRect.bottom - listRect.top
+ };
+
+ toggleTooltip({
+ service: service,
+ position: position
+ });
+ };
+
+ const instances = 5;
+
+ const serviceList = services.map((service) => (
+
+ ));
+
+ return (
+
+ { instances &&
}
+
+
+ {serviceList}
+
+
+
+
+ );
+ }
+}
Services.propTypes = {
org: PropTypes.org,
project: PropTypes.project,
- services: React.PropTypes.arrayOf(PropTypes.service)
+ services: React.PropTypes.arrayOf(PropTypes.service),
+ toggleTooltip: React.PropTypes.func,
+ uiTooltip: React.PropTypes.object
};
const mapStateToProps = (state, {
@@ -49,9 +98,15 @@ const mapStateToProps = (state, {
}) => ({
org: orgByIdSelector(match.params.org)(state),
project: projectByIdSelector(match.params.projectId)(state),
- services: servicesByProjectIdSelector(match.params.projectId)(state)
+ services: servicesByProjectIdSelector(match.params.projectId)(state),
+ uiTooltip: serviceUiTooltipSelector(state)
+});
+
+const mapDispatchToProps = (dispatch) => ({
+ toggleTooltip: (data) => dispatch(toggleTooltip(data))
});
export default connect(
- mapStateToProps
+ mapStateToProps,
+ mapDispatchToProps
)(Services);
diff --git a/frontend/src/containers/services/topology.js b/frontend/src/containers/services/topology.js
index e00a5691..a4bd6fc9 100644
--- a/frontend/src/containers/services/topology.js
+++ b/frontend/src/containers/services/topology.js
@@ -1,26 +1,49 @@
import React from 'react';
import { connect } from 'react-redux';
+import styled from 'styled-components';
import PropTypes from '@root/prop-types';
import { TopologyGraph } from '@ui/components/topology';
+import ServicesTooltip from '@components/services/tooltip';
+
+import { toggleTooltip } from '@state/actions';
import {
orgByIdSelector,
projectByIdSelector,
- servicesForTopologySelector
+ servicesForTopologySelector,
+ serviceUiTooltipSelector
} from '@state/selectors';
+const StyledContainer = styled.div`
+ position: relative;
+`;
+
const Services = (props) => {
const {
- services = []
+ services = [],
+ toggleTooltip,
+ uiTooltip
} = props;
+ const onQuickActions = (evt, tooltipData) => {
+ toggleTooltip(tooltipData);
+ };
+
return (
-
+
+
+
+
);
};
Services.propTypes = {
- services: React.PropTypes.arrayOf(PropTypes.service)
+ services: React.PropTypes.arrayOf(PropTypes.service),
+ toggleTooltip: React.PropTypes.func,
+ uiTooltip: React.PropTypes.object
};
const mapStateToProps = (state, {
@@ -30,9 +53,15 @@ const mapStateToProps = (state, {
}) => ({
org: orgByIdSelector(match.params.org)(state),
project: projectByIdSelector(match.params.projectId)(state),
- services: servicesForTopologySelector(match.params.projectId)(state)
+ services: servicesForTopologySelector(match.params.projectId)(state),
+ uiTooltip: serviceUiTooltipSelector(state)
+});
+
+const mapDispatchToProps = (dispatch) => ({
+ toggleTooltip: (data) => dispatch(toggleTooltip(data))
});
export default connect(
- mapStateToProps
+ mapStateToProps,
+ mapDispatchToProps
)(Services);
diff --git a/frontend/src/mock-state.json b/frontend/src/mock-state.json
index 77766bfc..7d3c686a 100644
--- a/frontend/src/mock-state.json
+++ b/frontend/src/mock-state.json
@@ -627,7 +627,10 @@
"activity-feed",
"service-manifest",
"firewall"
- ]
+ ],
+ "tooltip": {
+ "show": false
+ }
},
"data": [{
"uuid": "081a792c-47e0-4439-924b-2efa9788ae9e",
diff --git a/frontend/src/state/actions.js b/frontend/src/state/actions.js
index 874b29d5..7091eab6 100644
--- a/frontend/src/state/actions.js
+++ b/frontend/src/state/actions.js
@@ -52,3 +52,5 @@ export const switchMonitorViewPage =
createAction(`${APP}/SWITCH_MONITOR_VIEW_PAGE`);
export const handleNewProject =
createAction(`${APP}/CREATE_NEW_PROJECT`);
+export const toggleTooltip =
+ createAction(`${APP}/TOGGLE_QUICK_ACTIONS_TOOLTIP`);
diff --git a/frontend/src/state/reducers/services.js b/frontend/src/state/reducers/services.js
index d266d7af..0ed692d1 100644
--- a/frontend/src/state/reducers/services.js
+++ b/frontend/src/state/reducers/services.js
@@ -1,5 +1,9 @@
import { handleActions } from 'redux-actions';
-import { addMetric, toggleServiceCollapsed } from '@state/actions';
+import {
+ addMetric,
+ toggleServiceCollapsed,
+ toggleTooltip
+} from '@state/actions';
import { toggleCollapsed } from '@state/reducers/common';
const getMetrics = (stateMetrics, addMetric, metric) => {
@@ -42,5 +46,31 @@ export default handleActions({
action.payload.service,
action.payload.metric
)
- })
+ }),
+ [toggleTooltip.toString()]: (state, action) => {
+
+ const {
+ position,
+ service
+ } = action.payload;
+
+ const show = state.ui.tooltip.service !== service;
+ const tooltip = show ? {
+ show: true,
+ position: {
+ ...position
+ },
+ service: service
+ } : {
+ show: false
+ };
+
+ return {
+ ...state,
+ ui: {
+ ...state.ui,
+ tooltip: tooltip
+ }
+ };
+ }
}, {});
diff --git a/frontend/src/state/selectors.js b/frontend/src/state/selectors.js
index ca7eeb64..b8bd6370 100644
--- a/frontend/src/state/selectors.js
+++ b/frontend/src/state/selectors.js
@@ -7,6 +7,7 @@ const account = (state) => get(state, 'account.data', {});
const accountUi = (state) => get(state, 'account.ui', {});
const orgUiSections = (state) => get(state, 'orgs.ui.sections', []);
const projectUiSections = (state) => get(state, 'projects.ui.sections', []);
+const serviceUiTooltip = (state) => get(state, 'services.ui.tooltip', []);
const serviceUiSections = (state) => get(state, 'services.ui.sections', []);
const orgs = (state) => get(state, 'orgs.data', []);
const orgUI = (state) => get(state, 'orgs.ui', []);
@@ -249,5 +250,6 @@ export {
members as membersSelector,
peopleByProjectId as peopleByProjectIdSelector,
projectsUI as projectUISelector,
- projectIndexById as projectIndexByIdSelect
+ projectIndexById as projectIndexByIdSelect,
+ serviceUiTooltip as serviceUiTooltipSelector
};
diff --git a/ui/src/components/tooltip/button.js b/ui/src/components/tooltip/button.js
new file mode 100644
index 00000000..59ca5452
--- /dev/null
+++ b/ui/src/components/tooltip/button.js
@@ -0,0 +1,36 @@
+import styled from 'styled-components';
+import { colors } from '../../shared/constants';
+import { unitcalc } from '../../shared/functions';
+import Button from '../button';
+
+const TooltipButton = styled(Button)`
+ width: 100%;
+ padding: ${unitcalc(1)} ${unitcalc(3)};
+ background-color: ${colors.base.white};
+ color: ${colors.base.secondary};
+ text-align: left;
+ border: none;
+ box-shadow: none;
+
+ &:focus {
+ background-color: ${colors.base.white};
+ color: ${colors.base.primary};
+ border: none;
+ }
+
+ &:hover {
+ background-color: ${colors.base.white};
+ color: ${colors.base.primary};
+ border: none;
+ }
+
+ &:active,
+ &:active:hover,
+ &:active:focus {
+ background-color: ${colors.base.white};
+ color: ${colors.base.primary};
+ border: none;
+ }
+`;
+
+export default TooltipButton;
diff --git a/ui/src/components/tooltip/index.js b/ui/src/components/tooltip/index.js
index a0575bad..e3c0f4b4 100644
--- a/ui/src/components/tooltip/index.js
+++ b/ui/src/components/tooltip/index.js
@@ -1,42 +1,60 @@
-import { remcalc } from '../../shared/functions';
+import { remcalc, unitcalc } from '../../shared/functions';
import {
+ absolutePosition,
baseBox,
pseudoEl,
Baseline,
- moveZ
+ moveZ,
+ getMeasurement
} from '../../shared/composers';
-import { colors } from '../../shared/constants';
+import { boxes, colors, tooltipShadow } from '../../shared/constants';
import styled from 'styled-components';
import React from 'react';
const ItemPadder = 9;
const WrapperPadder = 24;
+const StyledContainer = styled.div`
+ ${(props) => absolutePosition(props)}
+`;
+
const StyledList = styled.ul`
background: ${colors.base.white};
+ box-sizing: border-box;
color: ${colors.base.text};
display: inline-block;
font-family: sans-serif;
list-style-type: none;
margin: 0;
- padding: 0;
- min-width: ${remcalc(200)};
+ padding: ${unitcalc(2)} 0;
+ /*min-width: ${remcalc(200)};*/
+
+ position: absolute;
+ top: 4px;
+ ${(props) => {
+ return props.arrowPosition.left ?
+ `left: -${getMeasurement(props.arrowPosition.left)}` :
+ props.arrowPosition.right ?
+ `right: -${getMeasurement(props.arrowPosition.right)}` : null;
+ }};
${props => props.styles}
- ${baseBox()}
-
+ ${baseBox({
+ shadow: tooltipShadow
+ })}
+
${moveZ({
amount: 1
})}
- & > * {
+ /*& > * {
padding: ${remcalc(ItemPadder)} ${remcalc(WrapperPadder)};
&:hover {
background: ${colors.base.grey};
}
- }
+ }*/
&:after, &:before {
border: solid transparent;
@@ -49,14 +67,14 @@ const StyledList = styled.ul`
&:after {
border-color: rgba(255, 255, 255, 0);
border-bottom-color: ${colors.base.white};
- border-width: ${remcalc(10)};
- margin-left: ${remcalc(-10)};
+ border-width: ${remcalc(3)};
+ margin-left: ${remcalc(-3)};
}
&:before {
border-color: rgba(216, 216, 216, 0);
- border-bottom-color: ${colors.base.greyDark};
- border-width: ${remcalc(12)};
- margin-left: ${remcalc(-12)};
+ border-bottom-color: ${colors.base.grey};
+ border-width: ${remcalc(5)};
+ margin-left: ${remcalc(-5)};
}
`;
@@ -64,13 +82,15 @@ const Tooltip = ({
children,
arrowPosition = {
bottom: '100%',
- left: '10%'
+ left: '50%'
},
...props
}) => (
-
- {children}
-
+
+
+ {children}
+
+
);
Tooltip.propTypes = {
@@ -81,3 +101,10 @@ Tooltip.propTypes = {
export default Baseline(
Tooltip
);
+
+export { default as TooltipButton } from './button';
+
+export const TooltipDivider = styled.div`
+ border-top: ${boxes.border.unchecked};
+ margin: ${unitcalc(1)} 0 ${unitcalc(1.5)} 0;
+`;
diff --git a/ui/src/components/topology/graph-node/index.js b/ui/src/components/topology/graph-node/index.js
index 3b67d04e..eabaa865 100644
--- a/ui/src/components/topology/graph-node/index.js
+++ b/ui/src/components/topology/graph-node/index.js
@@ -13,7 +13,8 @@ const GraphNode = ({
connected,
data,
index,
- onDragStart
+ onDragStart,
+ onQuickActions
}) => {
const {
@@ -38,7 +39,26 @@ const GraphNode = ({
}
const onButtonClick = (evt) => {
- // console.log('Rect clicked!!!');
+
+ const tooltipPosition = {
+ x: data.x + Constants.buttonRect.x + Constants.buttonRect.width/2,
+ y: data.y + Constants.buttonRect.y + Constants.buttonRect.height
+ };
+
+ if ( connected ) {
+ tooltipPosition.x = tooltipPosition.x + left;
+ tooltipPosition.y = tooltipPosition.y + top;
+ }
+
+ const d = {
+ service: data.uuid,
+ position: {
+ left: tooltipPosition.x,
+ top: tooltipPosition.y
+ }
+ };
+
+ onQuickActions(evt, d);
};
const onStart = (evt) => {
@@ -102,7 +122,8 @@ GraphNode.propTypes = {
connected: React.PropTypes.bool,
data: React.PropTypes.object.isRequired,
index: React.PropTypes.number.isRequired,
- onDragStart: React.PropTypes.func
+ onDragStart: React.PropTypes.func,
+ onQuickActions: React.PropTypes.func
};
export default Baseline(
diff --git a/ui/src/components/topology/topology-graph.js b/ui/src/components/topology/topology-graph.js
index d5c11172..1aefd3d2 100644
--- a/ui/src/components/topology/topology-graph.js
+++ b/ui/src/components/topology/topology-graph.js
@@ -88,7 +88,7 @@ class TopologyGraph extends React.Component {
const n = Math.ceil(
Math.log(
nextSimulation.alphaMin()) / Math.log(
- 1 - nextSimulation.alphaDecay())) - 200;
+ 1 - nextSimulation.alphaDecay()));
for (var i = 0; i < n; ++i) {
nextSimulation.tick();
}
@@ -104,7 +104,10 @@ class TopologyGraph extends React.Component {
render() {
- const services = this.props.services;
+ const {
+ onQuickActions,
+ services
+ } = this.props;
const {
nodes,
@@ -218,6 +221,7 @@ class TopologyGraph extends React.Component {
data={n}
index={index}
onDragStart={onDragStart}
+ onQuickActions={onQuickActions}
connected={n.id !== 'consul'}
/>
));
@@ -250,6 +254,7 @@ class TopologyGraph extends React.Component {
}
TopologyGraph.propTypes = {
+ onQuickActions: React.PropTypes.func,
services: React.PropTypes.array
};
diff --git a/ui/src/shared/composers/index.js b/ui/src/shared/composers/index.js
index 6f7f5bd7..64708119 100644
--- a/ui/src/shared/composers/index.js
+++ b/ui/src/shared/composers/index.js
@@ -1,5 +1,6 @@
import styled, { css } from 'styled-components';
import camelCase from 'camel-case';
+import isString from 'lodash.isstring';
import { boxes, colors } from '../constants';
import { unitcalc, remcalc } from '../functions';
@@ -82,15 +83,25 @@ export const baseBox = ({
box-shadow: ${shadow};
`;
+export const getMeasurement = (measurement) =>
+ isString(measurement) ? measurement :
+ !isNaN(measurement) ? `${measurement}px`: 'auto';
+
+export const absolutePosition = (
+ positions = {}
+) => css`
+ position: absolute;
+ top: ${getMeasurement(positions.top)};
+ right: ${getMeasurement(positions.right)};
+ bottom: ${getMeasurement(positions.bottom)};
+ left: ${getMeasurement(positions.left)};
+`;
+
export const pseudoEl = (
positions = {}
) => css`
content: "";
- position: absolute;
- top: ${positions.top || 'auto'};
- right: ${positions.right || 'auto'};
- bottom: ${positions.bottom || 'auto'};
- left: ${positions.left || 'auto'};
+ ${absolutePosition(positions)};
`;
export const clearfix = css`
diff --git a/ui/src/shared/constants/boxes.js b/ui/src/shared/constants/boxes.js
index d4f80bc7..3d97c578 100644
--- a/ui/src/shared/constants/boxes.js
+++ b/ui/src/shared/constants/boxes.js
@@ -5,6 +5,8 @@ export const borderRadius = remcalc(4);
export const bottomShaddow = `0 ${remcalc(2)} 0 0 rgba(0, 0, 0, 0.05)`;
export const bottomShaddowDarker = `0 ${remcalc(2)} 0 0 rgba(0, 0, 0, 0.1)`;
export const insetShaddow = `inset 0 ${remcalc(3)} 0 0 rgba(0, 0, 0, 0.05)`;
+export const tooltipShadow =
+ `0 ${remcalc(2)} ${remcalc(6)} ${remcalc(1)} rgba(0, 0, 0, 0.1)`;
export const border = {
checked: `${remcalc(1)} solid ${base.primary}`,