Support restarting service

This commit is contained in:
cuigh 2018-04-10 11:25:55 +08:00
parent 2277893dfe
commit 536dc59cc6
19 changed files with 112 additions and 68 deletions

View File

@ -1145,7 +1145,7 @@ var Swirl;
<p class="card-header-title is-paddingless">${this.opts.title}</p>
<a data-action="remove-chart" class="card-header-icon" aria-label="remove chart">
<span class="icon">
<i class="fas fa-times has-text-danger" aria-hidden="true"></i>
<i class="fas fa-trash has-text-danger" aria-hidden="true"></i>
</span>
</a>
</header>
@ -2339,7 +2339,7 @@ var Swirl;
<td>
<a class="button is-small is-outlined is-danger" data-action="delete-${this.name}">
<span class="icon is-small">
<i class="fas fa-times"></i>
<i class="fas fa-trash"></i>
</span>
</a>
</td>
@ -2416,7 +2416,8 @@ var Swirl;
this.table = new Table("#table-items");
this.table.on("delete-service", this.deleteService.bind(this))
.on("scale-service", this.scaleService.bind(this))
.on("rollback-service", this.rollbackService.bind(this));
.on("rollback-service", this.rollbackService.bind(this))
.on("restart-service", this.restartService.bind(this));
}
deleteService(e) {
let $tr = $(e.target).closest("tr");
@ -2429,23 +2430,30 @@ var Swirl;
});
}
scaleService(e) {
let $btn = $(e.target).closest("button");
let $tr = $btn.closest("tr");
let $target = $(e.target), $tr = $target.closest("tr");
let data = {
name: $tr.find("td:eq(0)").text().trim(),
count: $btn.data("replicas"),
count: $target.data("replicas"),
};
Modal.confirm(`<input name="count" value="${data.count}" class="input" placeholder="Replicas">`, "Scale service", (dlg, e) => {
Modal.confirm(`<input name="count" value="${data.count}" class="input" placeholder="Replicas">`, "Scale service", dlg => {
data.count = dlg.find("input[name=count]").val();
$ajax.post(`${data.name}/scale`, data).trigger(e.target).encoder("form").json(() => {
$ajax.post(`${data.name}/scale`, data).encoder("form").json(() => {
location.reload();
});
});
}
rollbackService(e) {
let $btn = $(e.target).closest("button"), $tr = $btn.closest("tr"), name = $tr.find("td:eq(0)").text().trim();
Modal.confirm(`Are you sure to rollback service: <strong>${name}</strong>?`, "Rollback service", (dlg, e) => {
$ajax.post(`${name}/rollback`, { name: name }).trigger(e.target).encoder("form").json(() => {
let $tr = $(e.target).closest("tr"), name = $tr.find("td:eq(0)").text().trim();
Modal.confirm(`Are you sure to rollback service: <strong>${name}</strong>?`, "Rollback service", dlg => {
$ajax.post(`${name}/rollback`, { name: name }).encoder("form").json(() => {
dlg.close();
});
});
}
restartService(e) {
let $tr = $(e.target).closest("tr"), name = $tr.find("td:eq(0)").text().trim();
Modal.confirm(`Are you sure to restart service: <strong>${name}</strong>?`, "Restart service", dlg => {
$ajax.post(`${name}/restart`, { name: name }).encoder("form").json(() => {
dlg.close();
});
});

File diff suppressed because one or more lines are too long

View File

@ -31,7 +31,7 @@ namespace Swirl.Core {
<p class="card-header-title is-paddingless">${this.opts.title}</p>
<a data-action="remove-chart" class="card-header-icon" aria-label="remove chart">
<span class="icon">
<i class="fas fa-times has-text-danger" aria-hidden="true"></i>
<i class="fas fa-trash has-text-danger" aria-hidden="true"></i>
</span>
</a>
</header>

View File

@ -176,7 +176,7 @@ namespace Swirl.Service {
<td>
<a class="button is-small is-outlined is-danger" data-action="delete-${this.name}">
<span class="icon is-small">
<i class="fas fa-times"></i>
<i class="fas fa-trash"></i>
</span>
</a>
</td>

View File

@ -13,7 +13,8 @@ namespace Swirl.Service {
// bind events
this.table.on("delete-service", this.deleteService.bind(this))
.on("scale-service", this.scaleService.bind(this))
.on("rollback-service", this.rollbackService.bind(this));
.on("rollback-service", this.rollbackService.bind(this))
.on("restart-service", this.restartService.bind(this));
}
private deleteService(e: JQueryEventObject) {
@ -28,26 +29,35 @@ namespace Swirl.Service {
}
private scaleService(e: JQueryEventObject) {
let $btn = $(e.target).closest("button");
let $tr = $btn.closest("tr");
let $target = $(e.target),
$tr = $target.closest("tr");
let data = {
name: $tr.find("td:eq(0)").text().trim(),
count: $btn.data("replicas"),
count: $target.data("replicas"),
};
Modal.confirm(`<input name="count" value="${data.count}" class="input" placeholder="Replicas">`, "Scale service", (dlg, e) => {
Modal.confirm(`<input name="count" value="${data.count}" class="input" placeholder="Replicas">`, "Scale service", dlg => {
data.count = dlg.find("input[name=count]").val();
$ajax.post(`${data.name}/scale`, data).trigger(e.target).encoder("form").json<AjaxResult>(() => {
$ajax.post(`${data.name}/scale`, data).encoder("form").json<AjaxResult>(() => {
location.reload();
})
});
}
private rollbackService(e: JQueryEventObject) {
let $btn = $(e.target).closest("button"),
$tr = $btn.closest("tr"),
let $tr = $(e.target).closest("tr"),
name = $tr.find("td:eq(0)").text().trim();
Modal.confirm(`Are you sure to rollback service: <strong>${name}</strong>?`, "Rollback service", (dlg, e) => {
$ajax.post(`${name}/rollback`, {name: name}).trigger(e.target).encoder("form").json<AjaxResult>(() => {
Modal.confirm(`Are you sure to rollback service: <strong>${name}</strong>?`, "Rollback service", dlg => {
$ajax.post(`${name}/rollback`, {name: name}).encoder("form").json<AjaxResult>(() => {
dlg.close();
})
});
}
private restartService(e: JQueryEventObject) {
let $tr = $(e.target).closest("tr"),
name = $tr.find("td:eq(0)").text().trim();
Modal.confirm(`Are you sure to restart service: <strong>${name}</strong>?`, "Restart service", dlg => {
$ajax.post(`${name}/restart`, {name: name}).encoder("form").json<AjaxResult>(() => {
dlg.close();
})
});

View File

@ -340,19 +340,15 @@ func ServiceScale(name string, version, count uint64) error {
spec := service.Spec
if spec.Mode.Replicated == nil {
return errors.New("the mode of service isn't replicated")
return errors.New("scale can only be used with replicated mode")
}
spec.Mode.Replicated.Replicas = &count
options := types.ServiceUpdateOptions{
RegistryAuthFrom: types.RegistryAuthFromSpec,
QueryRegistry: false,
}
ver := service.Version
if version > 0 {
ver = swarm.Version{Index: version}
}
resp, err := cli.ServiceUpdate(context.Background(), name, ver, spec, options)
resp, err := cli.ServiceUpdate(context.Background(), name, ver, spec, types.ServiceUpdateOptions{})
if err == nil && len(resp.Warnings) > 0 {
mgr.Logger().Warnf("service %s was scaled but got warnings: %v", name, resp.Warnings)
}
@ -734,20 +730,30 @@ func ServiceRollback(name string) error {
return err
}
if service.PreviousSpec == nil {
return errors.New("can't rollback service, previous spec is not exists")
}
spec := *service.PreviousSpec
options := types.ServiceUpdateOptions{
RegistryAuthFrom: types.RegistryAuthFromPreviousSpec,
QueryRegistry: false,
Rollback: "previous",
Rollback: "previous",
}
resp, err := cli.ServiceUpdate(context.Background(), name, service.Version, spec, options)
resp, err := cli.ServiceUpdate(context.Background(), name, service.Version, service.Spec, options)
if err == nil && len(resp.Warnings) > 0 {
mgr.Logger().Warnf("service %s was rollbacked but got warnings: %v", name, resp.Warnings)
}
return err
})
}
// ServiceRestart force to refresh a service.
func ServiceRestart(name string) error {
return mgr.Do(func(ctx context.Context, cli *client.Client) (err error) {
service, _, err := cli.ServiceInspectWithRaw(ctx, name, types.ServiceInspectOptions{})
if err != nil {
return err
}
service.Spec.TaskTemplate.ForceUpdate++
resp, err := cli.ServiceUpdate(context.Background(), name, service.Version, service.Spec, types.ServiceUpdateOptions{})
if err == nil && len(resp.Warnings) > 0 {
mgr.Logger().Warnf("service %s was restarted but got warnings: %v", name, resp.Warnings)
}
return err
})
}

View File

@ -20,6 +20,7 @@ button.return: Return
button.leave: Leave
button.scale: Scale
button.rollback: Rollback
button.restart: Restart
button.previous: Previous
button.next: Next
button.import: Import
@ -71,7 +72,7 @@ menu.stack.task: Tasks
menu.stack.archive: Archives
menu.task: Tasks
menu.secret: Secrets
menu.config: Config
menu.config: Configs
menu.system: System
menu.role: Roles
menu.user: Users

View File

@ -20,6 +20,7 @@ button.return: 返回
button.leave: 离开
button.scale: 调整
button.rollback: 回退
button.restart: 重启
button.previous: 前一页
button.next: 后一页
button.import: 导入

View File

@ -24,6 +24,7 @@ type ServiceController struct {
Delete web.HandlerFunc `path:"/:name/delete" method:"post" name:"service.delete" authorize:"!" perm:"write,service,name"`
Scale web.HandlerFunc `path:"/:name/scale" method:"post" name:"service.scale" authorize:"!" perm:"write,service,name"`
Rollback web.HandlerFunc `path:"/:name/rollback" method:"post" name:"service.rollback" authorize:"!" perm:"write,service,name"`
Restart web.HandlerFunc `path:"/:name/restart" method:"post" name:"service.restart" authorize:"!" perm:"write,service,name"`
New web.HandlerFunc `path:"/new" name:"service.new" authorize:"!" desc:"new service page"`
Create web.HandlerFunc `path:"/new" method:"post" name:"service.create" authorize:"!" desc:"create service"`
Edit web.HandlerFunc `path:"/:name/edit" name:"service.edit" authorize:"!" perm:"write,service,name"`
@ -47,6 +48,7 @@ func Service() (c *ServiceController) {
Update: serviceUpdate,
Scale: serviceScale,
Rollback: serviceRollback,
Restart: serviceRestart,
PermEdit: servicePermEdit,
PermUpdate: permUpdate("service", "name"),
Stats: serviceStats,
@ -271,6 +273,15 @@ func serviceRollback(ctx web.Context) error {
return ajaxResult(ctx, err)
}
func serviceRestart(ctx web.Context) error {
name := ctx.F("name")
err := docker.ServiceRestart(name)
if err == nil {
biz.Event.CreateService(model.EventActionRestart, name, ctx.User())
}
return ajaxResult(ctx, err)
}
func servicePermEdit(ctx web.Context) error {
name := ctx.P("name")
m := newModel(ctx).Set("Name", name)

View File

@ -36,6 +36,7 @@ const (
EventActionUpdate EventAction = "Update"
EventActionScale EventAction = "Scale"
EventActionRollback EventAction = "Rollback"
EventActionRestart EventAction = "Restart"
EventActionDisconnect EventAction = "Disconnect"
)

View File

@ -261,7 +261,7 @@
<td>
<a class="button is-small is-outlined is-danger" data-action="delete-secret">
<span class="icon is-small">
<i class="fas fa-times"></i>
<i class="fas fa-trash"></i>
</span>
</a>
</td>
@ -301,7 +301,7 @@
<td>
<a class="button is-small is-outlined is-danger" data-action="delete-config">
<span class="icon is-small">
<i class="fas fa-times"></i>
<i class="fas fa-trash"></i>
</span>
</a>
</td>

View File

@ -40,7 +40,7 @@
<!-- Right side -->
<div class="level-right">
<p class="level-item">
<button id="btn-delete" class="button is-danger"><span class="icon"><i class="fas fa-times"></i></span><span>{{ i18n("button.delete") }}</span></button>
<button id="btn-delete" class="button is-danger"><span class="icon"><i class="fas fa-trash"></i></span><span>{{ i18n("button.delete") }}</span></button>
</p>
<p class="level-item">
<a href="new" class="button is-success"><span class="icon"><i class="fas fa-plus"></i></span><span>{{ i18n("button.new") }}</span></a>

View File

@ -54,7 +54,7 @@
{{end}}
</p>
<p class="level-item">
<button id="btn-delete" class="button is-danger"><span class="icon"><i class="fas fa-times"></i></span><span>{{ i18n("button.delete") }}</span></button>
<button id="btn-delete" class="button is-danger"><span class="icon"><i class="fas fa-trash"></i></span><span>{{ i18n("button.delete") }}</span></button>
</p>
</div>
</nav>

View File

@ -41,7 +41,7 @@
<!-- Right side -->
<div class="level-right">
<p class="level-item">
<button id="btn-delete" class="button is-danger"><span class="icon"><i class="fas fa-times"></i></span><span>{{ i18n("button.delete") }}</span></button>
<button id="btn-delete" class="button is-danger"><span class="icon"><i class="fas fa-trash"></i></span><span>{{ i18n("button.delete") }}</span></button>
</p>
</div>
</nav>

View File

@ -40,7 +40,7 @@
<!-- Right side -->
<div class="level-right">
<p class="level-item">
<button id="btn-delete" class="button is-danger"><span class="icon"><i class="fas fa-times"></i></span><span>{{ i18n("button.delete") }}</span></button>
<button id="btn-delete" class="button is-danger"><span class="icon"><i class="fas fa-trash"></i></span><span>{{ i18n("button.delete") }}</span></button>
</p>
<p class="level-item">
<a href="new" class="button is-success"><span class="icon"><i class="fas fa-plus"></i></span><span>{{ i18n("button.new") }}</span></a>

View File

@ -44,7 +44,7 @@
<th>{{ i18n("field.image") }}</th>
<th width="145">Mode</th>
<th>{{ i18n("field.updated-at") }}</th>
<th width="140">{{ i18n("field.action") }}</th>
<th width="190">{{ i18n("field.action") }}</th>
</tr>
</thead>
<tbody>
@ -60,22 +60,28 @@
</td>
<td>{{time(.UpdatedAt)}}{{ if .UpdateStatus }}<span class="icon {{ if .UpdateStatus == "completed" }}has-text-success{{ else if .UpdateStatus == "updating" }}has-text-warning{{ else }}has-text-danger{{ end }} tooltip" data-tooltip="{{.UpdateStatus}}"><i class="fas fa-circle"></i></span>{{ end }}</td>
<td>
<a href="{{.Name}}/edit" class="button is-small is-dark is-outlined tooltip is-tooltip-bottom" data-tooltip="{{ i18n("button.edit") }}">
<span class="icon"><i class="fas fa-edit"></i></span>
</a>
{{if .Mode == "replicated"}}
<button type="button" class="button is-small is-info is-outlined tooltip is-tooltip-bottom" data-tooltip="{{ i18n("button.scale") }}" data-action="scale-service" data-replicas="{{.Replicas}}">
<span class="icon"><i class="fas fa-arrows-alt"></i></span>
</button>
{{end}}
{{if .Rollback }}
<button type="button" class="button is-small is-info is-outlined tooltip is-tooltip-bottom" data-tooltip="{{ i18n("button.rollback") }}" data-action="rollback-service">
<span class="icon"><i class="fas fa-history"></i></span>
</button>
{{end}}
<button class="button is-small is-danger is-outlined tooltip is-tooltip-bottom" data-tooltip="{{ i18n("button.delete") }}" data-action="delete-service">
<span class="icon"><i class="fas fa-times"></i></span>
</button>
<a href="{{.Name}}/edit" class="button is-small is-dark is-outlined">{{ i18n("button.edit") }}</a>
<button class="button is-small is-danger is-outlined" data-action="delete-service">{{ i18n("button.delete") }}</button>
<div class="dropdown is-hoverable is-right">
<div class="dropdown-trigger">
<button class="button is-small" aria-haspopup="true" aria-controls="dropdown-menu-{{ .Name }}">
<span>More</span>
<span class="icon is-small">
<i class="fas fa-angle-down" aria-hidden="true"></i>
{*<i class="fas fa-ellipsis-v" aria-hidden="true"></i>*}
</span>
</button>
</div>
<div class="dropdown-menu" id="dropdown-menu-{{ .Name }}" role="menu">
<div class="dropdown-content">
{{if .Mode == "replicated" }}<a class="dropdown-item" data-action="scale-service" data-replicas="{{.Replicas}}">{{ i18n("button.scale") }}</a>{{end}}
{{if .Rollback }}<a class="dropdown-item" data-action="rollback-service">{{ i18n("button.rollback") }}</a>{{end}}
<a class="dropdown-item" data-action="restart-service">{{ i18n("button.restart") }}</a>
{*<a class="dropdown-item has-text-danger" data-action="delete-service">{{ i18n("button.delete") }}</a>*}
</div>
</div>
</div>
</td>
</tr>
{{end}}

View File

@ -59,7 +59,7 @@
<span class="icon"><i class="fas fa-edit"></i></span>
</a>
<button class="button is-small is-danger is-outlined tooltip is-tooltip-bottom" data-tooltip="{{ i18n("button.delete") }}" data-action="delete-template">
<span class="icon"><i class="fas fa-times"></i></span>
<span class="icon"><i class="fas fa-trash"></i></span>
</button>
</td>
</tr>

View File

@ -12,7 +12,7 @@
<div class="field">
<label class="label">{{ i18n("field.name") }}</label>
<div class="control">
<input name="name" value="{{ .Chart.Name }}" class="input" placeholder="lower-case letters and underline only" data-v-rule="native;regex" data-v-arg-regex="[a-z_]+" data-v-msg-regex="Only lower-case letters and underline are allowed." required{{if .Chart.Name}} readonly{{ end }}>
<input name="name" value="{{ .Chart.Name }}" class="input" placeholder="Only lower-case letters and underscores are allowed" data-v-rule="native;regex" data-v-arg-regex="[a-z_]+" data-v-msg-regex="Only lower-case letters and underline are allowed." required{{if .Chart.Name}} readonly{{ end }}>
</div>
</div>
<div class="field">
@ -97,7 +97,7 @@
<div class="field">
<label class="label">Query expression</label>
<div class="control">
<input name="query" value="{{ .Chart.Query }}" class="input" placeholder="Prometheus query expression" data-v-rule="native" required>
<input name="query" value="{{ .Chart.Query }}" class="input" placeholder="Prometheus query expression, for service dashboard, you can use '${service}' variable" data-v-rule="native" required>
</div>
</div>
<div class="field">

View File

@ -40,10 +40,10 @@
<!-- Right side -->
<div class="level-right">
<p class="level-item">
<button id="btn-delete" class="button is-danger"><span class="icon"><i class="fas fa-times"></i></span><span>{{ i18n("button.delete") }}</span></button>
<button id="btn-delete" class="button is-danger"><span class="icon"><i class="fas fa-trash"></i></span><span>{{ i18n("button.delete") }}</span></button>
</p>
<p class="level-item">
<button id="btn-prune" class="button is-warning"><span class="icon"><i class="fas fa-times-circle"></i></span><span>{{ i18n("button.prune") }}</span></button>
<button id="btn-prune" class="button is-warning"><span class="icon"><i class="fas fa-trash-alt"></i></span><span>{{ i18n("button.prune") }}</span></button>
</p>
<p class="level-item">
<a class="button is-success" href="new"><span class="icon"><i class="fas fa-plus"></i></span><span>{{ i18n("button.new") }}</span></a>