Update auxo to latest version

This commit is contained in:
cuigh 2017-11-08 18:36:13 +08:00
parent 9de2f2e62c
commit a958d7de24
42 changed files with 189 additions and 192 deletions

View File

@ -8,7 +8,7 @@ import (
func do(fn func(d dao.Interface)) { func do(fn func(d dao.Interface)) {
d, err := dao.Get() d, err := dao.Get()
if err != nil { if err != nil {
panic(errors.Wrap("failed to load storage engine", err)) panic(errors.Wrap(err, "failed to load storage engine"))
} }
fn(d) fn(d)

View File

@ -165,7 +165,7 @@ func StackRemove(name string) error {
} }
if len(errs) > 0 { if len(errs) > 0 {
return errors.Multi(errs...) return errors.List(errs...)
} }
return nil return nil
}) })
@ -264,7 +264,7 @@ func createNetworks(ctx context.Context, cli *client.Client, namespace compose.N
mgr.Logger().Infof("Creating network %s", name) mgr.Logger().Infof("Creating network %s", name)
if _, err = cli.NetworkCreate(ctx, name, createOpts); err != nil { if _, err = cli.NetworkCreate(ctx, name, createOpts); err != nil {
return errors.Wrap("failed to create network "+internalName, err) return errors.Wrap(err, "failed to create network "+internalName)
} }
} }
return nil return nil
@ -277,12 +277,12 @@ func createSecrets(ctx context.Context, cli *client.Client, secrets []swarm.Secr
case err == nil: case err == nil:
// secret already exists, then we update that // secret already exists, then we update that
if err = cli.SecretUpdate(ctx, secret.ID, secret.Meta.Version, secretSpec); err != nil { if err = cli.SecretUpdate(ctx, secret.ID, secret.Meta.Version, secretSpec); err != nil {
return errors.Wrap("failed to update secret "+secretSpec.Name, err) return errors.Wrap(err, "failed to update secret "+secretSpec.Name)
} }
case client.IsErrSecretNotFound(err): case client.IsErrSecretNotFound(err):
// secret does not exist, then we create a new one. // secret does not exist, then we create a new one.
if _, err = cli.SecretCreate(ctx, secretSpec); err != nil { if _, err = cli.SecretCreate(ctx, secretSpec); err != nil {
return errors.Wrap("failed to create secret "+secretSpec.Name, err) return errors.Wrap(err, "failed to create secret "+secretSpec.Name)
} }
default: default:
return err return err
@ -298,12 +298,12 @@ func createConfigs(ctx context.Context, cli *client.Client, configs []swarm.Conf
case err == nil: case err == nil:
// config already exists, then we update that // config already exists, then we update that
if err = cli.ConfigUpdate(ctx, config.ID, config.Meta.Version, configSpec); err != nil { if err = cli.ConfigUpdate(ctx, config.ID, config.Meta.Version, configSpec); err != nil {
errors.Wrap("failed to update config "+configSpec.Name, err) errors.Wrap(err, "failed to update config "+configSpec.Name)
} }
case client.IsErrConfigNotFound(err): case client.IsErrConfigNotFound(err):
// config does not exist, then we create a new one. // config does not exist, then we create a new one.
if _, err = cli.ConfigCreate(ctx, configSpec); err != nil { if _, err = cli.ConfigCreate(ctx, configSpec); err != nil {
errors.Wrap("failed to create config "+configSpec.Name, err) errors.Wrap(err, "failed to create config "+configSpec.Name)
} }
default: default:
return err return err
@ -377,7 +377,7 @@ func deployServices(
updateOpts, updateOpts,
) )
if err != nil { if err != nil {
return errors.Wrap("failed to update service "+name, err) return errors.Wrap(err, "failed to update service "+name)
} }
for _, warning := range response.Warnings { for _, warning := range response.Warnings {
@ -394,7 +394,7 @@ func deployServices(
//} //}
if _, err = cli.ServiceCreate(ctx, serviceSpec, createOpts); err != nil { if _, err = cli.ServiceCreate(ctx, serviceSpec, createOpts); err != nil {
return errors.Wrap("failed to create service "+name, err) return errors.Wrap(err, "failed to create service "+name)
} }
} }
} }

View File

@ -6,7 +6,6 @@ import (
"math" "math"
"sort" "sort"
"github.com/cuigh/auxo/util/choose"
"github.com/cuigh/swirl/misc" "github.com/cuigh/swirl/misc"
"github.com/cuigh/swirl/model" "github.com/cuigh/swirl/model"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
@ -57,7 +56,11 @@ func TaskList(args *model.TaskListArgs) (infos []*model.TaskInfo, totalCount int
for _, t := range tasks { for _, t := range tasks {
if _, ok := nodes[t.NodeID]; !ok { if _, ok := nodes[t.NodeID]; !ok {
if n, _, e := cli.NodeInspectWithRaw(ctx, t.NodeID); e == nil { if n, _, e := cli.NodeInspectWithRaw(ctx, t.NodeID); e == nil {
nodes[t.NodeID] = choose.String(n.Spec.Name == "", n.Description.Hostname, n.Spec.Name) if n.Spec.Name == "" {
nodes[t.NodeID] = n.Description.Hostname
} else {
nodes[t.NodeID] = n.Spec.Name
}
} else { } else {
nodes[t.NodeID] = "" nodes[t.NodeID] = ""
} }

View File

@ -288,9 +288,9 @@ func (b *userBiz) Identify(token string) (user web.User) {
} }
// Authorize check permission of user // Authorize check permission of user
func (b *userBiz) Authorize(user web.User, handler string) bool { func (b *userBiz) Authorize(user web.User, h web.HandlerInfo) bool {
if au, ok := user.(*model.AuthUser); ok { if au, ok := user.(*model.AuthUser); ok {
return au.IsAllowed(handler) return au.IsAllowed(h.Name())
} }
return false return false
} }

View File

@ -1,20 +1,20 @@
name: swirl name: swirl
banner: false
web: web:
address: ':8001' address: ':8001'
authorize_mode: '*' authorize: '?'
swirl: swirl:
db_type: mongo db_type: mongo
db_address: localhost:27017/swirl db_address: localhost:27017/swirl
# docker_endpoint: tcp://docker-proxy:2375 # docker_endpoint: tcp://docker-proxy:2375
#log: log:
# loggers: loggers:
# - level: info - level: info
# writers: console writers: console
# writers: writers:
# - name: console - name: console
# type: console type: console
# format: json layout: '[{L}]{T}: {M}{N}'
# layout: '[{L}]{T}: {M} - {F:S}{N}'

View File

@ -1,19 +1,20 @@
package controller package controller
import ( import (
"github.com/cuigh/auxo/data"
"github.com/cuigh/auxo/net/web" "github.com/cuigh/auxo/net/web"
"github.com/cuigh/swirl/model" "github.com/cuigh/swirl/model"
) )
func newModel(ctx web.Context) web.Map { func newModel(ctx web.Context) data.Map {
return web.Map{ return data.Map{
"ContextUser": ctx.User(), "ContextUser": ctx.User(),
} }
} }
func newPagerModel(ctx web.Context, totalCount, size, page int) web.Map { func newPagerModel(ctx web.Context, totalCount, size, page int) data.Map {
pager := model.NewPager(ctx.Request().RequestURI, totalCount, size, page) pager := model.NewPager(ctx.Request().RequestURI, totalCount, size, page)
return newModel(ctx).Add("Pager", pager) return newModel(ctx).Set("Pager", pager)
} }
func ajaxResult(ctx web.Context, err error) error { func ajaxResult(ctx web.Context, err error) error {
@ -21,14 +22,14 @@ func ajaxResult(ctx web.Context, err error) error {
return err return err
} }
return ctx.JSON(web.Map{ return ctx.JSON(data.Map{
"success": err == nil, "success": err == nil,
}) })
} }
func ajaxSuccess(ctx web.Context, data interface{}) error { func ajaxSuccess(ctx web.Context, value interface{}) error {
return ctx.JSON(web.Map{ return ctx.JSON(data.Map{
"success": true, "success": true,
"data": data, "data": value,
}) })
} }

View File

@ -41,8 +41,8 @@ func configList(ctx web.Context) error {
} }
m := newPagerModel(ctx, totalCount, model.PageSize, page). m := newPagerModel(ctx, totalCount, model.PageSize, page).
Add("Name", name). Set("Name", name).
Add("Configs", configs) Set("Configs", configs)
return ctx.Render("config/list", m) return ctx.Render("config/list", m)
} }
@ -75,7 +75,7 @@ func configEdit(ctx web.Context) error {
if err != nil { if err != nil {
return err return err
} }
m := newModel(ctx).Add("Config", cfg) m := newModel(ctx).Set("Config", cfg)
return ctx.Render("config/edit", m) return ctx.Render("config/edit", m)
} }

View File

@ -47,9 +47,9 @@ func containerList(ctx web.Context) error {
} }
m := newPagerModel(ctx, totalCount, model.PageSize, args.PageIndex). m := newPagerModel(ctx, totalCount, model.PageSize, args.PageIndex).
Add("Name", args.Name). Set("Name", args.Name).
Add("Filter", args.Filter). Set("Filter", args.Filter).
Add("Containers", containers) Set("Containers", containers)
return ctx.Render("container/list", m) return ctx.Render("container/list", m)
} }
@ -60,7 +60,7 @@ func containerDetail(ctx web.Context) error {
return err return err
} }
m := newModel(ctx).Add("Container", container) m := newModel(ctx).Set("Container", container)
return ctx.Render("container/detail", m) return ctx.Render("container/detail", m)
} }
@ -76,7 +76,7 @@ func containerRaw(ctx web.Context) error {
return err return err
} }
m := newModel(ctx).Add("Container", container).Add("Raw", j) m := newModel(ctx).Set("Container", container).Set("Raw", j)
return ctx.Render("container/raw", m) return ctx.Render("container/raw", m)
} }
@ -94,8 +94,8 @@ func containerLogs(ctx web.Context) error {
return err return err
} }
m := newModel(ctx).Add("Container", container).Add("Line", line).Add("Timestamps", timestamps). m := newModel(ctx).Set("Container", container).Set("Line", line).Set("Timestamps", timestamps).
Add("Stdout", stdout.String()).Add("Stderr", stderr.String()) Set("Stdout", stdout.String()).Set("Stderr", stderr.String())
return ctx.Render("container/logs", m) return ctx.Render("container/logs", m)
} }

View File

@ -35,6 +35,6 @@ func eventList(ctx web.Context) error {
} }
m := newPagerModel(ctx, totalCount, model.PageSize, args.PageIndex). m := newPagerModel(ctx, totalCount, model.PageSize, args.PageIndex).
Add("Events", events).Add("Args", args) Set("Events", events).Set("Args", args)
return ctx.Render("system/event/list", m) return ctx.Render("system/event/list", m)
} }

View File

@ -39,22 +39,22 @@ func homeIndex(ctx web.Context) (err error) {
if count, err = docker.NodeCount(); err != nil { if count, err = docker.NodeCount(); err != nil {
return return
} }
m.Add("NodeCount", count) m.Set("NodeCount", count)
if count, err = docker.NetworkCount(); err != nil { if count, err = docker.NetworkCount(); err != nil {
return return
} }
m.Add("NetworkCount", count) m.Set("NetworkCount", count)
if count, err = docker.ServiceCount(); err != nil { if count, err = docker.ServiceCount(); err != nil {
return return
} }
m.Add("ServiceCount", count) m.Set("ServiceCount", count)
if count, err = docker.StackCount(); err != nil { if count, err = docker.StackCount(); err != nil {
return return
} }
m.Add("StackCount", count) m.Set("StackCount", count)
return ctx.Render("index", m) return ctx.Render("index", m)
} }

View File

@ -37,8 +37,8 @@ func imageList(ctx web.Context) error {
} }
m := newPagerModel(ctx, totalCount, model.PageSize, page). m := newPagerModel(ctx, totalCount, model.PageSize, page).
Add("Name", name). Set("Name", name).
Add("Images", images) Set("Images", images)
return ctx.Render("image/list", m) return ctx.Render("image/list", m)
} }
@ -54,7 +54,7 @@ func imageDetail(ctx web.Context) error {
return err return err
} }
m := newModel(ctx).Add("Image", image).Add("Histories", histories) m := newModel(ctx).Set("Image", image).Set("Histories", histories)
return ctx.Render("image/detail", m) return ctx.Render("image/detail", m)
} }
@ -70,7 +70,7 @@ func imageRaw(ctx web.Context) error {
return err return err
} }
m := newModel(ctx).Add("Image", image).Add("Raw", j) m := newModel(ctx).Set("Image", image).Set("Raw", j)
return ctx.Render("image/raw", m) return ctx.Render("image/raw", m)
} }

View File

@ -38,7 +38,7 @@ func networkList(ctx web.Context) error {
return err return err
} }
m := newModel(ctx).Add("Networks", networks) m := newModel(ctx).Set("Networks", networks)
return ctx.Render("network/list", m) return ctx.Render("network/list", m)
} }
@ -85,7 +85,7 @@ func networkDetail(ctx web.Context) error {
if err != nil { if err != nil {
return err return err
} }
m := newModel(ctx).Add("Network", network) m := newModel(ctx).Set("Network", network)
return ctx.Render("network/detail", m) return ctx.Render("network/detail", m)
} }
@ -101,6 +101,6 @@ func networkRaw(ctx web.Context) error {
return err return err
} }
m := newModel(ctx).Add("Network", name).Add("Raw", j) m := newModel(ctx).Set("Network", name).Set("Raw", j)
return ctx.Render("network/raw", m) return ctx.Render("network/raw", m)
} }

View File

@ -35,7 +35,7 @@ func nodeList(ctx web.Context) error {
return err return err
} }
m := newModel(ctx).Add("Nodes", nodes) m := newModel(ctx).Set("Nodes", nodes)
return ctx.Render("node/list", m) return ctx.Render("node/list", m)
} }
@ -57,7 +57,7 @@ func nodeDetail(ctx web.Context) error {
return err return err
} }
m := newModel(ctx).Add("Node", node).Add("Tasks", tasks) m := newModel(ctx).Set("Node", node).Set("Tasks", tasks)
return ctx.Render("node/detail", m) return ctx.Render("node/detail", m)
} }
@ -73,7 +73,7 @@ func nodeRaw(ctx web.Context) error {
return err return err
} }
m := newModel(ctx).Add("ID", id).Add("Node", node).Add("Raw", j) m := newModel(ctx).Set("ID", id).Set("Node", node).Set("Raw", j)
return ctx.Render("node/raw", m) return ctx.Render("node/raw", m)
} }
@ -84,7 +84,7 @@ func nodeEdit(ctx web.Context) error {
return err return err
} }
m := newModel(ctx).Add("Node", node) m := newModel(ctx).Set("Node", node)
return ctx.Render("node/edit", m) return ctx.Render("node/edit", m)
} }

View File

@ -30,7 +30,7 @@ func profileIndex(ctx web.Context) error {
return err return err
} }
m := newModel(ctx).Add("User", user) m := newModel(ctx).Set("User", user)
return ctx.Render("profile/index", m) return ctx.Render("profile/index", m)
} }

View File

@ -30,7 +30,7 @@ func registryList(ctx web.Context) error {
return err return err
} }
m := newModel(ctx).Add("Registries", registries) m := newModel(ctx).Set("Registries", registries)
return ctx.Render("registry/list", m) return ctx.Render("registry/list", m)
} }

View File

@ -37,12 +37,12 @@ func roleIndex(ctx web.Context) error {
return err return err
} }
m := newModel(ctx).Add("Roles", roles) m := newModel(ctx).Set("Roles", roles)
return ctx.Render("system/role/list", m) return ctx.Render("system/role/list", m)
} }
func roleNew(ctx web.Context) error { func roleNew(ctx web.Context) error {
m := newModel(ctx).Add("Perms", misc.Perms) m := newModel(ctx).Set("Perms", misc.Perms)
return ctx.Render("system/role/new", m) return ctx.Render("system/role/new", m)
} }
@ -75,7 +75,7 @@ func roleDetail(ctx web.Context) error {
for _, p := range role.Perms { for _, p := range role.Perms {
perms[p] = model.Placeholder perms[p] = model.Placeholder
} }
m := newModel(ctx).Add("Role", role).Add("Perms", misc.Perms).Add("CheckedPerms", perms) m := newModel(ctx).Set("Role", role).Set("Perms", misc.Perms).Set("CheckedPerms", perms)
return ctx.Render("system/role/detail", m) return ctx.Render("system/role/detail", m)
} }
@ -93,7 +93,7 @@ func roleEdit(ctx web.Context) error {
for _, p := range role.Perms { for _, p := range role.Perms {
perms[p] = model.Placeholder perms[p] = model.Placeholder
} }
m := newModel(ctx).Add("Role", role).Add("Perms", misc.Perms).Add("CheckedPerms", perms) m := newModel(ctx).Set("Role", role).Set("Perms", misc.Perms).Set("CheckedPerms", perms)
return ctx.Render("system/role/edit", m) return ctx.Render("system/role/edit", m)
} }

View File

@ -40,8 +40,8 @@ func secretList(ctx web.Context) error {
} }
m := newPagerModel(ctx, totalCount, model.PageSize, page). m := newPagerModel(ctx, totalCount, model.PageSize, page).
Add("Name", name). Set("Name", name).
Add("Secrets", secrets) Set("Secrets", secrets)
return ctx.Render("secret/list", m) return ctx.Render("secret/list", m)
} }
@ -82,7 +82,7 @@ func secretEdit(ctx web.Context) error {
if err != nil { if err != nil {
return err return err
} }
m := newModel(ctx).Add("Secret", secret) m := newModel(ctx).Set("Secret", secret)
return ctx.Render("secret/edit", m) return ctx.Render("secret/edit", m)
} }

View File

@ -5,7 +5,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/cuigh/auxo/data/set"
"github.com/cuigh/auxo/errors" "github.com/cuigh/auxo/errors"
"github.com/cuigh/auxo/net/web" "github.com/cuigh/auxo/net/web"
"github.com/cuigh/auxo/util/cast" "github.com/cuigh/auxo/util/cast"
@ -13,6 +12,7 @@ import (
"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"
"mtime.com/auxo/data/set"
) )
// ServiceController is a controller of docker service // ServiceController is a controller of docker service
@ -56,8 +56,8 @@ func serviceList(ctx web.Context) error {
} }
m := newPagerModel(ctx, totalCount, model.PageSize, page). m := newPagerModel(ctx, totalCount, model.PageSize, page).
Add("Name", name). Set("Name", name).
Add("Services", services) Set("Services", services)
return ctx.Render("service/list", m) return ctx.Render("service/list", m)
} }
@ -87,7 +87,7 @@ func serviceDetail(ctx web.Context) error {
return err return err
} }
m := newModel(ctx).Add("Service", info).Add("Tasks", tasks).Add("Command", cmd) m := newModel(ctx).Set("Service", info).Set("Tasks", tasks).Set("Command", cmd)
return ctx.Render("service/detail", m) return ctx.Render("service/detail", m)
} }
@ -103,7 +103,7 @@ func serviceRaw(ctx web.Context) error {
return err return err
} }
m := newModel(ctx).Add("Service", name).Add("Raw", j) m := newModel(ctx).Set("Service", name).Set("Raw", j)
return ctx.Render("service/raw", m) return ctx.Render("service/raw", m)
} }
@ -116,8 +116,8 @@ func serviceLogs(ctx web.Context) error {
return err return err
} }
m := newModel(ctx).Add("Service", name).Add("Line", line).Add("Timestamps", timestamps). m := newModel(ctx).Set("Service", name).Set("Line", line).Set("Timestamps", timestamps).
Add("Stdout", stdout.String()).Add("Stderr", stderr.String()) Set("Stdout", stdout.String()).Set("Stderr", stderr.String())
return ctx.Render("service/logs", m) return ctx.Render("service/logs", m)
} }
@ -177,9 +177,9 @@ func serviceNew(ctx web.Context) error {
} }
checkedNetworks := set.FromSlice(service.Networks, func(i int) interface{} { return service.Networks[i] }) checkedNetworks := set.FromSlice(service.Networks, func(i int) interface{} { return service.Networks[i] })
m := newModel(ctx).Add("Service", service).Add("Registries", registries). m := newModel(ctx).Set("Service", service).Set("Registries", registries).
Add("Networks", networks).Add("CheckedNetworks", checkedNetworks). Set("Networks", networks).Set("CheckedNetworks", checkedNetworks).
Add("Secrets", secrets).Add("Configs", configs) Set("Secrets", secrets).Set("Configs", configs)
return ctx.Render("service/new", m) return ctx.Render("service/new", m)
} }
@ -194,9 +194,9 @@ func serviceCreate(ctx web.Context) error {
var registry *model.Registry var registry *model.Registry
registry, err = biz.Registry.Get(info.Registry) registry, err = biz.Registry.Get(info.Registry)
if err != nil { if err != nil {
return errors.Wrap("Load registry info failed", err) return errors.Wrap(err, "load registry info failed")
} else if registry == nil { } else if registry == nil {
return errors.New("Can't load registry info") return errors.New("can't load registry info")
} }
info.Image = registry.URL + "/" + info.Image info.Image = registry.URL + "/" + info.Image
@ -232,9 +232,9 @@ func serviceEdit(ctx web.Context) error {
stack := service.Spec.Labels["com.docker.stack.namespace"] stack := service.Spec.Labels["com.docker.stack.namespace"]
checkedNetworks := set.FromSlice(service.Endpoint.VirtualIPs, func(i int) interface{} { return service.Endpoint.VirtualIPs[i].NetworkID }) checkedNetworks := set.FromSlice(service.Endpoint.VirtualIPs, func(i int) interface{} { return service.Endpoint.VirtualIPs[i].NetworkID })
m := newModel(ctx).Add("Service", model.NewServiceInfo(service)).Add("Stack", stack). m := newModel(ctx).Set("Service", model.NewServiceInfo(service)).Set("Stack", stack).
Add("Networks", networks).Add("CheckedNetworks", checkedNetworks). Set("Networks", networks).Set("CheckedNetworks", checkedNetworks).
Add("Secrets", secrets).Add("Configs", configs) Set("Secrets", secrets).Set("Configs", configs)
return ctx.Render("service/edit", m) return ctx.Render("service/edit", m)
} }

View File

@ -26,7 +26,7 @@ func settingIndex(ctx web.Context) error {
return err return err
} }
m := newModel(ctx).Add("Setting", setting) m := newModel(ctx).Set("Setting", setting)
return ctx.Render("system/setting/index", m) return ctx.Render("system/setting/index", m)
} }

View File

@ -44,7 +44,7 @@ func stackTaskList(ctx web.Context) error {
return err return err
} }
m := newModel(ctx).Add("Stacks", stacks) m := newModel(ctx).Set("Stacks", stacks)
return ctx.Render("stack/task/list", m) return ctx.Render("stack/task/list", m)
} }
@ -74,8 +74,8 @@ func stackArchiveList(ctx web.Context) error {
} }
m := newPagerModel(ctx, totalCount, model.PageSize, args.PageIndex). m := newPagerModel(ctx, totalCount, model.PageSize, args.PageIndex).
Add("Name", args.Name). Set("Name", args.Name).
Add("Archives", archives) Set("Archives", archives)
return ctx.Render("stack/archive/list", m) return ctx.Render("stack/archive/list", m)
} }
@ -89,7 +89,7 @@ func stackArchiveDetail(ctx web.Context) error {
return web.ErrNotFound return web.ErrNotFound
} }
m := newModel(ctx).Add("Archive", archive) m := newModel(ctx).Set("Archive", archive)
return ctx.Render("stack/archive/detail", m) return ctx.Render("stack/archive/detail", m)
} }
@ -103,7 +103,7 @@ func stackArchiveEdit(ctx web.Context) error {
return web.ErrNotFound return web.ErrNotFound
} }
m := newModel(ctx).Add("Archive", archive) m := newModel(ctx).Set("Archive", archive)
return ctx.Render("stack/archive/edit", m) return ctx.Render("stack/archive/edit", m)
} }

View File

@ -40,8 +40,8 @@ func taskList(ctx web.Context) error {
} }
m := newPagerModel(ctx, totalCount, args.PageSize, args.PageIndex). m := newPagerModel(ctx, totalCount, args.PageSize, args.PageIndex).
Add("Args", args). Set("Args", args).
Add("Tasks", tasks) Set("Tasks", tasks)
return ctx.Render("task/list", m) return ctx.Render("task/list", m)
} }
@ -52,7 +52,7 @@ func taskDetail(ctx web.Context) error {
return err return err
} }
m := newModel(ctx).Add("Task", task) m := newModel(ctx).Set("Task", task)
return ctx.Render("task/detail", m) return ctx.Render("task/detail", m)
} }
@ -68,6 +68,6 @@ func taskRaw(ctx web.Context) error {
return err return err
} }
m := newModel(ctx).Add("Task", task).Add("Raw", j) m := newModel(ctx).Set("Task", task).Set("Raw", j)
return ctx.Render("task/raw", m) return ctx.Render("task/raw", m)
} }

View File

@ -3,7 +3,7 @@ package controller
import ( import (
"encoding/json" "encoding/json"
"github.com/cuigh/auxo/data/set" "github.com/cuigh/auxo/data"
"github.com/cuigh/auxo/net/web" "github.com/cuigh/auxo/net/web"
"github.com/cuigh/swirl/biz" "github.com/cuigh/swirl/biz"
"github.com/cuigh/swirl/biz/docker" "github.com/cuigh/swirl/biz/docker"
@ -49,8 +49,8 @@ func templateList(ctx web.Context) error {
} }
m := newPagerModel(ctx, totalCount, model.PageSize, args.PageIndex). m := newPagerModel(ctx, totalCount, model.PageSize, args.PageIndex).
Add("Name", args.Name). Set("Name", args.Name).
Add("Templates", tpls) Set("Templates", tpls)
return ctx.Render("service/template/list", m) return ctx.Render("service/template/list", m)
} }
@ -72,9 +72,9 @@ func templateNew(ctx web.Context) error {
if err != nil { if err != nil {
return err return err
} }
m := newModel(ctx).Add("Action", "New").Add("Service", service).Add("Registries", registries). m := newModel(ctx).Set("Action", "New").Set("Service", service).Set("Registries", registries).
Add("Networks", networks).Add("CheckedNetworks", set.Set{}). Set("Networks", networks).Set("CheckedNetworks", data.Set{}).
Add("Secrets", secrets).Add("Configs", configs) Set("Secrets", secrets).Set("Configs", configs)
return ctx.Render("service/template/edit", m) return ctx.Render("service/template/edit", m)
} }
@ -140,11 +140,13 @@ func templateEdit(ctx web.Context) error {
if err != nil { if err != nil {
return err return err
} }
checkedNetworks := set.FromSlice(service.Networks, func(i int) interface{} { return service.Networks[i] })
m := newModel(ctx).Add("Action", "Edit").Add("Service", service).Add("Registries", registries). checkedNetworks := data.NewSet()
Add("Networks", networks).Add("CheckedNetworks", checkedNetworks). checkedNetworks.AddSlice(service.Networks, func(i int) interface{} { return service.Networks[i] })
Add("Secrets", secrets).Add("Configs", configs)
m := newModel(ctx).Set("Action", "Edit").Set("Service", service).Set("Registries", registries).
Set("Networks", networks).Set("CheckedNetworks", checkedNetworks).
Set("Secrets", secrets).Set("Configs", configs)
return ctx.Render("service/template/edit", m) return ctx.Render("service/template/edit", m)
} }

View File

@ -52,9 +52,9 @@ func userIndex(ctx web.Context) error {
} }
m := newPagerModel(ctx, totalCount, model.PageSize, args.PageIndex). m := newPagerModel(ctx, totalCount, model.PageSize, args.PageIndex).
Add("Query", args.Query). Set("Query", args.Query).
Add("Filter", args.Filter). Set("Filter", args.Filter).
Add("Users", users) Set("Users", users)
return ctx.Render("system/user/list", m) return ctx.Render("system/user/list", m)
} }
@ -64,7 +64,7 @@ func userNew(ctx web.Context) error {
return err return err
} }
m := newModel(ctx).Add("Roles", roles) m := newModel(ctx).Set("Roles", roles)
return ctx.Render("system/user/new", m) return ctx.Render("system/user/new", m)
} }
@ -106,7 +106,7 @@ func userDetail(ctx web.Context) error {
} }
} }
m := newModel(ctx).Add("User", user).Add("Roles", roles) m := newModel(ctx).Set("User", user).Set("Roles", roles)
return ctx.Render("system/user/detail", m) return ctx.Render("system/user/detail", m)
} }
@ -129,7 +129,7 @@ func userEdit(ctx web.Context) error {
for _, id := range user.Roles { for _, id := range user.Roles {
userRoles[id] = model.Placeholder userRoles[id] = model.Placeholder
} }
m := newModel(ctx).Add("User", user).Add("Roles", roles).Add("UserRoles", userRoles) m := newModel(ctx).Set("User", user).Set("Roles", roles).Set("UserRoles", userRoles)
return ctx.Render("system/user/edit", m) return ctx.Render("system/user/edit", m)
} }

View File

@ -44,8 +44,8 @@ func volumeList(ctx web.Context) error {
} }
m := newPagerModel(ctx, totalCount, model.PageSize, page). m := newPagerModel(ctx, totalCount, model.PageSize, page).
//Add("Name", name). //Set("Name", name).
Add("Volumes", volumes) Set("Volumes", volumes)
return ctx.Render("volume/list", m) return ctx.Render("volume/list", m)
} }
@ -93,7 +93,7 @@ func volumeDetail(ctx web.Context) error {
if err != nil { if err != nil {
return err return err
} }
m := newModel(ctx).Add("Volume", volume) m := newModel(ctx).Set("Volume", volume)
return ctx.Render("volume/detail", m) return ctx.Render("volume/detail", m)
} }
@ -109,6 +109,6 @@ func volumeRaw(ctx web.Context) error {
return err return err
} }
m := newModel(ctx).Add("Volume", name).Add("Raw", j) m := newModel(ctx).Set("Volume", name).Set("Raw", j)
return ctx.Render("volume/raw", m) return ctx.Render("volume/raw", m)
} }

21
main.go
View File

@ -11,8 +11,8 @@ import (
"github.com/cuigh/auxo/app/flag" "github.com/cuigh/auxo/app/flag"
"github.com/cuigh/auxo/config" "github.com/cuigh/auxo/config"
"github.com/cuigh/auxo/net/web" "github.com/cuigh/auxo/net/web"
"github.com/cuigh/auxo/net/web/auth" "github.com/cuigh/auxo/net/web/filter"
"github.com/cuigh/auxo/net/web/middleware" "github.com/cuigh/auxo/net/web/filter/auth"
"github.com/cuigh/auxo/net/web/renderer/jet" "github.com/cuigh/auxo/net/web/renderer/jet"
"github.com/cuigh/swirl/biz" "github.com/cuigh/swirl/biz"
"github.com/cuigh/swirl/controller" "github.com/cuigh/swirl/controller"
@ -23,7 +23,7 @@ func main() {
misc.BindOptions() misc.BindOptions()
app.Name = "Swirl" app.Name = "Swirl"
app.Version = "0.6" app.Version = "0.6.1"
app.Desc = "A web management UI for Docker, focused on swarm cluster" app.Desc = "A web management UI for Docker, focused on swarm cluster"
app.Action = func(ctx *app.Context) { app.Action = func(ctx *app.Context) {
misc.LoadOptions() misc.LoadOptions()
@ -49,7 +49,7 @@ func server() *web.Server {
ws := web.Auto() ws := web.Auto()
// set render/validator.. // set render
ws.Renderer = jet.New().SetDebug(config.GetBool("debug")). ws.Renderer = jet.New().SetDebug(config.GetBool("debug")).
AddFunc("time", misc.FormatTime(setting.TimeZone.Offset)). AddFunc("time", misc.FormatTime(setting.TimeZone.Offset)).
AddFunc("i18n", misc.Message(setting.Language)). AddFunc("i18n", misc.Message(setting.Language)).
@ -57,17 +57,20 @@ func server() *web.Server {
AddVariable("language", setting.Language). AddVariable("language", setting.Language).
AddVariable("version", app.Version). AddVariable("version", app.Version).
AddVariable("go_version", runtime.Version()) AddVariable("go_version", runtime.Version())
//ws.Validator = valid.New()
// register global filters // register global filters
ws.Filter(middleware.Recover()) ws.Use(filter.NewRecover())
// register static handlers // register static handlers
ws.Static("", filepath.Join(filepath.Dir(app.GetPath()), "assets")) ws.Static("/assets", filepath.Join(filepath.Dir(app.GetPath()), "assets"))
// create biz group // create biz group
form := auth.NewForm(biz.User.Identify, &auth.FormConfig{Timeout: time.Minute * 30, SlidingExpiration: true}) form := &auth.Form{
g := ws.Group("", form.Middleware, middleware.Authorize(biz.User.Authorize)) Identifier: biz.User.Identify,
Timeout: time.Minute * 30,
SlidingExpiration: true,
}
g := ws.Group("", form, filter.NewAuthorizer(biz.User.Authorize))
// register auth handlers // register auth handlers
g.Post("/login", form.LoginJSON(biz.User.Login)).SetAuthorize(web.AuthAnonymous) g.Post("/login", form.LoginJSON(biz.User.Login)).SetAuthorize(web.AuthAnonymous)

View File

@ -12,12 +12,6 @@ import (
) )
var Funcs = map[string]interface{}{ var Funcs = map[string]interface{}{
"limit": func(s string, length int) string {
if len(s) > length {
return s[:length] + "..."
}
return s
},
//"time": func(t time.Time) string { //"time": func(t time.Time) string {
// return t.Local().Format("2006-01-02 15:04:05") // return t.Local().Format("2006-01-02 15:04:05")
//}, //},
@ -41,9 +35,6 @@ var Funcs = map[string]interface{}{
"trimPrefix": func(s, prefix string) string { "trimPrefix": func(s, prefix string) string {
return strings.TrimPrefix(s, prefix) return strings.TrimPrefix(s, prefix)
}, },
"slice": func(values ...interface{}) interface{} {
return values
},
} }
func Message(lang string) func(key string, args ...interface{}) string { func Message(lang string) func(key string, args ...interface{}) string {

View File

@ -50,10 +50,10 @@ type User struct {
type UserListArgs struct { type UserListArgs struct {
// admins, active, blocked // admins, active, blocked
Filter string `query:"filter"` Filter string `bind:"filter"`
Query string `query:"query"` Query string `bind:"query"`
PageIndex int `query:"page"` PageIndex int `bind:"page"`
PageSize int `query:"size"` PageSize int `bind:"size"`
} }
type Session struct { type Session struct {

View File

@ -9,7 +9,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/cuigh/auxo/data/size" "github.com/cuigh/auxo/byte/size"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/mount" "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/swarm"
@ -487,12 +487,12 @@ type ConfigUpdateInfo struct {
} }
type TaskListArgs struct { type TaskListArgs struct {
Service string `query:"service"` Service string `bind:"service"`
Node string `query:"node"` Node string `bind:"node"`
Name string `query:"name"` Name string `bind:"name"`
State string `query:"state"` State string `bind:"state"`
PageIndex int `query:"page"` PageIndex int `bind:"page"`
PageSize int `query:"size"` PageSize int `bind:"size"`
} }
type TaskInfo struct { type TaskInfo struct {
@ -594,10 +594,10 @@ func NewImageListInfo(image types.ImageSummary) *ImageListInfo {
type ContainerListArgs struct { type ContainerListArgs struct {
// created|restarting|running|removing|paused|exited|dead // created|restarting|running|removing|paused|exited|dead
Filter string `query:"filter"` Filter string `bind:"filter"`
Name string `query:"name"` Name string `bind:"name"`
PageIndex int `query:"page"` PageIndex int `bind:"page"`
PageSize int `query:"size"` PageSize int `bind:"size"`
} }
type ContainerListInfo struct { type ContainerListInfo struct {

View File

@ -8,7 +8,6 @@ import (
type EventType string type EventType string
const ( const (
// swarm
EventTypeRegistry EventType = "Registry" EventTypeRegistry EventType = "Registry"
EventTypeNode EventType = "Node" EventTypeNode EventType = "Node"
EventTypeNetwork EventType = "Network" EventTypeNetwork EventType = "Network"
@ -19,10 +18,8 @@ const (
EventTypeSecret EventType = "Secret" EventTypeSecret EventType = "Secret"
EventTypeConfig EventType = "Config" EventTypeConfig EventType = "Config"
// local
EventTypeVolume EventType = "Volume" EventTypeVolume EventType = "Volume"
// system
EventTypeAuthentication EventType = "Authentication" EventTypeAuthentication EventType = "Authentication"
EventTypeRole EventType = "Role" EventTypeRole EventType = "Role"
EventTypeUser EventType = "User" EventTypeUser EventType = "User"
@ -32,8 +29,8 @@ const (
type EventAction string type EventAction string
const ( const (
EventActionLogin EventAction = "Login" EventActionLogin EventAction = "Login"
EventActionLogout EventAction = "Logout" //EventActionLogout EventAction = "Logout"
EventActionCreate EventAction = "Create" EventActionCreate EventAction = "Create"
EventActionDelete EventAction = "Delete" EventActionDelete EventAction = "Delete"
EventActionUpdate EventAction = "Update" EventActionUpdate EventAction = "Update"
@ -78,8 +75,8 @@ func (e *Event) URL(et EventType, code string) string {
} }
type EventListArgs struct { type EventListArgs struct {
Type string `query:"type"` Type string `bind:"type"`
Name string `query:"name"` Name string `bind:"name"`
PageIndex int `query:"page"` PageIndex int `bind:"page"`
PageSize int `query:"size"` PageSize int `bind:"size"`
} }

View File

@ -13,9 +13,9 @@ type Archive struct {
} }
type ArchiveListArgs struct { type ArchiveListArgs struct {
Name string `query:"name"` Name string `bind:"name"`
PageIndex int `query:"page"` PageIndex int `bind:"page"`
PageSize int `query:"size"` PageSize int `bind:"size"`
} }
type Template struct { type Template struct {
@ -29,7 +29,7 @@ type Template struct {
} }
type TemplateListArgs struct { type TemplateListArgs struct {
Name string `query:"name"` Name string `bind:"name"`
PageIndex int `query:"page"` PageIndex int `bind:"page"`
PageSize int `query:"size"` PageSize int `bind:"size"`
} }

View File

@ -6,9 +6,9 @@
<meta name="keywords" content="swirl,docker,moby,swarm,docker-compose,docker-ui,docker-web-management"> <meta name="keywords" content="swirl,docker,moby,swarm,docker-compose,docker-ui,docker-web-management">
<meta name="description" content="{{ i18n("description") }}"> <meta name="description" content="{{ i18n("description") }}">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="/lyicon/css/lyicon.css"> <link rel="stylesheet" href="/assets/lyicon/css/lyicon.css">
<link rel="stylesheet" href="/bulma/bulma.css?v=0.5.3"> <link rel="stylesheet" href="/assets/bulma/bulma.css?v=0.5.3">
<link rel="stylesheet" href="/swirl/css/swirl.css?v={{ version }}"> <link rel="stylesheet" href="/assets/swirl/css/swirl.css?v={{ version }}">
{{ block style() }}{{end}} {{ block style() }}{{end}}
</head> </head>
<body> <body>
@ -16,7 +16,7 @@
<nav class="navbar"> <nav class="navbar">
<div class="navbar-brand"> <div class="navbar-brand">
<a class="navbar-item" href="/"> <a class="navbar-item" href="/">
<img src="/swirl/img/logo.png" alt="logo" height="28"> <img src="/assets/swirl/img/logo.png" alt="logo" height="28">
</a> </a>
<div class="navbar-burger" data-target="menu-top"> <div class="navbar-burger" data-target="menu-top">
<span></span> <span></span>
@ -99,8 +99,8 @@
</div> </div>
</div> </div>
</footer> </footer>
<script src="/jquery/jquery-3.2.1.min.js"></script> <script src="/assets/jquery/jquery-3.2.1.min.js"></script>
<script src="/swirl/js/swirl.js?v={{ version }}"></script> <script src="/assets/swirl/js/swirl.js?v={{ version }}"></script>
{{ block script() }}{{end}} {{ block script() }}{{end}}
</body> </body>
</html> </html>

View File

@ -6,13 +6,13 @@
<meta name="keywords" content="swirl,docker,moby,swarm,docker-compose,docker-ui,docker-web-management"> <meta name="keywords" content="swirl,docker,moby,swarm,docker-compose,docker-ui,docker-web-management">
<meta name="description" content="{{ i18n("description") }}"> <meta name="description" content="{{ i18n("description") }}">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="/lyicon/css/lyicon.css"> <link rel="stylesheet" href="/assets/lyicon/css/lyicon.css">
<link rel="stylesheet" href="/bulma/bulma.css?v=0.5.3"> <link rel="stylesheet" href="/assets/bulma/bulma.css?v=0.5.3">
<link rel="stylesheet" href="/swirl/css/swirl.css?v={{ version }}"> <link rel="stylesheet" href="/assets/swirl/css/swirl.css?v={{ version }}">
</head> </head>
<body> <body>
{{ yield body() }} {{ yield body() }}
<script src="/jquery/jquery-3.2.1.min.js"></script> <script src="/assets/jquery/jquery-3.2.1.min.js"></script>
<script src="/swirl/js/swirl.js?v={{ version }}"></script> <script src="/assets/swirl/js/swirl.js?v={{ version }}"></script>
</body> </body>
</html> </html>

View File

@ -1,11 +1,11 @@
{{ extends "../_layouts/default" }} {{ extends "../_layouts/default" }}
{{ block style() }} {{ block style() }}
<link rel="stylesheet" href="/highlight/highlight.css?v=9.12"> <link rel="stylesheet" href="/assets/highlight/highlight.css?v=9.12">
{{ end }} {{ end }}
{{ block script() }} {{ block script() }}
<script src="/highlight/highlight.pack.js?v=9.12"></script> <script src="/assets/highlight/highlight.pack.js?v=9.12"></script>
<script>hljs.initHighlightingOnLoad();</script> <script>hljs.initHighlightingOnLoad();</script>
{{ end }} {{ end }}

View File

@ -1,11 +1,11 @@
{{ extends "../_layouts/default" }} {{ extends "../_layouts/default" }}
{{ block style() }} {{ block style() }}
<link rel="stylesheet" href="/highlight/highlight.css?v=9.12"> <link rel="stylesheet" href="/assets/highlight/highlight.css?v=9.12">
{{ end }} {{ end }}
{{ block script() }} {{ block script() }}
<script src="/highlight/highlight.pack.js?v=9.12"></script> <script src="/assets/highlight/highlight.pack.js?v=9.12"></script>
<script>hljs.initHighlightingOnLoad();</script> <script>hljs.initHighlightingOnLoad();</script>
{{ end }} {{ end }}

View File

@ -1,11 +1,11 @@
{{ extends "../_layouts/default" }} {{ extends "../_layouts/default" }}
{{ block style() }} {{ block style() }}
<link rel="stylesheet" href="/highlight/highlight.css?v=9.12"> <link rel="stylesheet" href="/assets/highlight/highlight.css?v=9.12">
{{ end }} {{ end }}
{{ block script() }} {{ block script() }}
<script src="/highlight/highlight.pack.js?v=9.12"></script> <script src="/assets/highlight/highlight.pack.js?v=9.12"></script>
<script>hljs.initHighlightingOnLoad();</script> <script>hljs.initHighlightingOnLoad();</script>
{{ end }} {{ end }}

View File

@ -1,11 +1,11 @@
{{ extends "../_layouts/default" }} {{ extends "../_layouts/default" }}
{{ block style() }} {{ block style() }}
<link rel="stylesheet" href="/highlight/highlight.css?v=9.12"> <link rel="stylesheet" href="/assets/highlight/highlight.css?v=9.12">
{{ end }} {{ end }}
{{ block script() }} {{ block script() }}
<script src="/highlight/highlight.pack.js?v=9.12"></script> <script src="/assets/highlight/highlight.pack.js?v=9.12"></script>
<script>hljs.initHighlightingOnLoad();</script> <script>hljs.initHighlightingOnLoad();</script>
{{ end }} {{ end }}

View File

@ -1,11 +1,11 @@
{{ extends "_base" }} {{ extends "_base" }}
{{ block style() }} {{ block style() }}
<link rel="stylesheet" href="/highlight/highlight.css?v=9.12"> <link rel="stylesheet" href="/assets/highlight/highlight.css?v=9.12">
{{ end }} {{ end }}
{{ block script() }} {{ block script() }}
<script src="/highlight/highlight.pack.js?v=9.12"></script> <script src="/assets/highlight/highlight.pack.js?v=9.12"></script>
<script>hljs.initHighlightingOnLoad();</script> <script>hljs.initHighlightingOnLoad();</script>
{{ end }} {{ end }}

View File

@ -1,11 +1,11 @@
{{ extends "../../_layouts/default" }} {{ extends "../../_layouts/default" }}
{{ block style() }} {{ block style() }}
<link rel="stylesheet" href="/highlight/highlight.css?v=9.12"> <link rel="stylesheet" href="/assets/highlight/highlight.css?v=9.12">
{{ end }} {{ end }}
{{ block script() }} {{ block script() }}
<script src="/highlight/highlight.pack.js?v=9.12"></script> <script src="/assets/highlight/highlight.pack.js?v=9.12"></script>
<script>hljs.initHighlightingOnLoad();</script> <script>hljs.initHighlightingOnLoad();</script>
{{ end }} {{ end }}

View File

@ -1,12 +1,12 @@
{{ extends "../../_layouts/default" }} {{ extends "../../_layouts/default" }}
{{ block style() }} {{ block style() }}
<link rel="stylesheet" href="/codemirror/codemirror.css?v=5.30"> <link rel="stylesheet" href="/assets/codemirror/codemirror.css?v=5.30">
{{ end }} {{ end }}
{{ block script() }} {{ block script() }}
<script src="/codemirror/codemirror.js?v=5.30"></script> <script src="/assets/codemirror/codemirror.js?v=5.30"></script>
<script src="/codemirror/mode/yaml.js?v=5.30"></script> <script src="/assets/codemirror/mode/yaml.js?v=5.30"></script>
<script>var editor = CodeMirror.fromTextArea(document.getElementById("txt-content"), {lineNumbers: true});</script> <script>var editor = CodeMirror.fromTextArea(document.getElementById("txt-content"), {lineNumbers: true});</script>
{{ end }} {{ end }}

View File

@ -1,12 +1,12 @@
{{ extends "../../_layouts/default" }} {{ extends "../../_layouts/default" }}
{{ block style() }} {{ block style() }}
<link rel="stylesheet" href="/codemirror/codemirror.css?v=5.30"> <link rel="stylesheet" href="/assets/codemirror/codemirror.css?v=5.30">
{{ end }} {{ end }}
{{ block script() }} {{ block script() }}
<script src="/codemirror/codemirror.js?v=5.30"></script> <script src="/assets/codemirror/codemirror.js?v=5.30"></script>
<script src="/codemirror/mode/yaml.js?v=5.30"></script> <script src="/assets/codemirror/mode/yaml.js?v=5.30"></script>
<script>var editor = CodeMirror.fromTextArea(document.getElementById("txt-content"), {lineNumbers: true});</script> <script>var editor = CodeMirror.fromTextArea(document.getElementById("txt-content"), {lineNumbers: true});</script>
{{ end }} {{ end }}

View File

@ -1,11 +1,11 @@
{{ extends "_base" }} {{ extends "_base" }}
{{ block style() }} {{ block style() }}
<link rel="stylesheet" href="/highlight/highlight.css?v=9.12"> <link rel="stylesheet" href="/assets/highlight/highlight.css?v=9.12">
{{ end }} {{ end }}
{{ block script() }} {{ block script() }}
<script src="/highlight/highlight.pack.js?v=9.12"></script> <script src="/assets/highlight/highlight.pack.js?v=9.12"></script>
<script>hljs.initHighlightingOnLoad();</script> <script>hljs.initHighlightingOnLoad();</script>
{{ end }} {{ end }}

View File

@ -1,11 +1,11 @@
{{ extends "../_layouts/default" }} {{ extends "../_layouts/default" }}
{{ block style() }} {{ block style() }}
<link rel="stylesheet" href="/highlight/highlight.css?v=9.12"> <link rel="stylesheet" href="/assets/highlight/highlight.css?v=9.12">
{{ end }} {{ end }}
{{ block script() }} {{ block script() }}
<script src="/highlight/highlight.pack.js?v=9.12"></script> <script src="/assets/highlight/highlight.pack.js?v=9.12"></script>
<script>hljs.initHighlightingOnLoad();</script> <script>hljs.initHighlightingOnLoad();</script>
{{ end }} {{ end }}