From 65dbca997c6d659d30364c035394965b771786ef Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Wed, 13 Apr 2016 12:27:17 +0100 Subject: [PATCH 1/5] Add control for removing stopped docker containers. --- probe/docker/container.go | 2 +- probe/docker/controls.go | 10 ++++++++++ probe/docker/registry.go | 1 + probe/docker/registry_test.go | 4 ++++ probe/docker/reporter.go | 5 +++++ 5 files changed, 21 insertions(+), 1 deletion(-) diff --git a/probe/docker/container.go b/probe/docker/container.go index 61ce97719..1b59976f8 100644 --- a/probe/docker/container.go +++ b/probe/docker/container.go @@ -388,7 +388,7 @@ func (c *container) GetNode(hostID string, localAddrs []net.IP) report.Node { RestartContainer, StopContainer, PauseContainer, AttachContainer, ExecContainer, ) } else { - result = result.WithControls(StartContainer) + result = result.WithControls(StartContainer, RemoveContainer) } result = result.AddTable(LabelPrefix, c.container.Config.Labels) diff --git a/probe/docker/controls.go b/probe/docker/controls.go index 23b8d23ba..edb3a476f 100644 --- a/probe/docker/controls.go +++ b/probe/docker/controls.go @@ -17,6 +17,7 @@ const ( RestartContainer = "docker_restart_container" PauseContainer = "docker_pause_container" UnpauseContainer = "docker_unpause_container" + RemoveContainer = "docker_remove_container" AttachContainer = "docker_attach_container" ExecContainer = "docker_exec_container" @@ -48,6 +49,13 @@ func (r *registry) unpauseContainer(containerID string, _ xfer.Request) xfer.Res return xfer.ResponseError(r.client.UnpauseContainer(containerID)) } +func (r *registry) removeContainer(containerID string, _ xfer.Request) xfer.Response { + log.Infof("Removing container %s", containerID) + return xfer.ResponseError(r.client.RemoveContainer(docker_client.RemoveContainerOptions{ + ID: containerID, + })) +} + func (r *registry) attachContainer(containerID string, req xfer.Request) xfer.Response { c, ok := r.GetContainer(containerID) if !ok { @@ -156,6 +164,7 @@ func (r *registry) registerControls() { controls.Register(RestartContainer, captureContainerID(r.restartContainer)) controls.Register(PauseContainer, captureContainerID(r.pauseContainer)) controls.Register(UnpauseContainer, captureContainerID(r.unpauseContainer)) + controls.Register(RemoveContainer, captureContainerID(r.removeContainer)) controls.Register(AttachContainer, captureContainerID(r.attachContainer)) controls.Register(ExecContainer, captureContainerID(r.execContainer)) } @@ -166,6 +175,7 @@ func (r *registry) deregisterControls() { controls.Rm(RestartContainer) controls.Rm(PauseContainer) controls.Rm(UnpauseContainer) + controls.Rm(RemoveContainer) controls.Rm(AttachContainer) controls.Rm(ExecContainer) } diff --git a/probe/docker/registry.go b/probe/docker/registry.go index b839e86e5..c1a7ff655 100644 --- a/probe/docker/registry.go +++ b/probe/docker/registry.go @@ -68,6 +68,7 @@ type Client interface { RestartContainer(string, uint) error PauseContainer(string) error UnpauseContainer(string) error + RemoveContainer(docker_client.RemoveContainerOptions) error AttachToContainerNonBlocking(docker_client.AttachToContainerOptions) (docker_client.CloseWaiter, error) CreateExec(docker_client.CreateExecOptions) (*docker_client.Exec, error) StartExecNonBlocking(string, docker_client.StartExecOptions) (docker_client.CloseWaiter, error) diff --git a/probe/docker/registry_test.go b/probe/docker/registry_test.go index 2658489c2..e94231e5d 100644 --- a/probe/docker/registry_test.go +++ b/probe/docker/registry_test.go @@ -136,6 +136,10 @@ func (m *mockDockerClient) UnpauseContainer(_ string) error { return fmt.Errorf("unpaused") } +func (m *mockDockerClient) RemoveContainer(_ client.RemoveContainerOptions) error { + return fmt.Errorf("remove") +} + type mockCloseWaiter struct{} func (mockCloseWaiter) Close() error { return nil } diff --git a/probe/docker/reporter.go b/probe/docker/reporter.go index 0b4ea8ff2..7dd53e4a2 100644 --- a/probe/docker/reporter.go +++ b/probe/docker/reporter.go @@ -132,6 +132,11 @@ func (r *Reporter) containerTopology(localAddrs []net.IP) report.Topology { Human: "Unpause", Icon: "fa-play", }) + result.Controls.AddControl(report.Control{ + ID: RemoveContainer, + Human: "Remove", + Icon: "fa-trash-o", + }) result.Controls.AddControl(report.Control{ ID: AttachContainer, Human: "Attach", From ed555b67809548faaaaf57a469640fd3a4205eb4 Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 21 Apr 2016 16:40:21 +0100 Subject: [PATCH 2/5] Add 'closeDetails' field to control responses. --- common/xfer/controls.go | 12 ++++++++---- probe/docker/controls.go | 9 +++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/common/xfer/controls.go b/common/xfer/controls.go index 49a0f35ad..d1fd19c23 100644 --- a/common/xfer/controls.go +++ b/common/xfer/controls.go @@ -18,10 +18,14 @@ type Request struct { // Response is the Probe -> App -> UI message type for the control RPCs. type Response struct { - Value interface{} `json:"value,omitempty"` - Error string `json:"error,omitempty"` - Pipe string `json:"pipe,omitempty"` - RawTTY bool `json:"raw_tty,omitempty"` + Value interface{} `json:"value,omitempty"` + Error string `json:"error,omitempty"` + + // Pipe specific fields + Pipe string `json:"pipe,omitempty"` + RawTTY bool `json:"raw_tty,omitempty"` + + CloseDetails bool `json:"closeDetails,omitempty"` // True will cause the UI to close the details panel. } // Message is the unions of Request, Response and arbitrary Value. diff --git a/probe/docker/controls.go b/probe/docker/controls.go index edb3a476f..1748710b4 100644 --- a/probe/docker/controls.go +++ b/probe/docker/controls.go @@ -51,9 +51,14 @@ func (r *registry) unpauseContainer(containerID string, _ xfer.Request) xfer.Res func (r *registry) removeContainer(containerID string, _ xfer.Request) xfer.Response { log.Infof("Removing container %s", containerID) - return xfer.ResponseError(r.client.RemoveContainer(docker_client.RemoveContainerOptions{ + if err := r.client.RemoveContainer(docker_client.RemoveContainerOptions{ ID: containerID, - })) + }); err != nil { + return xfer.ResponseError(err) + } + return xfer.Response{ + CloseDetails: true, + } } func (r *registry) attachContainer(containerID string, req xfer.Request) xfer.Response { From 62ed88cf1678e55448a824d459e7bb2aa3d17802 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Thu, 21 Apr 2016 18:34:51 +0200 Subject: [PATCH 3/5] Close details panel when node was removed via control --- client/app/scripts/actions/app-actions.js | 8 ++++++++ client/app/scripts/constants/action-types.js | 1 + client/app/scripts/stores/app-store.js | 5 +++++ client/app/scripts/utils/web-api-utils.js | 13 +++++++++---- common/xfer/controls.go | 3 ++- probe/docker/controls.go | 2 +- 6 files changed, 26 insertions(+), 6 deletions(-) diff --git a/client/app/scripts/actions/app-actions.js b/client/app/scripts/actions/app-actions.js index 2094e257b..1b654a934 100644 --- a/client/app/scripts/actions/app-actions.js +++ b/client/app/scripts/actions/app-actions.js @@ -322,6 +322,14 @@ export function receiveApiDetails(apiDetails) { }); } +export function receiveControlNodeRemoved(nodeId) { + AppDispatcher.dispatch({ + type: ActionTypes.RECEIVE_CONTROL_NODE_REMOVED, + nodeId + }); + updateRoute(); +} + export function receiveControlPipeFromParams(pipeId, rawTty) { // TODO add nodeId AppDispatcher.dispatch({ diff --git a/client/app/scripts/constants/action-types.js b/client/app/scripts/constants/action-types.js index b6290f2fb..773143829 100644 --- a/client/app/scripts/constants/action-types.js +++ b/client/app/scripts/constants/action-types.js @@ -27,6 +27,7 @@ const ACTION_TYPES = [ 'PIN_METRIC', 'UNPIN_METRIC', 'OPEN_WEBSOCKET', + 'RECEIVE_CONTROL_NODE_REMOVED', 'RECEIVE_CONTROL_PIPE', 'RECEIVE_CONTROL_PIPE_STATUS', 'RECEIVE_NODE_DETAILS', diff --git a/client/app/scripts/stores/app-store.js b/client/app/scripts/stores/app-store.js index bfd9ab608..78a18aa39 100644 --- a/client/app/scripts/stores/app-store.js +++ b/client/app/scripts/stores/app-store.js @@ -557,6 +557,11 @@ export class AppStore extends Store { this.__emitChange(); break; } + case ActionTypes.RECEIVE_CONTROL_NODE_REMOVED: { + closeNodeDetails(payload.nodeId); + this.__emitChange(); + break; + } case ActionTypes.RECEIVE_CONTROL_PIPE: { controlPipes = controlPipes.set(payload.pipeId, makeOrderedMap({ id: payload.pipeId, diff --git a/client/app/scripts/utils/web-api-utils.js b/client/app/scripts/utils/web-api-utils.js index ddd0a5f9f..885adb271 100644 --- a/client/app/scripts/utils/web-api-utils.js +++ b/client/app/scripts/utils/web-api-utils.js @@ -3,8 +3,8 @@ import reqwest from 'reqwest'; import { clearControlError, closeWebsocket, openWebsocket, receiveError, receiveApiDetails, receiveNodesDelta, receiveNodeDetails, receiveControlError, - receiveControlPipe, receiveControlPipeStatus, receiveControlSuccess, - receiveTopologies, receiveNotFound } from '../actions/app-actions'; + receiveControlNodeRemoved, receiveControlPipe, receiveControlPipeStatus, + receiveControlSuccess, receiveTopologies, receiveNotFound } from '../actions/app-actions'; import { API_INTERVAL, TOPOLOGY_INTERVAL } from '../constants/timer'; @@ -184,8 +184,13 @@ export function doControlRequest(nodeId, control) { url, success: (res) => { receiveControlSuccess(nodeId); - if (res && res.pipe) { - receiveControlPipe(res.pipe, nodeId, res.raw_tty, true); + if (res) { + if (res.pipe) { + receiveControlPipe(res.pipe, nodeId, res.raw_tty, true); + } + if (res.removedNode) { + receiveControlNodeRemoved(nodeId); + } } }, error: (err) => { diff --git a/common/xfer/controls.go b/common/xfer/controls.go index d1fd19c23..244d70add 100644 --- a/common/xfer/controls.go +++ b/common/xfer/controls.go @@ -25,7 +25,8 @@ type Response struct { Pipe string `json:"pipe,omitempty"` RawTTY bool `json:"raw_tty,omitempty"` - CloseDetails bool `json:"closeDetails,omitempty"` // True will cause the UI to close the details panel. + // Remove specific fields + RemovedNode string `json:"removedNode,omitempty"` // Set if node was removed } // Message is the unions of Request, Response and arbitrary Value. diff --git a/probe/docker/controls.go b/probe/docker/controls.go index 1748710b4..de2f4fe30 100644 --- a/probe/docker/controls.go +++ b/probe/docker/controls.go @@ -57,7 +57,7 @@ func (r *registry) removeContainer(containerID string, _ xfer.Request) xfer.Resp return xfer.ResponseError(err) } return xfer.Response{ - CloseDetails: true, + RemovedNode: containerID, } } From 8b06f6c0d86087d2427a8f591a8a9e773455184a Mon Sep 17 00:00:00 2001 From: Tom Wilkie Date: Thu, 21 Apr 2016 18:05:48 +0100 Subject: [PATCH 4/5] Add rank field to controls. --- probe/docker/reporter.go | 68 ++++++++++++++++++++---------------- probe/kubernetes/reporter.go | 1 + render/detailed/node.go | 3 ++ report/controls.go | 1 + 4 files changed, 43 insertions(+), 30 deletions(-) diff --git a/probe/docker/reporter.go b/probe/docker/reporter.go index 7dd53e4a2..e85e2b999 100644 --- a/probe/docker/reporter.go +++ b/probe/docker/reporter.go @@ -107,45 +107,53 @@ func (r *Reporter) containerTopology(localAddrs []net.IP) report.Topology { WithMetadataTemplates(ContainerMetadataTemplates). WithMetricTemplates(ContainerMetricTemplates). WithTableTemplates(ContainerTableTemplates) - result.Controls.AddControl(report.Control{ - ID: StopContainer, - Human: "Stop", - Icon: "fa-stop", - }) - result.Controls.AddControl(report.Control{ - ID: StartContainer, - Human: "Start", - Icon: "fa-play", - }) - result.Controls.AddControl(report.Control{ - ID: RestartContainer, - Human: "Restart", - Icon: "fa-repeat", - }) - result.Controls.AddControl(report.Control{ - ID: PauseContainer, - Human: "Pause", - Icon: "fa-pause", - }) - result.Controls.AddControl(report.Control{ - ID: UnpauseContainer, - Human: "Unpause", - Icon: "fa-play", - }) - result.Controls.AddControl(report.Control{ - ID: RemoveContainer, - Human: "Remove", - Icon: "fa-trash-o", - }) result.Controls.AddControl(report.Control{ ID: AttachContainer, Human: "Attach", Icon: "fa-desktop", + Rank: 1, }) result.Controls.AddControl(report.Control{ ID: ExecContainer, Human: "Exec shell", Icon: "fa-terminal", + Rank: 2, + }) + result.Controls.AddControl(report.Control{ + ID: StartContainer, + Human: "Start", + Icon: "fa-play", + Rank: 3, + }) + result.Controls.AddControl(report.Control{ + ID: RestartContainer, + Human: "Restart", + Icon: "fa-repeat", + Rank: 4, + }) + result.Controls.AddControl(report.Control{ + ID: PauseContainer, + Human: "Pause", + Icon: "fa-pause", + Rank: 5, + }) + result.Controls.AddControl(report.Control{ + ID: UnpauseContainer, + Human: "Unpause", + Icon: "fa-play", + Rank: 6, + }) + result.Controls.AddControl(report.Control{ + ID: StopContainer, + Human: "Stop", + Icon: "fa-stop", + Rank: 7, + }) + result.Controls.AddControl(report.Control{ + ID: RemoveContainer, + Human: "Remove", + Icon: "fa-trash-o", + Rank: 8, }) metadata := map[string]string{report.ControlProbeID: r.probeID} diff --git a/probe/kubernetes/reporter.go b/probe/kubernetes/reporter.go index d27a68731..45d646a02 100644 --- a/probe/kubernetes/reporter.go +++ b/probe/kubernetes/reporter.go @@ -129,6 +129,7 @@ func (r *Reporter) podTopology(services []Service) (report.Topology, report.Topo ID: GetLogs, Human: "Get logs", Icon: "fa-desktop", + Rank: 0, }) for _, service := range services { selectors[service.ID()] = service.Selector() diff --git a/render/detailed/node.go b/render/detailed/node.go index 7ab8de293..11e110e35 100644 --- a/render/detailed/node.go +++ b/render/detailed/node.go @@ -45,6 +45,7 @@ type wiredControlInstance struct { ID string `json:"id"` Human string `json:"human"` Icon string `json:"icon"` + Rank int `json:"rank"` } // CodecEncodeSelf marshals this ControlInstance. It takes the basic Metric @@ -56,6 +57,7 @@ func (c *ControlInstance) CodecEncodeSelf(encoder *codec.Encoder) { ID: c.Control.ID, Human: c.Control.Human, Icon: c.Control.Icon, + Rank: c.Control.Rank, }) } @@ -70,6 +72,7 @@ func (c *ControlInstance) CodecDecodeSelf(decoder *codec.Decoder) { ID: in.ID, Human: in.Human, Icon: in.Icon, + Rank: in.Rank, }, } } diff --git a/report/controls.go b/report/controls.go index e4c3d243b..4c294aa0a 100644 --- a/report/controls.go +++ b/report/controls.go @@ -16,6 +16,7 @@ type Control struct { ID string `json:"id"` Human string `json:"human"` Icon string `json:"icon"` // from https://fortawesome.github.io/Font-Awesome/cheatsheet/ please + Rank int `json:"rank"` } // Merge merges other with cs, returning a fresh Controls. From eda2a208c6d443dbe3f08fd9f4b3990bfc561eb4 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Thu, 21 Apr 2016 19:36:13 +0200 Subject: [PATCH 5/5] Sort controls by rank in the UI --- .../scripts/components/node-details/node-details-controls.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/app/scripts/components/node-details/node-details-controls.js b/client/app/scripts/components/node-details/node-details-controls.js index d3028e74d..703d5f79e 100644 --- a/client/app/scripts/components/node-details/node-details-controls.js +++ b/client/app/scripts/components/node-details/node-details-controls.js @@ -1,4 +1,5 @@ import React from 'react'; +import _ from 'lodash'; import NodeDetailsControlButton from './node-details-control-button'; @@ -17,7 +18,7 @@ export default function NodeDetailsControls({controls, error, nodeId, pending}) {error} } - {controls && controls.map(control => )} {controls && }