mirror of
https://github.com/cuigh/swirl
synced 2024-12-28 14:51:57 +00:00
Add task list page
This commit is contained in:
parent
e862737699
commit
6d820149cf
@ -2,10 +2,12 @@ package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"io"
|
||||
|
||||
"github.com/cuigh/auxo/util/choose"
|
||||
"github.com/cuigh/swirl/misc"
|
||||
"github.com/cuigh/swirl/model"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
@ -14,35 +16,50 @@ import (
|
||||
)
|
||||
|
||||
// TaskList return all running tasks of a service or a node.
|
||||
func TaskList(service, node string) (infos []*model.TaskInfo, err error) {
|
||||
func TaskList(args *model.TaskListArgs) (infos []*model.TaskInfo, totalCount int, err error) {
|
||||
err = mgr.Do(func(ctx context.Context, cli *client.Client) (err error) {
|
||||
var (
|
||||
tasks []swarm.Task
|
||||
opts = types.TaskListOptions{
|
||||
Filters: filters.NewArgs(),
|
||||
}
|
||||
)
|
||||
|
||||
opts := types.TaskListOptions{
|
||||
Filters: filters.NewArgs(),
|
||||
if args.PageIndex < 1 {
|
||||
args.PageIndex = 1
|
||||
}
|
||||
if service != "" {
|
||||
opts.Filters.Add("service", service)
|
||||
if args.PageSize < 1 {
|
||||
args.PageSize = math.MaxInt32
|
||||
}
|
||||
if node != "" {
|
||||
opts.Filters.Add("node", node)
|
||||
if args.Service != "" {
|
||||
opts.Filters.Add("service", args.Service)
|
||||
}
|
||||
if args.Node != "" {
|
||||
opts.Filters.Add("node", args.Node)
|
||||
}
|
||||
if args.Name != "" {
|
||||
opts.Filters.Add("name", args.Name)
|
||||
}
|
||||
if args.State != "" {
|
||||
opts.Filters.Add("desired-state", args.State)
|
||||
}
|
||||
|
||||
tasks, err = cli.TaskList(ctx, opts)
|
||||
if err == nil && len(tasks) > 0 {
|
||||
totalCount = len(tasks)
|
||||
if err == nil && totalCount > 0 {
|
||||
sort.Slice(tasks, func(i, j int) bool {
|
||||
return tasks[i].UpdatedAt.After(tasks[j].UpdatedAt)
|
||||
})
|
||||
start, end := misc.Page(totalCount, args.PageIndex, args.PageSize)
|
||||
tasks = tasks[start:end]
|
||||
|
||||
nodes := make(map[string]string)
|
||||
for _, t := range tasks {
|
||||
if _, ok := nodes[t.NodeID]; !ok {
|
||||
if n, _, e := cli.NodeInspectWithRaw(ctx, t.NodeID); e == nil {
|
||||
nodes[t.NodeID] = n.Description.Hostname
|
||||
nodes[t.NodeID] = choose.String(n.Spec.Name == "", n.Description.Hostname, n.Spec.Name)
|
||||
} else {
|
||||
nodes[t.NodeID] = ""
|
||||
//mgr.Logger().Warnf("Node %s of task %s can't be load: %s", t.NodeID, t.ID, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -132,6 +132,10 @@ stack.title: Stack
|
||||
stack.description: A stack is a logical grouping of related services that are usually deployed together and require each other to work as intended.
|
||||
stack.button.deploy: Deploy
|
||||
|
||||
# task pages
|
||||
task.title: Task
|
||||
task.description: A task is a container running on a swarm. It is the atomic scheduling unit of swarm.
|
||||
|
||||
# secret pages
|
||||
secret.title: Secret
|
||||
secret.description: Secrets are sensitive data that can be used by services.
|
||||
|
@ -132,6 +132,10 @@ stack.title: 编排
|
||||
stack.description: 编排是相关服务的一个逻辑分组,这些服务通常互相依赖,需要一块部署。
|
||||
stack.button.deploy: 部署
|
||||
|
||||
# task pages
|
||||
task.title: 任务
|
||||
task.description: 任务是运行在 Swarm 集群上的一个容器,它是 Swarm 调度的原子单元。
|
||||
|
||||
# secret pages
|
||||
secret.title: 私密配置
|
||||
secret.description: 运行服务需要的敏感数据,如密钥。
|
||||
|
@ -52,7 +52,7 @@ func nodeDetail(ctx web.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
tasks, err := docker.TaskList("", id)
|
||||
tasks, _, err := docker.TaskList(&model.TaskListArgs{Node: id})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ func serviceDetail(ctx web.Context) error {
|
||||
info.Networks = append(info.Networks, model.Network{ID: vip.NetworkID, Name: n.Name, Address: vip.Addr})
|
||||
}
|
||||
|
||||
tasks, err := docker.TaskList(name, "")
|
||||
tasks, _, err := docker.TaskList(&model.TaskListArgs{Service: name})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -4,10 +4,12 @@ import (
|
||||
"github.com/cuigh/auxo/net/web"
|
||||
"github.com/cuigh/swirl/biz/docker"
|
||||
"github.com/cuigh/swirl/misc"
|
||||
"github.com/cuigh/swirl/model"
|
||||
)
|
||||
|
||||
// TaskController is a controller of swarm task
|
||||
type TaskController struct {
|
||||
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"`
|
||||
Raw web.HandlerFunc `path:"/:id/raw" name:"task.raw" authorize:"!" desc:"task raw page"`
|
||||
}
|
||||
@ -15,11 +17,34 @@ type TaskController struct {
|
||||
// Task creates an instance of TaskController
|
||||
func Task() (c *TaskController) {
|
||||
return &TaskController{
|
||||
List: taskList,
|
||||
Detail: taskDetail,
|
||||
Raw: taskRaw,
|
||||
}
|
||||
}
|
||||
|
||||
func taskList(ctx web.Context) error {
|
||||
args := &model.TaskListArgs{}
|
||||
err := ctx.Bind(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
args.PageSize = model.PageSize
|
||||
if args.PageIndex == 0 {
|
||||
args.PageIndex = 1
|
||||
}
|
||||
|
||||
tasks, totalCount, err := docker.TaskList(args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m := newPagerModel(ctx, totalCount, args.PageSize, args.PageIndex).
|
||||
Add("Args", args).
|
||||
Add("Tasks", tasks)
|
||||
return ctx.Render("task/list", m)
|
||||
}
|
||||
|
||||
func taskDetail(ctx web.Context) error {
|
||||
id := ctx.P("id")
|
||||
task, _, err := docker.TaskInspect(id)
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
|
||||
const (
|
||||
// Version is the version of Swirl
|
||||
Version = "0.5.5"
|
||||
Version = "0.5.6"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -87,6 +87,7 @@ var Perms = []PermGroup{
|
||||
{
|
||||
Name: "Task",
|
||||
Perms: []Perm{
|
||||
{Key: "task.list", Text: "View list"},
|
||||
{Key: "task.detail", Text: "View detail"},
|
||||
{Key: "task.raw", Text: "View raw"},
|
||||
},
|
||||
|
@ -478,6 +478,15 @@ type ConfigUpdateInfo struct {
|
||||
ConfigCreateInfo
|
||||
}
|
||||
|
||||
type TaskListArgs struct {
|
||||
Service string `query:"service"`
|
||||
Node string `query:"node"`
|
||||
Name string `query:"name"`
|
||||
State string `query:"state"`
|
||||
PageIndex int `query:"page"`
|
||||
PageSize int `query:"size"`
|
||||
}
|
||||
|
||||
type TaskInfo struct {
|
||||
swarm.Task
|
||||
NodeName string
|
||||
|
@ -39,6 +39,7 @@
|
||||
<a class="navbar-item" href="/node/">{{ i18n("menu.node") }}</a>
|
||||
<a class="navbar-item" href="/network/">{{ i18n("menu.network") }}</a>
|
||||
<a class="navbar-item" href="/service/">{{ i18n("menu.service") }}</a>
|
||||
<a class="navbar-item" href="/task/">{{ i18n("menu.task") }}</a>
|
||||
<a class="navbar-item" href="/stack/task/">{{ i18n("menu.stack") }}</a>
|
||||
<a class="navbar-item" href="/secret/">{{ i18n("menu.secret") }}</a>
|
||||
<a class="navbar-item" href="/config/">{{ i18n("menu.config") }}</a>
|
||||
|
13
views/task/_base.jet
Normal file
13
views/task/_base.jet
Normal file
@ -0,0 +1,13 @@
|
||||
{{ 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("task.title") }}</h1>
|
||||
<h2 class="subtitle is-5">{{ i18n("task.description") }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{ yield body_content() }}
|
||||
{{ end }}
|
@ -1,23 +1,12 @@
|
||||
{{ extends "../_layouts/default" }}
|
||||
{{ extends "_base" }}
|
||||
|
||||
{{ block body() }}
|
||||
<section class="hero is-info">
|
||||
<div class="hero-body">
|
||||
<div class="container has-text-centered">
|
||||
<h1 class="title is-2">
|
||||
TASK
|
||||
</h1>
|
||||
<h2 class="subtitle is-5">
|
||||
A task is a container running on a swarm. It is the atomic scheduling unit of swarm.
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{ block body_content() }}
|
||||
<div class="container">
|
||||
<nav class="breadcrumb has-succeeds-separator is-small is-marginless" aria-label="breadcrumbs">
|
||||
<ul>
|
||||
<li><a href="/">Dashboard</a></li>
|
||||
<li class="is-active"><a>Detail</a></li>
|
||||
<li><a href="/">{{ i18n("menu.dashboard") }}</a></li>
|
||||
<li><a href="/task/">{{ i18n("menu.task") }}</a></li>
|
||||
<li class="is-active"><a>{{ i18n("menu.detail") }}</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
@ -26,19 +15,15 @@
|
||||
<div class="container">
|
||||
<h2 class="title is-2">
|
||||
{{ .Task.Name ? .Task.Name : .Task.ID }}
|
||||
</h2>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<nav class="navbar has-shadow">
|
||||
<div class="container">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item is-tab is-active" href="/task/{{.Task.ID}}/detail">
|
||||
Detail
|
||||
</a>
|
||||
<a class="navbar-item is-tab " href="/task/{{.Task.ID}}/raw">
|
||||
Raw
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@ -104,6 +89,10 @@
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
<a href="/task/" class="button is-primary">
|
||||
<span class="icon"><i class="fa fa-reply"></i></span>
|
||||
<span>{{ i18n("button.return") }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
{{ end }}
|
73
views/task/list.jet
Normal file
73
views/task/list.jet
Normal file
@ -0,0 +1,73 @@
|
||||
{{ extends "_base" }}
|
||||
{{ import "../_modules/pager" }}
|
||||
|
||||
{{ block body_content() }}
|
||||
<section class="section">
|
||||
<nav class="level">
|
||||
<!-- Left side -->
|
||||
<div class="level-left">
|
||||
<div class="level-item">
|
||||
<form>
|
||||
<div class="field has-addons">
|
||||
<p class="control">
|
||||
<input name="service" value="{{ .Args.Service }}" class="input" placeholder="Search by service">
|
||||
</p>
|
||||
<p class="control">
|
||||
<button class="button is-primary">{{ i18n("button.search") }}</button>
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="level-item">
|
||||
<p class="subtitle is-5">
|
||||
<strong>{{.Pager.Count}}</strong>
|
||||
<span class="is-lowercase">{{ i18n("menu.task") }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Right side -->
|
||||
<div class="level-right">
|
||||
<p class="level-item">
|
||||
{{if .Args.State == ""}}
|
||||
<strong>All</strong>
|
||||
{{else}}
|
||||
<a href="/task/">All</a>
|
||||
{{end}}
|
||||
</p>
|
||||
<p class="level-item">
|
||||
{{if .Args.State == "running"}}
|
||||
<strong>Running</strong>
|
||||
{{else}}
|
||||
<a href="?state=running">Running</a>
|
||||
{{end}}
|
||||
</p>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<table id="table-items" class="table is-bordered is-striped is-narrow is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ i18n("field.id") }}</th>
|
||||
<th>Service</th>
|
||||
<th>{{ i18n("field.image") }}</th>
|
||||
<th>Node</th>
|
||||
<th>{{ i18n("field.state") }}</th>
|
||||
<th>{{ i18n("field.created-at") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Tasks}}
|
||||
<tr>
|
||||
<td><a href="{{ .ID }}/detail">{{ .ID }}</a></td>
|
||||
<td><a href="/service/{{ .ServiceID }}/detail">{{ .ServiceID }}</a></td>
|
||||
<td>{{ limit(.Image, 50) }}</td>
|
||||
<td><a href="/node/{{ .NodeID }}/detail">{{ .NodeName }}</a></td>
|
||||
<td><span class="tag is-{{ .Status.State == "running" ? "success" : "danger" }}">{{ .Status.State }}</span></td>
|
||||
<td>{{ time(.CreatedAt) }}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{ yield pager(info=.Pager) }}
|
||||
</section>
|
||||
{{ end }}
|
@ -1,4 +1,4 @@
|
||||
{{ extends "../_layouts/default" }}
|
||||
{{ extends "_base" }}
|
||||
|
||||
{{ block style() }}
|
||||
<link rel="stylesheet" href="/highlight/highlight.css?v=9.12">
|
||||
@ -9,24 +9,13 @@
|
||||
<script>hljs.initHighlightingOnLoad();</script>
|
||||
{{ end }}
|
||||
|
||||
{{ block body() }}
|
||||
<section class="hero is-info">
|
||||
<div class="hero-body">
|
||||
<div class="container has-text-centered">
|
||||
<h1 class="title is-2">
|
||||
TASK
|
||||
</h1>
|
||||
<h2 class="subtitle is-5">
|
||||
A task is a container running on a swarm. It is the atomic scheduling unit of swarm.
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{{ block body_content() }}
|
||||
<div class="container">
|
||||
<nav class="breadcrumb has-succeeds-separator is-small is-marginless" aria-label="breadcrumbs">
|
||||
<ul>
|
||||
<li><a href="/">Dashboard</a></li>
|
||||
<li class="is-active"><a>Raw</a></li>
|
||||
<li><a href="/">{{ i18n("menu.dashboard") }}</a></li>
|
||||
<li><a href="/task/">{{ i18n("menu.task") }}</a></li>
|
||||
<li class="is-active"><a>{{ i18n("menu.raw") }}</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
@ -42,12 +31,8 @@
|
||||
<nav class="navbar has-shadow">
|
||||
<div class="container">
|
||||
<div class="navbar-brand">
|
||||
<a class="navbar-item is-tab" href="/task/{{.Task.ID}}/detail">
|
||||
Detail
|
||||
</a>
|
||||
<a class="navbar-item is-tab is-active" href="/task/{{.Task.ID}}/raw">
|
||||
Raw
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@ -56,6 +41,10 @@
|
||||
<div class="content">
|
||||
<pre class="is-paddingless"><code class="json">{{ .Raw }}</code></pre>
|
||||
</div>
|
||||
<a href="/task/" class="button is-primary">
|
||||
<span class="icon"><i class="fa fa-reply"></i></span>
|
||||
<span>{{ i18n("button.return") }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
{{ end }}
|
Loading…
Reference in New Issue
Block a user