mirror of
https://github.com/cuigh/swirl
synced 2024-12-28 23:02:02 +00:00
Add task logs page
This commit is contained in:
parent
f08ec4a142
commit
81081c8164
@ -1,10 +1,12 @@
|
|||||||
package docker
|
package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/cuigh/swirl/misc"
|
"github.com/cuigh/swirl/misc"
|
||||||
"github.com/cuigh/swirl/model"
|
"github.com/cuigh/swirl/model"
|
||||||
@ -12,6 +14,7 @@ import (
|
|||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TaskList return all running tasks of a service or a node.
|
// TaskList return all running tasks of a service or a node.
|
||||||
@ -88,11 +91,31 @@ func TaskInspect(id string) (task swarm.Task, raw []byte, err error) {
|
|||||||
|
|
||||||
// TaskLogs returns the logs generated by a task in an io.ReadCloser.
|
// TaskLogs returns the logs generated by a task in an io.ReadCloser.
|
||||||
// It's up to the caller to close the stream.
|
// It's up to the caller to close the stream.
|
||||||
func TaskLogs(id string) (rc io.ReadCloser, err error) {
|
func TaskLogs(id string, line int, timestamps bool) (stdout, stderr *bytes.Buffer, err error) {
|
||||||
err = mgr.Do(func(ctx context.Context, cli *client.Client) (err error) {
|
var (
|
||||||
opts := types.ContainerLogsOptions{}
|
ctx context.Context
|
||||||
rc, err = cli.TaskLogs(ctx, id, opts)
|
cli *client.Client
|
||||||
|
rc io.ReadCloser
|
||||||
|
)
|
||||||
|
|
||||||
|
ctx, cli, err = mgr.Client()
|
||||||
|
if err != nil {
|
||||||
return
|
return
|
||||||
})
|
}
|
||||||
|
|
||||||
|
opts := types.ContainerLogsOptions{
|
||||||
|
ShowStdout: true,
|
||||||
|
ShowStderr: true,
|
||||||
|
Tail: strconv.Itoa(line),
|
||||||
|
Timestamps: timestamps,
|
||||||
|
//Since: (time.Hour * 24).String()
|
||||||
|
}
|
||||||
|
if rc, err = cli.TaskLogs(ctx, id, opts); err == nil {
|
||||||
|
defer rc.Close()
|
||||||
|
|
||||||
|
stdout = &bytes.Buffer{}
|
||||||
|
stderr = &bytes.Buffer{}
|
||||||
|
_, err = stdcopy.StdCopy(stdout, stderr, rc)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/cuigh/auxo/net/web"
|
"github.com/cuigh/auxo/net/web"
|
||||||
|
"github.com/cuigh/auxo/util/cast"
|
||||||
"github.com/cuigh/swirl/biz/docker"
|
"github.com/cuigh/swirl/biz/docker"
|
||||||
"github.com/cuigh/swirl/misc"
|
"github.com/cuigh/swirl/misc"
|
||||||
"github.com/cuigh/swirl/model"
|
"github.com/cuigh/swirl/model"
|
||||||
@ -12,6 +13,7 @@ type TaskController struct {
|
|||||||
List web.HandlerFunc `path:"/" name:"task.list" authorize:"!" desc:"task list page"`
|
List web.HandlerFunc `path:"/" name:"task.list" authorize:"!" desc:"task list page"`
|
||||||
Detail web.HandlerFunc `path:"/:id/detail" name:"task.detail" authorize:"!" desc:"task detail page"`
|
Detail web.HandlerFunc `path:"/:id/detail" name:"task.detail" authorize:"!" desc:"task detail page"`
|
||||||
Raw web.HandlerFunc `path:"/:id/raw" name:"task.raw" authorize:"!" desc:"task raw page"`
|
Raw web.HandlerFunc `path:"/:id/raw" name:"task.raw" authorize:"!" desc:"task raw page"`
|
||||||
|
Logs web.HandlerFunc `path:"/:id/logs" name:"task.logs" authorize:"!" desc:"task logs page"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Task creates an instance of TaskController
|
// Task creates an instance of TaskController
|
||||||
@ -20,6 +22,7 @@ func Task() (c *TaskController) {
|
|||||||
List: taskList,
|
List: taskList,
|
||||||
Detail: taskDetail,
|
Detail: taskDetail,
|
||||||
Raw: taskRaw,
|
Raw: taskRaw,
|
||||||
|
Logs: taskLogs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,3 +74,22 @@ func taskRaw(ctx web.Context) error {
|
|||||||
m := newModel(ctx).Set("Task", task).Set("Raw", j)
|
m := newModel(ctx).Set("Task", task).Set("Raw", j)
|
||||||
return ctx.Render("task/raw", m)
|
return ctx.Render("task/raw", m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func taskLogs(ctx web.Context) error {
|
||||||
|
id := ctx.P("id")
|
||||||
|
task, _, err := docker.TaskInspect(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
line := cast.ToInt(ctx.Q("line"), 500)
|
||||||
|
timestamps := cast.ToBool(ctx.Q("timestamps"), false)
|
||||||
|
stdout, stderr, err := docker.TaskLogs(id, line, timestamps)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
m := newModel(ctx).Set("Task", task).Set("Line", line).Set("Timestamps", timestamps).
|
||||||
|
Set("Stdout", stdout.String()).Set("Stderr", stderr.String())
|
||||||
|
return ctx.Render("task/logs", m)
|
||||||
|
}
|
||||||
|
@ -91,6 +91,7 @@ var Perms = []PermGroup{
|
|||||||
{Key: "task.list", Text: "View list"},
|
{Key: "task.list", Text: "View list"},
|
||||||
{Key: "task.detail", Text: "View detail"},
|
{Key: "task.detail", Text: "View detail"},
|
||||||
{Key: "task.raw", Text: "View raw"},
|
{Key: "task.raw", Text: "View raw"},
|
||||||
|
{Key: "task.logs", Text: "View logs"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -108,6 +109,7 @@ var Perms = []PermGroup{
|
|||||||
{Key: "container.list", Text: "View list"},
|
{Key: "container.list", Text: "View list"},
|
||||||
{Key: "container.detail", Text: "View detail"},
|
{Key: "container.detail", Text: "View detail"},
|
||||||
{Key: "container.raw", Text: "View raw"},
|
{Key: "container.raw", Text: "View raw"},
|
||||||
|
{Key: "container.logs", Text: "View logs"},
|
||||||
{Key: "container.delete", Text: "Delete"},
|
{Key: "container.delete", Text: "Delete"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
<div class="navbar-brand">
|
<div class="navbar-brand">
|
||||||
<a class="navbar-item is-tab is-active" href="/task/{{.Task.ID}}/detail">{{ i18n("menu.detail") }}</a>
|
<a class="navbar-item is-tab is-active" href="/task/{{.Task.ID}}/detail">{{ i18n("menu.detail") }}</a>
|
||||||
<a class="navbar-item is-tab " href="/task/{{.Task.ID}}/raw">{{ i18n("menu.raw") }}</a>
|
<a class="navbar-item is-tab " href="/task/{{.Task.ID}}/raw">{{ i18n("menu.raw") }}</a>
|
||||||
|
<a class="navbar-item is-tab" href="/task/{{.Task.ID}}/logs">{{ i18n("menu.log") }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
103
views/task/logs.jet
Normal file
103
views/task/logs.jet
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
{{ extends "../_layouts/default" }}
|
||||||
|
|
||||||
|
{{ block body() }}
|
||||||
|
<section class="hero is-info">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container has-text-centered">
|
||||||
|
<h1 class="title is-2 is-uppercase">{{ i18n("container.title") }}</h1>
|
||||||
|
<h2 class="subtitle is-5">{{ i18n("container.description") }}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<nav class="breadcrumb has-succeeds-separator is-small is-marginless" aria-label="breadcrumbs">
|
||||||
|
<ul>
|
||||||
|
<li><a href="/">{{ i18n("menu.dashboard") }}</a></li>
|
||||||
|
<li><a href="/task/">{{ i18n("menu.task") }}</a></li>
|
||||||
|
<li class="is-active"><a>{{ i18n("menu.log") }}</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="hero is-small is-light">
|
||||||
|
<div class="hero-body">
|
||||||
|
<div class="container">
|
||||||
|
<h2 class="title is-2">
|
||||||
|
{{ .Task.Name ? .Task.Name : .Task.ID }}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<nav class="navbar has-shadow">
|
||||||
|
<div class="container">
|
||||||
|
<div class="navbar-brand">
|
||||||
|
<a class="navbar-item is-tab" href="/task/{{.Task.ID}}/detail">{{ i18n("menu.detail") }}</a>
|
||||||
|
<a class="navbar-item is-tab " href="/task/{{.Task.ID}}/raw">{{ i18n("menu.raw") }}</a>
|
||||||
|
<a class="navbar-item is-tab is-active" href="/task/{{.Task.ID}}/logs">{{ i18n("menu.log") }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<section class="section">
|
||||||
|
<div class="container">
|
||||||
|
<nav class="level">
|
||||||
|
<form>
|
||||||
|
<div class="level-left">
|
||||||
|
<div class="level-item">
|
||||||
|
<div class="field has-addons">
|
||||||
|
<p class="control">
|
||||||
|
<a class="button is-static">Lines</a>
|
||||||
|
</p>
|
||||||
|
<p class="control">
|
||||||
|
<input name="line" value="{{ .Line }}" class="input" placeholder="Max lines from tail">
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="level-item">
|
||||||
|
<div class="field">
|
||||||
|
<input id="cb-timestamps" name="timestamps" value="true" type="checkbox" class="switch is-success is-rounded"{{if .Timestamps}} checked{{end}}>
|
||||||
|
<label for="cb-timestamps">Add timestamps</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="level-item">
|
||||||
|
<div class="field">
|
||||||
|
<p class="control">
|
||||||
|
<button class="button is-primary">{{ i18n("button.search") }}</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="tabs is-boxed" data-target="tab-content">
|
||||||
|
<ul>
|
||||||
|
<li class="is-active">
|
||||||
|
<a><span>Stdout</span></a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a><span>Stderr</span></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div id="tab-content" class="content">
|
||||||
|
<div class="field">
|
||||||
|
<div class="control">
|
||||||
|
{{ if .Stdout }}<textarea class="textarea code is-small" rows="30" readonly>{{ .Stdout }}</textarea>{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field" style="display: none">
|
||||||
|
<div class="control">
|
||||||
|
{{ if .Stderr }}<textarea class="textarea code is-small" rows="30" readonly>{{ .Stderr }}</textarea>{{ end }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a href="/task/" class="button is-primary">
|
||||||
|
<span class="icon"><i class="fas fa-reply"></i></span>
|
||||||
|
<span>{{ i18n("button.return") }}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{{ end }}
|
@ -33,6 +33,7 @@
|
|||||||
<div class="navbar-brand">
|
<div class="navbar-brand">
|
||||||
<a class="navbar-item is-tab" href="/task/{{.Task.ID}}/detail">{{ i18n("menu.detail") }}</a>
|
<a class="navbar-item is-tab" href="/task/{{.Task.ID}}/detail">{{ i18n("menu.detail") }}</a>
|
||||||
<a class="navbar-item is-tab is-active" href="/task/{{.Task.ID}}/raw">{{ i18n("menu.raw") }}</a>
|
<a class="navbar-item is-tab is-active" href="/task/{{.Task.ID}}/raw">{{ i18n("menu.raw") }}</a>
|
||||||
|
<a class="navbar-item is-tab" href="/task/{{.Task.ID}}/logs">{{ i18n("menu.log") }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
Loading…
Reference in New Issue
Block a user