mirror of
https://github.com/weaveworks/scope.git
synced 2026-09-06 10:17:19 +00:00
Merge pull request #1290 from weaveworks/remove-container-control
Add control for removing stopped docker containers.
This commit is contained in:
@@ -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({
|
||||
|
||||
@@ -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})
|
||||
<span className="node-details-controls-error-messages">{error}</span>
|
||||
</div>}
|
||||
<span className="node-details-controls-buttons">
|
||||
{controls && controls.map(control => <NodeDetailsControlButton
|
||||
{_.sortBy(controls, 'rank').map(control => <NodeDetailsControlButton
|
||||
nodeId={nodeId} control={control} pending={pending} key={control.id} />)}
|
||||
</span>
|
||||
{controls && <span title="Applying..." className={spinnerClassName}></span>}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -561,6 +561,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,
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -18,10 +18,15 @@ 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"`
|
||||
|
||||
// Remove specific fields
|
||||
RemovedNode string `json:"removedNode,omitempty"` // Set if node was removed
|
||||
}
|
||||
|
||||
// Message is the unions of Request, Response and arbitrary Value.
|
||||
|
||||
@@ -394,7 +394,7 @@ func (c *container) GetNode(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)
|
||||
|
||||
@@ -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,18 @@ 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)
|
||||
if err := r.client.RemoveContainer(docker_client.RemoveContainerOptions{
|
||||
ID: containerID,
|
||||
}); err != nil {
|
||||
return xfer.ResponseError(err)
|
||||
}
|
||||
return xfer.Response{
|
||||
RemovedNode: containerID,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *registry) attachContainer(containerID string, req xfer.Request) xfer.Response {
|
||||
c, ok := r.GetContainer(containerID)
|
||||
if !ok {
|
||||
@@ -156,6 +169,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 +180,7 @@ func (r *registry) deregisterControls() {
|
||||
controls.Rm(RestartContainer)
|
||||
controls.Rm(PauseContainer)
|
||||
controls.Rm(UnpauseContainer)
|
||||
controls.Rm(RemoveContainer)
|
||||
controls.Rm(AttachContainer)
|
||||
controls.Rm(ExecContainer)
|
||||
}
|
||||
|
||||
@@ -70,6 +70,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)
|
||||
|
||||
@@ -138,6 +138,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 }
|
||||
|
||||
+38
-25
@@ -100,40 +100,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: 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}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user