mirror of
https://github.com/cuigh/swirl
synced 2024-12-28 23:02:02 +00:00
Combine stack tasks with archives
This commit is contained in:
parent
c624b1f9bd
commit
f2a4b7209c
@ -2813,4 +2813,111 @@ var Swirl;
|
|||||||
Volume.NewPage = NewPage;
|
Volume.NewPage = NewPage;
|
||||||
})(Volume = Swirl.Volume || (Swirl.Volume = {}));
|
})(Volume = Swirl.Volume || (Swirl.Volume = {}));
|
||||||
})(Swirl || (Swirl = {}));
|
})(Swirl || (Swirl = {}));
|
||||||
|
var Swirl;
|
||||||
|
(function (Swirl) {
|
||||||
|
var Stack;
|
||||||
|
(function (Stack) {
|
||||||
|
var Validator = Swirl.Core.Validator;
|
||||||
|
var Notification = Swirl.Core.Notification;
|
||||||
|
class ContentRequiredRule {
|
||||||
|
validate($form, $input, arg) {
|
||||||
|
let el = $input[0];
|
||||||
|
if ($("#type-" + arg).prop("checked")) {
|
||||||
|
console.log(el.value);
|
||||||
|
return { ok: el.checkValidity ? el.checkValidity() : true, error: el.validationMessage };
|
||||||
|
}
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class EditPage {
|
||||||
|
constructor() {
|
||||||
|
Validator.register("content", new ContentRequiredRule(), "");
|
||||||
|
this.editor = CodeMirror.fromTextArea($("#txt-content")[0], { lineNumbers: true });
|
||||||
|
$("#file-content").change(e => {
|
||||||
|
let file = e.target;
|
||||||
|
if (file.files.length > 0) {
|
||||||
|
$('#filename').text(file.files[0].name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$("#type-input,#type-upload").click(e => {
|
||||||
|
let type = $(e.target).val();
|
||||||
|
$("#div-input").toggle(type == "input");
|
||||||
|
$("#div-upload").toggle(type == "upload");
|
||||||
|
});
|
||||||
|
$("#btn-submit").click(this.submit.bind(this));
|
||||||
|
}
|
||||||
|
submit(e) {
|
||||||
|
this.editor.save();
|
||||||
|
let results = Validator.bind("#div-form").validate();
|
||||||
|
if (results.length > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let data = new FormData();
|
||||||
|
data.append('name', $("#name").val());
|
||||||
|
if ($("#type-input").prop("checked")) {
|
||||||
|
data.append('content', $('#txt-content').val());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let file = $('#file-content')[0];
|
||||||
|
data.append('content', file.files[0]);
|
||||||
|
}
|
||||||
|
let url = $(e.target).data("url") || "";
|
||||||
|
$ajax.post(url, data).encoder("none").trigger(e.target).json((r) => {
|
||||||
|
if (r.success) {
|
||||||
|
location.href = "/stack/";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Notification.show("danger", `FAILED: ${r.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Stack.EditPage = EditPage;
|
||||||
|
})(Stack = Swirl.Stack || (Swirl.Stack = {}));
|
||||||
|
})(Swirl || (Swirl = {}));
|
||||||
|
var Swirl;
|
||||||
|
(function (Swirl) {
|
||||||
|
var Stack;
|
||||||
|
(function (Stack) {
|
||||||
|
var Modal = Swirl.Core.Modal;
|
||||||
|
var Dispatcher = Swirl.Core.Dispatcher;
|
||||||
|
class ListPage {
|
||||||
|
constructor() {
|
||||||
|
let dispatcher = Dispatcher.bind("#table-items");
|
||||||
|
dispatcher.on("deploy-stack", this.deployStack.bind(this));
|
||||||
|
dispatcher.on("shutdown-stack", this.shutdownStack.bind(this));
|
||||||
|
dispatcher.on("delete-stack", this.deleteStack.bind(this));
|
||||||
|
}
|
||||||
|
deployStack(e) {
|
||||||
|
let $tr = $(e.target).closest("tr");
|
||||||
|
let name = $tr.find("td:first").text().trim();
|
||||||
|
Modal.confirm(`Are you sure to deploy stack: <strong>${name}</strong>?`, "Deploy stack", (dlg, e) => {
|
||||||
|
$ajax.post(`${name}/deploy`).trigger(e.target).json(r => {
|
||||||
|
location.reload();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
shutdownStack(e) {
|
||||||
|
let $tr = $(e.target).closest("tr");
|
||||||
|
let name = $tr.find("td:first").text().trim();
|
||||||
|
Modal.confirm(`Are you sure to shutdown stack: <strong>${name}</strong>?`, "Shutdown stack", (dlg, e) => {
|
||||||
|
$ajax.post(`${name}/shutdown`).trigger(e.target).json(r => {
|
||||||
|
location.reload();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
deleteStack(e) {
|
||||||
|
let $tr = $(e.target).closest("tr");
|
||||||
|
let name = $tr.find("td:first").text().trim();
|
||||||
|
Modal.confirm(`Are you sure to remove archive: <strong>${name}</strong>?`, "Delete stack", (dlg, e) => {
|
||||||
|
$ajax.post(`${name}/delete`).trigger(e.target).json(r => {
|
||||||
|
$tr.remove();
|
||||||
|
dlg.close();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Stack.ListPage = ListPage;
|
||||||
|
})(Stack = Swirl.Stack || (Swirl.Stack = {}));
|
||||||
|
})(Swirl || (Swirl = {}));
|
||||||
//# sourceMappingURL=swirl.js.map
|
//# sourceMappingURL=swirl.js.map
|
File diff suppressed because one or more lines are too long
70
assets/swirl/ts/stack/edit.ts
Normal file
70
assets/swirl/ts/stack/edit.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
///<reference path="../core/core.ts" />
|
||||||
|
namespace Swirl.Stack {
|
||||||
|
import Validator = Swirl.Core.Validator;
|
||||||
|
import AjaxResult = Swirl.Core.AjaxResult;
|
||||||
|
import Notification = Swirl.Core.Notification;
|
||||||
|
import ValidationRule = Swirl.Core.ValidationRule;
|
||||||
|
|
||||||
|
class ContentRequiredRule implements ValidationRule {
|
||||||
|
validate($form: JQuery, $input: JQuery, arg?: string): {ok: boolean, error?: string} {
|
||||||
|
let el = <HTMLInputElement>$input[0];
|
||||||
|
if ($("#type-" + arg).prop("checked")) {
|
||||||
|
console.log(el.value);
|
||||||
|
return {ok: el.checkValidity ? el.checkValidity() : true, error: el.validationMessage};
|
||||||
|
}
|
||||||
|
return {ok: true}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EditPage {
|
||||||
|
private editor: any;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
Validator.register("content", new ContentRequiredRule(), "");
|
||||||
|
|
||||||
|
this.editor = CodeMirror.fromTextArea($("#txt-content")[0], {lineNumbers: true});
|
||||||
|
|
||||||
|
$("#file-content").change(e => {
|
||||||
|
let file = <HTMLInputElement>e.target;
|
||||||
|
if (file.files.length > 0) {
|
||||||
|
$('#filename').text(file.files[0].name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$("#type-input,#type-upload").click(e => {
|
||||||
|
let type = $(e.target).val();
|
||||||
|
$("#div-input").toggle(type == "input");
|
||||||
|
$("#div-upload").toggle(type == "upload");
|
||||||
|
});
|
||||||
|
$("#btn-submit").click(this.submit.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
private submit(e: JQueryEventObject) {
|
||||||
|
this.editor.save();
|
||||||
|
|
||||||
|
let results = Validator.bind("#div-form").validate();
|
||||||
|
if (results.length > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = new FormData();
|
||||||
|
data.append('name', $("#name").val());
|
||||||
|
if ($("#type-input").prop("checked")) {
|
||||||
|
data.append('content', $('#txt-content').val());
|
||||||
|
} else {
|
||||||
|
let file = <HTMLInputElement>$('#file-content')[0];
|
||||||
|
data.append('content', file.files[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = $(e.target).data("url") || "";
|
||||||
|
$ajax.post(url, data).encoder("none").trigger(e.target).json((r: AjaxResult) => {
|
||||||
|
if (r.success) {
|
||||||
|
location.href = "/stack/"
|
||||||
|
} else {
|
||||||
|
Notification.show("danger", `FAILED: ${r.message}`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare var CodeMirror: any;
|
46
assets/swirl/ts/stack/list.ts
Normal file
46
assets/swirl/ts/stack/list.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
///<reference path="../core/core.ts" />
|
||||||
|
namespace Swirl.Stack {
|
||||||
|
import Modal = Swirl.Core.Modal;
|
||||||
|
import AjaxResult = Swirl.Core.AjaxResult;
|
||||||
|
import Dispatcher = Swirl.Core.Dispatcher;
|
||||||
|
|
||||||
|
export class ListPage {
|
||||||
|
constructor() {
|
||||||
|
let dispatcher = Dispatcher.bind("#table-items");
|
||||||
|
dispatcher.on("deploy-stack", this.deployStack.bind(this));
|
||||||
|
dispatcher.on("shutdown-stack", this.shutdownStack.bind(this));
|
||||||
|
dispatcher.on("delete-stack", this.deleteStack.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private deployStack(e: JQueryEventObject) {
|
||||||
|
let $tr = $(e.target).closest("tr");
|
||||||
|
let name = $tr.find("td:first").text().trim();
|
||||||
|
Modal.confirm(`Are you sure to deploy stack: <strong>${name}</strong>?`, "Deploy stack", (dlg, e) => {
|
||||||
|
$ajax.post(`${name}/deploy`).trigger(e.target).json<AjaxResult>(r => {
|
||||||
|
location.reload();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private shutdownStack(e: JQueryEventObject) {
|
||||||
|
let $tr = $(e.target).closest("tr");
|
||||||
|
let name = $tr.find("td:first").text().trim();
|
||||||
|
Modal.confirm(`Are you sure to shutdown stack: <strong>${name}</strong>?`, "Shutdown stack", (dlg, e) => {
|
||||||
|
$ajax.post(`${name}/shutdown`).trigger(e.target).json<AjaxResult>(r => {
|
||||||
|
location.reload();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private deleteStack(e: JQueryEventObject) {
|
||||||
|
let $tr = $(e.target).closest("tr");
|
||||||
|
let name = $tr.find("td:first").text().trim();
|
||||||
|
Modal.confirm(`Are you sure to remove archive: <strong>${name}</strong>?`, "Delete stack", (dlg, e) => {
|
||||||
|
$ajax.post(`${name}/delete`).trigger(e.target).json<AjaxResult>(r => {
|
||||||
|
$tr.remove();
|
||||||
|
dlg.close();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -18,7 +18,7 @@ import (
|
|||||||
const stackLabel = "com.docker.stack.namespace"
|
const stackLabel = "com.docker.stack.namespace"
|
||||||
|
|
||||||
// StackList return all stacks.
|
// StackList return all stacks.
|
||||||
func StackList() (stacks []*model.StackListInfo, err error) {
|
func StackList() (stacks []*model.Stack, err error) {
|
||||||
err = mgr.Do(func(ctx context.Context, cli *client.Client) (err error) {
|
err = mgr.Do(func(ctx context.Context, cli *client.Client) (err error) {
|
||||||
var services []swarm.Service
|
var services []swarm.Service
|
||||||
opts := types.ServiceListOptions{
|
opts := types.ServiceListOptions{
|
||||||
@ -30,7 +30,7 @@ func StackList() (stacks []*model.StackListInfo, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
m := make(map[string]*model.StackListInfo)
|
m := make(map[string]*model.Stack)
|
||||||
for _, service := range services {
|
for _, service := range services {
|
||||||
labels := service.Spec.Labels
|
labels := service.Spec.Labels
|
||||||
name, ok := labels[stackLabel]
|
name, ok := labels[stackLabel]
|
||||||
@ -42,7 +42,7 @@ func StackList() (stacks []*model.StackListInfo, err error) {
|
|||||||
if stack, ok := m[name]; ok {
|
if stack, ok := m[name]; ok {
|
||||||
stack.Services = append(stack.Services, service.Spec.Name)
|
stack.Services = append(stack.Services, service.Spec.Name)
|
||||||
} else {
|
} else {
|
||||||
m[name] = &model.StackListInfo{
|
m[name] = &model.Stack{
|
||||||
Name: name,
|
Name: name,
|
||||||
Services: []string{service.Spec.Name},
|
Services: []string{service.Spec.Name},
|
||||||
}
|
}
|
||||||
@ -125,7 +125,8 @@ func StackRemove(name string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(services)+len(networks)+len(secrets)+len(configs) == 0 {
|
if len(services)+len(networks)+len(secrets)+len(configs) == 0 {
|
||||||
return fmt.Errorf("nothing found in stack: %s", name)
|
//return fmt.Errorf("nothing found in stack: %s", name)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove services
|
// Remove services
|
||||||
|
16
biz/event.go
16
biz/event.go
@ -89,9 +89,9 @@ func (b *eventBiz) CreateVolume(action model.EventAction, name string, user web.
|
|||||||
b.Create(event)
|
b.Create(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *eventBiz) CreateStackTask(action model.EventAction, name string, user web.User) {
|
func (b *eventBiz) CreateStack(action model.EventAction, name string, user web.User) {
|
||||||
event := &model.Event{
|
event := &model.Event{
|
||||||
Type: model.EventTypeStackTask,
|
Type: model.EventTypeStack,
|
||||||
Action: action,
|
Action: action,
|
||||||
Code: name,
|
Code: name,
|
||||||
Name: name,
|
Name: name,
|
||||||
@ -101,18 +101,6 @@ func (b *eventBiz) CreateStackTask(action model.EventAction, name string, user w
|
|||||||
b.Create(event)
|
b.Create(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *eventBiz) CreateStackArchive(action model.EventAction, id, name string, user web.User) {
|
|
||||||
event := &model.Event{
|
|
||||||
Type: model.EventTypeStackArchive,
|
|
||||||
Action: action,
|
|
||||||
Code: id,
|
|
||||||
Name: name,
|
|
||||||
UserID: user.ID(),
|
|
||||||
Username: user.Name(),
|
|
||||||
}
|
|
||||||
b.Create(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *eventBiz) CreateSecret(action model.EventAction, name string, user web.User) {
|
func (b *eventBiz) CreateSecret(action model.EventAction, name string, user web.User) {
|
||||||
event := &model.Event{
|
event := &model.Event{
|
||||||
Type: model.EventTypeSecret,
|
Type: model.EventTypeSecret,
|
||||||
|
124
biz/stack.go
124
biz/stack.go
@ -1,60 +1,128 @@
|
|||||||
package biz
|
package biz
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/cuigh/auxo/net/web"
|
"github.com/cuigh/auxo/net/web"
|
||||||
|
"github.com/cuigh/swirl/biz/docker"
|
||||||
"github.com/cuigh/swirl/dao"
|
"github.com/cuigh/swirl/dao"
|
||||||
"github.com/cuigh/swirl/model"
|
"github.com/cuigh/swirl/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Stack return a stack biz instance.
|
// Stack return a stack biz instance.
|
||||||
var Archive = &archiveBiz{}
|
var Stack = &stackBiz{}
|
||||||
|
|
||||||
type archiveBiz struct {
|
type stackBiz struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *archiveBiz) List(args *model.ArchiveListArgs) (archives []*model.Archive, count int, err error) {
|
func (b *stackBiz) List(args *model.StackListArgs) (stacks []*model.Stack, err error) {
|
||||||
|
var (
|
||||||
|
upStacks, internalStacks []*model.Stack
|
||||||
|
upMap = make(map[string]*model.Stack)
|
||||||
|
)
|
||||||
|
|
||||||
|
// load real stacks
|
||||||
|
upStacks, err = docker.StackList()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, stack := range upStacks {
|
||||||
|
upMap[stack.Name] = stack
|
||||||
|
}
|
||||||
|
|
||||||
|
// load stack definitions
|
||||||
do(func(d dao.Interface) {
|
do(func(d dao.Interface) {
|
||||||
archives, count, err = d.ArchiveList(args)
|
internalStacks, err = d.StackList()
|
||||||
})
|
})
|
||||||
return
|
if err != nil {
|
||||||
}
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (b *archiveBiz) Create(archive *model.Archive) (err error) {
|
// merge stacks and definitions
|
||||||
do(func(d dao.Interface) {
|
for _, stack := range internalStacks {
|
||||||
err = d.ArchiveCreate(archive)
|
stack.Internal = true
|
||||||
//if err == nil {
|
if s, ok := upMap[stack.Name]; ok {
|
||||||
// Event.CreateStackArchive(model.EventActionCreate, archive.ID, archive.Name, ctx.User())
|
stack.Services = s.Services
|
||||||
//}
|
delete(upMap, stack.Name)
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *archiveBiz) Delete(id string, user web.User) (err error) {
|
|
||||||
do(func(d dao.Interface) {
|
|
||||||
var archive *model.Archive
|
|
||||||
archive, err = d.ArchiveGet(id)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
if !b.filter(stack, args) {
|
||||||
|
stacks = append(stacks, stack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, stack := range upMap {
|
||||||
|
if !b.filter(stack, args) {
|
||||||
|
stacks = append(stacks, stack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
err = d.ArchiveDelete(id)
|
func (b *stackBiz) filter(stack *model.Stack, args *model.StackListArgs) bool {
|
||||||
|
if args.Name != "" {
|
||||||
|
if !strings.Contains(strings.ToLower(stack.Name), strings.ToLower(args.Name)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch args.Filter {
|
||||||
|
case "up":
|
||||||
|
if len(stack.Services) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case "internal":
|
||||||
|
if !stack.Internal {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case "external":
|
||||||
|
if stack.Internal {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *stackBiz) Create(stack *model.Stack, user web.User) (err error) {
|
||||||
|
do(func(d dao.Interface) {
|
||||||
|
err = d.StackCreate(stack)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
Event.CreateStackArchive(model.EventActionDelete, id, archive.Name, user)
|
Event.CreateStack(model.EventActionCreate, stack.Name, user)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *archiveBiz) Get(id string) (archives *model.Archive, err error) {
|
func (b *stackBiz) Get(name string) (stack *model.Stack, err error) {
|
||||||
do(func(d dao.Interface) {
|
do(func(d dao.Interface) {
|
||||||
archives, err = d.ArchiveGet(id)
|
stack, err = d.StackGet(name)
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *archiveBiz) Update(archive *model.Archive) (err error) {
|
func (b *stackBiz) Update(stack *model.Stack, user web.User) (err error) {
|
||||||
do(func(d dao.Interface) {
|
do(func(d dao.Interface) {
|
||||||
err = d.ArchiveUpdate(archive)
|
err = d.StackUpdate(stack)
|
||||||
|
if err == nil {
|
||||||
|
Event.CreateStack(model.EventActionUpdate, stack.Name, user)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *stackBiz) Delete(name string, user web.User) (err error) {
|
||||||
|
do(func(d dao.Interface) {
|
||||||
|
err = d.StackDelete(name)
|
||||||
|
if err == nil {
|
||||||
|
Event.CreateStack(model.EventActionDelete, name, user)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate migrates old archives to stack collection.
|
||||||
|
func (b *stackBiz) Migrate() {
|
||||||
|
do(func(d dao.Interface) {
|
||||||
|
d.StackMigrate()
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ button.submit: Submit
|
|||||||
button.cancel: Cancel
|
button.cancel: Cancel
|
||||||
button.confirm: Confirm
|
button.confirm: Confirm
|
||||||
button.delete: Delete
|
button.delete: Delete
|
||||||
|
button.remove: Remove
|
||||||
button.prune: Prune
|
button.prune: Prune
|
||||||
button.new: New
|
button.new: New
|
||||||
button.add: Add
|
button.add: Add
|
||||||
@ -150,6 +151,7 @@ service.template.button.create: Create service
|
|||||||
stack.title: Stack
|
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.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
|
stack.button.deploy: Deploy
|
||||||
|
stack.button.shutdown: Shutdown
|
||||||
|
|
||||||
# task pages
|
# task pages
|
||||||
task.title: Task
|
task.title: Task
|
||||||
|
@ -10,6 +10,7 @@ button.submit: 提交
|
|||||||
button.cancel: 取消
|
button.cancel: 取消
|
||||||
button.confirm: 确定
|
button.confirm: 确定
|
||||||
button.delete: 删除
|
button.delete: 删除
|
||||||
|
button.remove: 移除
|
||||||
button.prune: 清理
|
button.prune: 清理
|
||||||
button.new: 新建
|
button.new: 新建
|
||||||
button.add: 添加
|
button.add: 添加
|
||||||
@ -149,7 +150,8 @@ service.template.button.create: 创建服务
|
|||||||
# stack pages
|
# stack pages
|
||||||
stack.title: 编排
|
stack.title: 编排
|
||||||
stack.description: 编排是相关服务的一个逻辑分组,这些服务通常互相依赖,需要一块部署。
|
stack.description: 编排是相关服务的一个逻辑分组,这些服务通常互相依赖,需要一块部署。
|
||||||
stack.button.deploy: 部署
|
stack.button.deploy: 发布
|
||||||
|
stack.button.shutdown: 发布
|
||||||
|
|
||||||
# task pages
|
# task pages
|
||||||
task.title: 任务
|
task.title: 任务
|
||||||
|
@ -10,136 +10,126 @@ import (
|
|||||||
|
|
||||||
// StackController is a controller of docker stack(compose)
|
// StackController is a controller of docker stack(compose)
|
||||||
type StackController struct {
|
type StackController struct {
|
||||||
TaskList web.HandlerFunc `path:"/task/" name:"stack.task.list" authorize:"!" desc:"stack task list page"`
|
List web.HandlerFunc `path:"/" name:"stack.list" authorize:"!" desc:"stack list page"`
|
||||||
TaskDelete web.HandlerFunc `path:"/task/delete" method:"post" name:"stack.task.delete" authorize:"!" desc:"delete stack task"`
|
New web.HandlerFunc `path:"/new" name:"stack.new" authorize:"!" desc:"new stack page"`
|
||||||
ArchiveList web.HandlerFunc `path:"/archive/" name:"stack.archive.list" authorize:"!" desc:"stack archive list page"`
|
Create web.HandlerFunc `path:"/new" method:"post" name:"stack.create" authorize:"!" desc:"create stack"`
|
||||||
ArchiveDetail web.HandlerFunc `path:"/archive/:id/detail" name:"stack.archive.detail" authorize:"!" desc:"stack archive detail page"`
|
Detail web.HandlerFunc `path:"/:name/detail" name:"stack.detail" authorize:"!" desc:"stack detail page"`
|
||||||
ArchiveEdit web.HandlerFunc `path:"/archive/:id/edit" name:"stack.archive.edit" authorize:"!" desc:"stack archive edit page"`
|
Edit web.HandlerFunc `path:"/:name/edit" name:"stack.edit" authorize:"!" desc:"stack edit page"`
|
||||||
ArchiveUpdate web.HandlerFunc `path:"/archive/:id/update" method:"post" name:"stack.archive.update" authorize:"!" desc:"update stack archive"`
|
Update web.HandlerFunc `path:"/:name/update" method:"post" name:"stack.update" authorize:"!" desc:"update stack"`
|
||||||
ArchiveDelete web.HandlerFunc `path:"/archive/delete" method:"post" name:"stack.archive.delete" authorize:"!" desc:"delete stack archive"`
|
Deploy web.HandlerFunc `path:"/:name/deploy" method:"post" name:"stack.deploy" authorize:"!" desc:"deploy stack"`
|
||||||
ArchiveDeploy web.HandlerFunc `path:"/archive/deploy" method:"post" name:"stack.archive.deploy" authorize:"!" desc:"deploy stack archive"`
|
Shutdown web.HandlerFunc `path:"/:name/shutdown" method:"post" name:"stack.shutdown" authorize:"!" desc:"shutdown stack"`
|
||||||
ArchiveNew web.HandlerFunc `path:"/archive/new" name:"stack.archive.new" authorize:"!" desc:"new stack.archive page"`
|
Delete web.HandlerFunc `path:"/:name/delete" method:"post" name:"stack.delete" authorize:"!" desc:"delete stack"`
|
||||||
ArchiveCreate web.HandlerFunc `path:"/archive/new" method:"post" name:"stack.archive.create" authorize:"!" desc:"create stack.archive"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stack creates an instance of StackController
|
// Stack creates an instance of StackController
|
||||||
func Stack() (c *StackController) {
|
func Stack() (c *StackController) {
|
||||||
return &StackController{
|
return &StackController{
|
||||||
TaskList: stackTaskList,
|
List: stackList,
|
||||||
TaskDelete: stackTaskDelete,
|
New: stackNew,
|
||||||
ArchiveList: stackArchiveList,
|
Create: stackCreate,
|
||||||
ArchiveDetail: stackArchiveDetail,
|
Detail: stackDetail,
|
||||||
ArchiveEdit: stackArchiveEdit,
|
Edit: stackEdit,
|
||||||
ArchiveUpdate: stackArchiveUpdate,
|
Update: stackUpdate,
|
||||||
ArchiveDelete: stackArchiveDelete,
|
Deploy: stackDeploy,
|
||||||
ArchiveDeploy: stackArchiveDeploy,
|
Shutdown: stackShutdown,
|
||||||
ArchiveNew: stackArchiveNew,
|
Delete: stackDelete,
|
||||||
ArchiveCreate: stackArchiveCreate,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func stackTaskList(ctx web.Context) error {
|
func stackList(ctx web.Context) error {
|
||||||
stacks, err := docker.StackList()
|
args := &model.StackListArgs{}
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
m := newModel(ctx).Set("Stacks", stacks)
|
|
||||||
return ctx.Render("stack/task/list", m)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stackTaskDelete(ctx web.Context) error {
|
|
||||||
name := ctx.F("name")
|
|
||||||
err := docker.StackRemove(name)
|
|
||||||
if err == nil {
|
|
||||||
biz.Event.CreateStackTask(model.EventActionDelete, name, ctx.User())
|
|
||||||
}
|
|
||||||
return ajaxResult(ctx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stackArchiveList(ctx web.Context) error {
|
|
||||||
args := &model.ArchiveListArgs{}
|
|
||||||
err := ctx.Bind(args)
|
err := ctx.Bind(args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
args.PageSize = model.PageSize
|
|
||||||
if args.PageIndex == 0 {
|
|
||||||
args.PageIndex = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
archives, totalCount, err := biz.Archive.List(args)
|
stacks, err := biz.Stack.List(args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
m := newPagerModel(ctx, totalCount, model.PageSize, args.PageIndex).
|
m := newModel(ctx).Set("Stacks", stacks).
|
||||||
Set("Name", args.Name).
|
Set("Name", args.Name).
|
||||||
Set("Archives", archives)
|
Set("Filter", args.Filter)
|
||||||
return ctx.Render("stack/archive/list", m)
|
return ctx.Render("stack/list", m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func stackArchiveDetail(ctx web.Context) error {
|
func stackNew(ctx web.Context) error {
|
||||||
id := ctx.P("id")
|
m := newModel(ctx)
|
||||||
archive, err := biz.Archive.Get(id)
|
return ctx.Render("stack/new", m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stackCreate(ctx web.Context) error {
|
||||||
|
stack := &model.Stack{}
|
||||||
|
err := ctx.Bind(stack)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if archive == nil {
|
|
||||||
return web.ErrNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
m := newModel(ctx).Set("Archive", archive)
|
// Validate format
|
||||||
return ctx.Render("stack/archive/detail", m)
|
_, err = compose.Parse(stack.Name, stack.Content)
|
||||||
}
|
|
||||||
|
|
||||||
func stackArchiveEdit(ctx web.Context) error {
|
|
||||||
id := ctx.P("id")
|
|
||||||
archive, err := biz.Archive.Get(id)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if archive == nil {
|
|
||||||
|
stack.CreatedBy = ctx.User().ID()
|
||||||
|
stack.UpdatedBy = stack.CreatedBy
|
||||||
|
err = biz.Stack.Create(stack, ctx.User())
|
||||||
|
return ajaxResult(ctx, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stackDetail(ctx web.Context) error {
|
||||||
|
name := ctx.P("name")
|
||||||
|
stack, err := biz.Stack.Get(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if stack == nil {
|
||||||
return web.ErrNotFound
|
return web.ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
m := newModel(ctx).Set("Archive", archive)
|
m := newModel(ctx).Set("Stack", stack)
|
||||||
return ctx.Render("stack/archive/edit", m)
|
return ctx.Render("stack/detail", m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func stackArchiveUpdate(ctx web.Context) error {
|
func stackEdit(ctx web.Context) error {
|
||||||
archive := &model.Archive{}
|
name := ctx.P("name")
|
||||||
err := ctx.Bind(archive)
|
stack, err := biz.Stack.Get(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if stack == nil {
|
||||||
|
return web.ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
m := newModel(ctx).Set("Stack", stack)
|
||||||
|
return ctx.Render("stack/edit", m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stackUpdate(ctx web.Context) error {
|
||||||
|
stack := &model.Stack{}
|
||||||
|
err := ctx.Bind(stack)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Validate format
|
// Validate format
|
||||||
_, err = compose.Parse(archive.Name, archive.Content)
|
_, err = compose.Parse(stack.Name, stack.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
archive.UpdatedBy = ctx.User().ID()
|
stack.UpdatedBy = ctx.User().ID()
|
||||||
err = biz.Archive.Update(archive)
|
err = biz.Stack.Update(stack, ctx.User())
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
biz.Event.CreateStackArchive(model.EventActionUpdate, archive.ID, archive.Name, ctx.User())
|
|
||||||
}
|
}
|
||||||
return ajaxResult(ctx, err)
|
return ajaxResult(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func stackArchiveDelete(ctx web.Context) error {
|
func stackDeploy(ctx web.Context) error {
|
||||||
id := ctx.F("id")
|
name := ctx.P("name")
|
||||||
err := biz.Archive.Delete(id, ctx.User())
|
stack, err := biz.Stack.Get(name)
|
||||||
return ajaxResult(ctx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stackArchiveDeploy(ctx web.Context) error {
|
|
||||||
id := ctx.F("id")
|
|
||||||
archive, err := biz.Archive.Get(id)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := compose.Parse(archive.Name, archive.Content)
|
cfg, err := compose.Parse(stack.Name, stack.Content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -161,30 +151,27 @@ func stackArchiveDeploy(ctx web.Context) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = docker.StackDeploy(archive.Name, archive.Content, authes)
|
err = docker.StackDeploy(stack.Name, stack.Content, authes)
|
||||||
|
if err == nil {
|
||||||
|
biz.Event.CreateStack(model.EventActionDeploy, name, ctx.User())
|
||||||
|
}
|
||||||
return ajaxResult(ctx, err)
|
return ajaxResult(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func stackArchiveNew(ctx web.Context) error {
|
func stackShutdown(ctx web.Context) error {
|
||||||
m := newModel(ctx)
|
name := ctx.P("name")
|
||||||
return ctx.Render("stack/archive/new", m)
|
err := docker.StackRemove(name)
|
||||||
}
|
if err == nil {
|
||||||
|
biz.Event.CreateStack(model.EventActionShutdown, name, ctx.User())
|
||||||
func stackArchiveCreate(ctx web.Context) error {
|
}
|
||||||
archive := &model.Archive{}
|
return ajaxResult(ctx, err)
|
||||||
err := ctx.Bind(archive)
|
}
|
||||||
if err != nil {
|
|
||||||
return err
|
func stackDelete(ctx web.Context) error {
|
||||||
|
name := ctx.P("name")
|
||||||
|
err := docker.StackRemove(name)
|
||||||
|
if err == nil {
|
||||||
|
err = biz.Stack.Delete(name, ctx.User())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate format
|
|
||||||
_, err = compose.Parse(archive.Name, archive.Content)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
archive.CreatedBy = ctx.User().ID()
|
|
||||||
archive.UpdatedBy = archive.CreatedBy
|
|
||||||
err = biz.Archive.Create(archive)
|
|
||||||
return ajaxResult(ctx, err)
|
return ajaxResult(ctx, err)
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,14 @@ type Interface interface {
|
|||||||
ArchiveUpdate(archive *model.Archive) error
|
ArchiveUpdate(archive *model.Archive) error
|
||||||
ArchiveDelete(id string) error
|
ArchiveDelete(id string) error
|
||||||
|
|
||||||
|
StackList() (stacks []*model.Stack, err error)
|
||||||
|
StackGet(name string) (*model.Stack, error)
|
||||||
|
StackCreate(stack *model.Stack) error
|
||||||
|
StackUpdate(stack *model.Stack) error
|
||||||
|
StackDelete(name string) error
|
||||||
|
// StackMigrate migrates stacks from old archive collection. This method will removed after v0.8.
|
||||||
|
StackMigrate()
|
||||||
|
|
||||||
TemplateList(args *model.TemplateListArgs) (tpls []*model.Template, count int, err error)
|
TemplateList(args *model.TemplateListArgs) (tpls []*model.Template, count int, err error)
|
||||||
TemplateGet(id string) (*model.Template, error)
|
TemplateGet(id string) (*model.Template, error)
|
||||||
TemplateCreate(tpl *model.Template) error
|
TemplateCreate(tpl *model.Template) error
|
||||||
|
@ -23,9 +23,6 @@ var (
|
|||||||
"session": {
|
"session": {
|
||||||
mgo.Index{Key: []string{"token"}, Unique: true},
|
mgo.Index{Key: []string{"token"}, Unique: true},
|
||||||
},
|
},
|
||||||
"archive": {
|
|
||||||
mgo.Index{Key: []string{"name"}, Unique: true},
|
|
||||||
},
|
|
||||||
"event": {
|
"event": {
|
||||||
mgo.Index{Key: []string{"type"}},
|
mgo.Index{Key: []string{"type"}},
|
||||||
mgo.Index{Key: []string{"name"}},
|
mgo.Index{Key: []string{"name"}},
|
||||||
|
@ -3,6 +3,8 @@ package mongo
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/cuigh/auxo/app"
|
||||||
|
"github.com/cuigh/auxo/log"
|
||||||
"github.com/cuigh/swirl/misc"
|
"github.com/cuigh/swirl/misc"
|
||||||
"github.com/cuigh/swirl/model"
|
"github.com/cuigh/swirl/model"
|
||||||
"github.com/globalsign/mgo"
|
"github.com/globalsign/mgo"
|
||||||
@ -73,3 +75,107 @@ func (d *Dao) ArchiveDelete(id string) (err error) {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//===============================
|
||||||
|
|
||||||
|
func (d *Dao) StackList() (stacks []*model.Stack, err error) {
|
||||||
|
d.do(func(db *database) {
|
||||||
|
stacks = []*model.Stack{}
|
||||||
|
err = db.C("stack").Find(nil).All(&stacks)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dao) StackCreate(stack *model.Stack) (err error) {
|
||||||
|
stack.CreatedAt = time.Now()
|
||||||
|
stack.UpdatedAt = stack.CreatedAt
|
||||||
|
|
||||||
|
d.do(func(db *database) {
|
||||||
|
err = db.C("stack").Insert(stack)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dao) StackGet(name string) (stack *model.Stack, err error) {
|
||||||
|
d.do(func(db *database) {
|
||||||
|
stack = &model.Stack{}
|
||||||
|
err = db.C("stack").FindId(name).One(stack)
|
||||||
|
if err == mgo.ErrNotFound {
|
||||||
|
stack, err = nil, nil
|
||||||
|
} else if err != nil {
|
||||||
|
stack = nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dao) StackUpdate(stack *model.Stack) (err error) {
|
||||||
|
d.do(func(db *database) {
|
||||||
|
update := bson.M{
|
||||||
|
"$set": bson.M{
|
||||||
|
"content": stack.Content,
|
||||||
|
"updated_by": stack.UpdatedBy,
|
||||||
|
"updated_at": time.Now(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err = db.C("stack").UpdateId(stack.Name, update)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dao) StackDelete(name string) (err error) {
|
||||||
|
d.do(func(db *database) {
|
||||||
|
err = db.C("stack").RemoveId(name)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// StackMigrate migrates stacks from old archive collection.
|
||||||
|
func (d *Dao) StackMigrate() {
|
||||||
|
d.do(func(db *database) {
|
||||||
|
logger := log.Get(app.Name)
|
||||||
|
archiveColl := db.C("archive")
|
||||||
|
|
||||||
|
// check collection is exists.
|
||||||
|
if _, err := archiveColl.Indexes(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
archives := make([]*model.Archive, 0)
|
||||||
|
err := archiveColl.Find(nil).All(&archives)
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("Failed to migrate archives: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var errs []error
|
||||||
|
stackColl := db.C("stack")
|
||||||
|
for _, archive := range archives {
|
||||||
|
stack := &model.Stack{
|
||||||
|
Name: archive.Name,
|
||||||
|
Content: archive.Content,
|
||||||
|
CreatedBy: archive.CreatedBy,
|
||||||
|
CreatedAt: archive.CreatedAt,
|
||||||
|
UpdatedBy: archive.UpdatedBy,
|
||||||
|
UpdatedAt: archive.UpdatedAt,
|
||||||
|
}
|
||||||
|
err = stackColl.Insert(stack)
|
||||||
|
if err == nil || mgo.IsDup(err) {
|
||||||
|
archiveColl.RemoveId(archive.ID)
|
||||||
|
} else {
|
||||||
|
logger.Warnf("Failed to migrate archive '%s': %v", archive.Name, err)
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// drop archive collection
|
||||||
|
if len(errs) == 0 {
|
||||||
|
err = archiveColl.DropCollection()
|
||||||
|
if err != nil {
|
||||||
|
logger.Warn("Failed to drop archive collection: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
2
main.go
2
main.go
@ -43,6 +43,8 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
biz.Stack.Migrate()
|
||||||
|
|
||||||
scaler.Start()
|
scaler.Start()
|
||||||
app.Run(server(setting))
|
app.Run(server(setting))
|
||||||
}
|
}
|
||||||
|
@ -91,11 +91,6 @@ func (opts Options) Compress() Options {
|
|||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
|
|
||||||
type StackListInfo struct {
|
|
||||||
Name string
|
|
||||||
Services []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceListInfo struct {
|
type ServiceListInfo struct {
|
||||||
Name string
|
Name string
|
||||||
Image string
|
Image string
|
||||||
|
@ -13,17 +13,14 @@ const (
|
|||||||
EventTypeNetwork EventType = "Network"
|
EventTypeNetwork EventType = "Network"
|
||||||
EventTypeService EventType = "Service"
|
EventTypeService EventType = "Service"
|
||||||
EventTypeServiceTemplate EventType = "Service Template"
|
EventTypeServiceTemplate EventType = "Service Template"
|
||||||
EventTypeStackTask EventType = "Stack Task"
|
EventTypeStack EventType = "Stack"
|
||||||
EventTypeStackArchive EventType = "Stack Archive"
|
|
||||||
EventTypeSecret EventType = "Secret"
|
EventTypeSecret EventType = "Secret"
|
||||||
EventTypeConfig EventType = "Config"
|
EventTypeConfig EventType = "Config"
|
||||||
|
EventTypeVolume EventType = "Volume"
|
||||||
EventTypeVolume EventType = "Volume"
|
EventTypeAuthentication EventType = "Authentication"
|
||||||
|
EventTypeRole EventType = "Role"
|
||||||
EventTypeAuthentication EventType = "Authentication"
|
EventTypeUser EventType = "User"
|
||||||
EventTypeRole EventType = "Role"
|
EventTypeSetting EventType = "Setting"
|
||||||
EventTypeUser EventType = "User"
|
|
||||||
EventTypeSetting EventType = "Setting"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type EventAction string
|
type EventAction string
|
||||||
@ -38,6 +35,8 @@ const (
|
|||||||
EventActionRollback EventAction = "Rollback"
|
EventActionRollback EventAction = "Rollback"
|
||||||
EventActionRestart EventAction = "Restart"
|
EventActionRestart EventAction = "Restart"
|
||||||
EventActionDisconnect EventAction = "Disconnect"
|
EventActionDisconnect EventAction = "Disconnect"
|
||||||
|
EventActionDeploy EventAction = "Deploy"
|
||||||
|
EventActionShutdown EventAction = "Shutdown"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
@ -61,8 +60,8 @@ func (e *Event) URL(et EventType, code string) string {
|
|||||||
return fmt.Sprintf("/network/%s/detail", code)
|
return fmt.Sprintf("/network/%s/detail", code)
|
||||||
case EventTypeService:
|
case EventTypeService:
|
||||||
return fmt.Sprintf("/service/%s/detail", code)
|
return fmt.Sprintf("/service/%s/detail", code)
|
||||||
case EventTypeStackArchive:
|
case EventTypeStack:
|
||||||
return fmt.Sprintf("/stack/archive/%s/detail", code)
|
return fmt.Sprintf("/stack/%s/detail", code)
|
||||||
case EventTypeVolume:
|
case EventTypeVolume:
|
||||||
return fmt.Sprintf("/volume/%s/detail", code)
|
return fmt.Sprintf("/volume/%s/detail", code)
|
||||||
case EventTypeRole:
|
case EventTypeRole:
|
||||||
|
@ -18,6 +18,22 @@ type ArchiveListArgs struct {
|
|||||||
PageSize int `bind:"size"`
|
PageSize int `bind:"size"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Stack struct {
|
||||||
|
Name string `bson:"_id" json:"name,omitempty"`
|
||||||
|
Content string `bson:"content" json:"content,omitempty" bind:"content=form,content=file"`
|
||||||
|
CreatedBy string `bson:"created_by" json:"created_by,omitempty"`
|
||||||
|
CreatedAt time.Time `bson:"created_at" json:"created_at,omitempty"`
|
||||||
|
UpdatedBy string `bson:"updated_by" json:"updated_by,omitempty"`
|
||||||
|
UpdatedAt time.Time `bson:"updated_at" json:"updated_at,omitempty"`
|
||||||
|
Services []string `bson:"-" json:"services,omitempty"`
|
||||||
|
Internal bool `bson:"-" json:"internal"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StackListArgs struct {
|
||||||
|
Name string `bind:"name"`
|
||||||
|
Filter string `bind:"filter"`
|
||||||
|
}
|
||||||
|
|
||||||
type Template struct {
|
type Template struct {
|
||||||
ID string `bson:"_id" json:"id,omitempty"`
|
ID string `bson:"_id" json:"id,omitempty"`
|
||||||
Name string `bson:"name" json:"name,omitempty"`
|
Name string `bson:"name" json:"name,omitempty"`
|
||||||
|
@ -109,16 +109,15 @@ var Perms = []PermGroup{
|
|||||||
{
|
{
|
||||||
Name: "Stack",
|
Name: "Stack",
|
||||||
Perms: []Perm{
|
Perms: []Perm{
|
||||||
{Key: "stack.task.list", Text: "View task list"},
|
{Key: "stack.list", Text: "View list"},
|
||||||
{Key: "stack.task.delete", Text: "Delete task"},
|
{Key: "stack.new", Text: "View new"},
|
||||||
{Key: "stack.archive.list", Text: "View archive list"},
|
{Key: "stack.detail", Text: "View detail"},
|
||||||
{Key: "stack.archive.new", Text: "View archive new"},
|
{Key: "stack.edit", Text: "View edit"},
|
||||||
{Key: "stack.archive.detail", Text: "View archive detail"},
|
{Key: "stack.create", Text: "Create"},
|
||||||
{Key: "stack.archive.edit", Text: "View archive edit"},
|
{Key: "stack.update", Text: "Update"},
|
||||||
{Key: "stack.archive.delete", Text: "Delete archive"},
|
{Key: "stack.deploy", Text: "Deploy"},
|
||||||
{Key: "stack.archive.create", Text: "Create archive"},
|
{Key: "stack.shutdown", Text: "Shutdown"},
|
||||||
{Key: "stack.archive.update", Text: "Update archive"},
|
{Key: "stack.delete", Text: "Delete"},
|
||||||
{Key: "stack.archive.deploy", Text: "Deploy archive"},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -43,7 +43,7 @@
|
|||||||
<a class="navbar-item" href="/network/">{{ i18n("menu.network") }}</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="/service/">{{ i18n("menu.service") }}</a>
|
||||||
<a class="navbar-item" href="/task/">{{ i18n("menu.task") }}</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="/stack/">{{ i18n("menu.stack") }}</a>
|
||||||
<a class="navbar-item" href="/secret/">{{ i18n("menu.secret") }}</a>
|
<a class="navbar-item" href="/secret/">{{ i18n("menu.secret") }}</a>
|
||||||
<a class="navbar-item" href="/config/">{{ i18n("menu.config") }}</a>
|
<a class="navbar-item" href="/config/">{{ i18n("menu.config") }}</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,88 +0,0 @@
|
|||||||
{{ extends "../../_layouts/default" }}
|
|
||||||
{{ import "../../_modules/pager" }}
|
|
||||||
|
|
||||||
{{ block script() }}
|
|
||||||
<script>$(() => new Swirl.Stack.Archive.ListPage())</script>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ 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("stack.title") }}</h1>
|
|
||||||
<h2 class="subtitle is-5">{{ i18n("stack.description") }}</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="hero-foot">
|
|
||||||
<div class="container">
|
|
||||||
<nav class="tabs is-boxed">
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="/stack/task/">{{ i18n("menu.stack.task") }}</a>
|
|
||||||
</li>
|
|
||||||
<li class="is-active">
|
|
||||||
<a href="/stack/archive/">{{ i18n("menu.stack.archive") }}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<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="name" value="{{.Name}}" class="input" type="text" placeholder="Search by name">
|
|
||||||
</p>
|
|
||||||
<p class="control">
|
|
||||||
<button type="submit" class="button is-primary">{{ i18n("button.search") }}</button>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<div class="level-item">
|
|
||||||
<p class="subtitle is-5">
|
|
||||||
<strong>{{len(.Archives)}}</strong>
|
|
||||||
<span class="is-lowercase">{{ i18n("menu.stack.archive") }}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- Right side -->
|
|
||||||
<div class="level-right">
|
|
||||||
<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>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<table id="table-items" class="table is-bordered is-striped is-narrow is-fullwidth">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{{ i18n("field.name") }}</th>
|
|
||||||
<th>{{ i18n("field.created-at") }}</th>
|
|
||||||
<th>{{ i18n("field.updated-at") }}</th>
|
|
||||||
<th>{{ i18n("field.action") }}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{{range .Archives}}
|
|
||||||
<tr data-id="{{.ID}}">
|
|
||||||
<td><a href="{{.ID}}/detail">{{.Name}}</a></td>
|
|
||||||
<td>{{time(.CreatedAt)}}</td>
|
|
||||||
<td>{{time(.UpdatedAt)}}</td>
|
|
||||||
<td>
|
|
||||||
<a href="{{.ID}}/edit" class="button is-small is-dark is-outlined">{{ i18n("button.edit") }}</a>
|
|
||||||
<button type="button" class="button is-small is-info is-outlined" data-action="deploy-archive">{{ i18n("stack.button.deploy") }}</button>
|
|
||||||
<button type="button" class="button is-small is-danger is-outlined" data-action="delete-archive">{{ i18n("button.delete") }}</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{end}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{{ yield pager(info=.Pager) }}
|
|
||||||
</section>
|
|
||||||
{{ end }}
|
|
@ -1,4 +1,4 @@
|
|||||||
{{ extends "../../_layouts/default" }}
|
{{ extends "../_layouts/default" }}
|
||||||
|
|
||||||
{{ block style() }}
|
{{ block style() }}
|
||||||
<link rel="stylesheet" href="/assets/highlight/highlight.css?v=9.12">
|
<link rel="stylesheet" href="/assets/highlight/highlight.css?v=9.12">
|
||||||
@ -17,27 +17,13 @@
|
|||||||
<h2 class="subtitle is-5">{{ i18n("stack.description") }}</h2>
|
<h2 class="subtitle is-5">{{ i18n("stack.description") }}</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hero-foot">
|
|
||||||
<div class="container">
|
|
||||||
<nav class="tabs is-boxed">
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="/stack/task/">{{ i18n("menu.stack.task") }}</a>
|
|
||||||
</li>
|
|
||||||
<li class="is-active">
|
|
||||||
<a href="/stack/archive/">{{ i18n("menu.stack.archive") }}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<nav class="breadcrumb has-succeeds-separator is-small is-marginless" aria-label="breadcrumbs">
|
<nav class="breadcrumb has-succeeds-separator is-small is-marginless" aria-label="breadcrumbs">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/">{{ i18n("menu.home") }}</a></li>
|
<li><a href="/">{{ i18n("menu.home") }}</a></li>
|
||||||
<li><a href="/stack/archive/">{{ i18n("menu.stack.archive") }}</a></li>
|
<li><a href="/stack/">{{ i18n("menu.stack") }}</a></li>
|
||||||
<li class="is-active"><a>{{ i18n("menu.detail") }}</a></li>
|
<li class="is-active"><a>{{ i18n("menu.detail") }}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
@ -46,7 +32,7 @@
|
|||||||
<div class="hero-body">
|
<div class="hero-body">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h2 class="title is-2">
|
<h2 class="title is-2">
|
||||||
{{ .Archive.Name }}
|
{{ .Stack.Name }}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -55,8 +41,8 @@
|
|||||||
<nav class="navbar has-shadow">
|
<nav class="navbar has-shadow">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand">
|
<div class="navbar-brand">
|
||||||
<a class="navbar-item is-tab is-active" href="/stack/archive/{{.Archive.ID}}/detail">{{ i18n("menu.detail") }}</a>
|
<a class="navbar-item is-tab is-active" href="/stack/{{.Stack.Name}}/detail">{{ i18n("menu.detail") }}</a>
|
||||||
<a class="navbar-item is-tab" href="/stack/archive/{{.Archive.ID}}/edit">{{ i18n("menu.edit") }}</a>
|
<a class="navbar-item is-tab" href="/stack/{{.Stack.Name}}/edit">{{ i18n("menu.edit") }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@ -65,13 +51,13 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<dl>
|
<dl>
|
||||||
<dt>{{ i18n("field.created-at") }}</dt>
|
<dt>{{ i18n("field.created-at") }}</dt>
|
||||||
<dd>{{ time(.Archive.CreatedAt) }}</dd>
|
<dd>{{ time(.Stack.CreatedAt) }}</dd>
|
||||||
<dt>{{ i18n("field.updated-at") }}</dt>
|
<dt>{{ i18n("field.updated-at") }}</dt>
|
||||||
<dd>{{ time(.Archive.UpdatedAt) }}</dd>
|
<dd>{{ time(.Stack.UpdatedAt) }}</dd>
|
||||||
<dt>Content</dt>
|
<dt>Content</dt>
|
||||||
<dd class="content"><pre class="is-paddingless"><code class="yaml">{{ .Archive.Content }}</code></pre></dd>
|
<dd class="content"><pre class="is-paddingless"><code class="yaml">{{ .Stack.Content }}</code></pre></dd>
|
||||||
</dl>
|
</dl>
|
||||||
<a href="/stack/archive/" class="button is-primary">
|
<a href="/stack/" class="button is-primary">
|
||||||
<span class="icon"><i class="fas fa-reply"></i></span>
|
<span class="icon"><i class="fas fa-reply"></i></span>
|
||||||
<span>{{ i18n("button.return") }}</span>
|
<span>{{ i18n("button.return") }}</span>
|
||||||
</a>
|
</a>
|
@ -1,5 +1,5 @@
|
|||||||
{{ extends "../../_layouts/default" }}
|
{{ extends "../_layouts/default" }}
|
||||||
{{ import "../../_modules/form" }}
|
{{ import "../_modules/form" }}
|
||||||
|
|
||||||
{{ block style() }}
|
{{ block style() }}
|
||||||
<link rel="stylesheet" href="/assets/codemirror/codemirror.css?v=5.30">
|
<link rel="stylesheet" href="/assets/codemirror/codemirror.css?v=5.30">
|
||||||
@ -8,7 +8,7 @@
|
|||||||
{{ block script() }}
|
{{ block script() }}
|
||||||
<script src="/assets/codemirror/codemirror.js?v=5.30"></script>
|
<script src="/assets/codemirror/codemirror.js?v=5.30"></script>
|
||||||
<script src="/assets/codemirror/mode/yaml.js?v=5.30"></script>
|
<script src="/assets/codemirror/mode/yaml.js?v=5.30"></script>
|
||||||
<script>$(() => new Swirl.Stack.Archive.EditPage())</script>
|
<script>$(() => new Swirl.Stack.EditPage())</script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ block body() }}
|
{{ block body() }}
|
||||||
@ -19,27 +19,13 @@
|
|||||||
<h2 class="subtitle is-5">{{ i18n("stack.description") }}</h2>
|
<h2 class="subtitle is-5">{{ i18n("stack.description") }}</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hero-foot">
|
|
||||||
<div class="container">
|
|
||||||
<nav class="tabs is-boxed">
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="/stack/task/">{{ i18n("menu.stack.task") }}</a>
|
|
||||||
</li>
|
|
||||||
<li class="is-active">
|
|
||||||
<a href="/stack/archive/">{{ i18n("menu.stack.archive") }}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<nav class="breadcrumb has-succeeds-separator is-small is-marginless" aria-label="breadcrumbs">
|
<nav class="breadcrumb has-succeeds-separator is-small is-marginless" aria-label="breadcrumbs">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/">{{ i18n("menu.home") }}</a></li>
|
<li><a href="/">{{ i18n("menu.home") }}</a></li>
|
||||||
<li><a href="/stack/archive/">{{ i18n("menu.stack.archive") }}</a></li>
|
<li><a href="/stack/">{{ i18n("menu.stack") }}</a></li>
|
||||||
<li class="is-active"><a>{{ i18n("menu.edit") }}</a></li>
|
<li class="is-active"><a>{{ i18n("menu.edit") }}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
@ -49,7 +35,7 @@
|
|||||||
<div class="hero-body">
|
<div class="hero-body">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h2 class="title is-2">
|
<h2 class="title is-2">
|
||||||
{{ .Archive.Name }}
|
{{ .Stack.Name }}
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -58,8 +44,8 @@
|
|||||||
<nav class="navbar has-shadow">
|
<nav class="navbar has-shadow">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-brand">
|
<div class="navbar-brand">
|
||||||
<a class="navbar-item is-tab" href="/stack/archive/{{.Archive.ID}}/detail">{{ i18n("menu.detail") }}</a>
|
<a class="navbar-item is-tab" href="/stack/{{.Stack.Name}}/detail">{{ i18n("menu.detail") }}</a>
|
||||||
<a class="navbar-item is-tab is-active" href="/stack/archive/{{.Archive.ID}}/edit">{{ i18n("menu.edit") }}</a>
|
<a class="navbar-item is-tab is-active" href="/stack/{{.Stack.Name}}/edit">{{ i18n("menu.edit") }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
@ -69,7 +55,7 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label class="label">{{ i18n("field.name") }}</label>
|
<label class="label">{{ i18n("field.name") }}</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<input id="name" name="name" class="input" value="{{ .Archive.Name }}" type="text" placeholder="" data-v-rule="native;regex" data-v-arg-regex="^[a-z0-9_-]+$" data-v-msg-regex="Name can contain only letters, digits, '_' and '-'." required>
|
<input id="name" name="name" class="input" value="{{ .Stack.Name }}" type="text" placeholder="" data-v-rule="native;regex" data-v-arg-regex="^[a-z0-9_-]+$" data-v-msg-regex="Name can contain only letters, digits, '_' and '-'." required>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
@ -81,7 +67,7 @@
|
|||||||
<div id="div-input" class="field">
|
<div id="div-input" class="field">
|
||||||
<label class="label">Content</label>
|
<label class="label">Content</label>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<textarea id="txt-content" name="content" class="textarea" rows="20" placeholder="Compose file content" data-v-rule="content" data-v-arg-content="input" required>{{ .Archive.Content }}</textarea>
|
<textarea id="txt-content" name="content" class="textarea" rows="20" placeholder="Compose file content" data-v-rule="content" data-v-arg-content="input" required>{{ .Stack.Content }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="div-upload" class="field" style="display: none">
|
<div id="div-upload" class="field" style="display: none">
|
||||||
@ -104,7 +90,7 @@
|
|||||||
<button id="btn-submit" type="submit" class="button is-primary" data-url="update">{{ i18n("button.submit") }}</button>
|
<button id="btn-submit" type="submit" class="button is-primary" data-url="update">{{ i18n("button.submit") }}</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<a href="/stack/archive/" class="button is-link">{{ i18n("button.cancel") }}</a>
|
<a href="/stack/" class="button is-link">{{ i18n("button.cancel") }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
114
views/stack/list.jet
Normal file
114
views/stack/list.jet
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
{{ extends "../_layouts/default" }}
|
||||||
|
|
||||||
|
{{ block script() }}
|
||||||
|
<script>$(() => new Swirl.Stack.ListPage())</script>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ 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("stack.title") }}</h1>
|
||||||
|
<h2 class="subtitle is-5">{{ i18n("stack.description") }}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<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="name" value="{{.Name}}" class="input" type="text" placeholder="Search by name">
|
||||||
|
</p>
|
||||||
|
<p class="control">
|
||||||
|
<button type="submit" class="button is-primary">{{ i18n("button.search") }}</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="level-item">
|
||||||
|
<p class="subtitle is-5">
|
||||||
|
<strong>{{len(.Stacks)}}</strong>
|
||||||
|
<span class="is-lowercase">{{ i18n("menu.stack") }}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Right side -->
|
||||||
|
<div class="level-right">
|
||||||
|
<p class="level-item">
|
||||||
|
{{if .Filter == ""}}
|
||||||
|
<strong>All</strong>
|
||||||
|
{{else}}
|
||||||
|
<a href="/stack/">All</a>
|
||||||
|
{{end}}
|
||||||
|
</p>
|
||||||
|
<p class="level-item">
|
||||||
|
{{if .Filter == "up"}}
|
||||||
|
<strong>Up</strong>
|
||||||
|
{{else}}
|
||||||
|
<a href="?filter=up">Up</a>
|
||||||
|
{{end}}
|
||||||
|
</p>
|
||||||
|
<p class="level-item">
|
||||||
|
{{if .Filter == "internal"}}
|
||||||
|
<strong>Internal</strong>
|
||||||
|
{{else}}
|
||||||
|
<a href="?filter=internal">Internal</a>
|
||||||
|
{{end}}
|
||||||
|
</p>
|
||||||
|
<p class="level-item">
|
||||||
|
{{if .Filter == "external"}}
|
||||||
|
<strong>External</strong>
|
||||||
|
{{else}}
|
||||||
|
<a href="?filter=external">External</a>
|
||||||
|
{{end}}
|
||||||
|
</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>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<table id="table-items" class="table is-bordered is-striped is-narrow is-fullwidth">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{ i18n("field.name") }}</th>
|
||||||
|
<th>Services</th>
|
||||||
|
<th>{{ i18n("field.created-at") }}</th>
|
||||||
|
<th>{{ i18n("field.updated-at") }}</th>
|
||||||
|
<th>{{ i18n("field.action") }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{range .Stacks}}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{{.Name}}/detail">{{.Name}}</a>{{ if !.Internal }}<span class="icon has-text-danger tooltip is-tooltip-right" data-tooltip="External stack, can't be edited by Swirl"><i class="fas fa-exclamation-circle"></i></span>{{ end }}</td>
|
||||||
|
<td>
|
||||||
|
<div class="tags">
|
||||||
|
{{range .Services}}
|
||||||
|
<a href="/service/{{.}}/detail" class="tag is-success">{{.}}</a>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>{{time(.CreatedAt)}}</td>
|
||||||
|
<td>{{time(.UpdatedAt)}}</td>
|
||||||
|
<td>
|
||||||
|
{{ if .Internal }}
|
||||||
|
<a href="{{ .Name }}/edit" class="button is-small is-dark is-outlined">{{ i18n("button.edit") }}</a>
|
||||||
|
<button class="button is-small is-info is-outlined" data-action="deploy-stack">{{ i18n("stack.button.deploy") }}</button>
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Services }}
|
||||||
|
<button class="button is-small is-danger is-outlined" data-action="shutdown-stack">{{ i18n("stack.button.shutdown") }}</button>
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Internal }}
|
||||||
|
<button class="button is-small is-danger is-outlined" data-action="delete-stack">{{ i18n("button.delete") }}</button>
|
||||||
|
{{ end }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{end}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
{{ end }}
|
@ -1,5 +1,5 @@
|
|||||||
{{ extends "../../_layouts/default" }}
|
{{ extends "../_layouts/default" }}
|
||||||
{{ import "../../_modules/form" }}
|
{{ import "../_modules/form" }}
|
||||||
|
|
||||||
{{ block style() }}
|
{{ block style() }}
|
||||||
<link rel="stylesheet" href="/assets/codemirror/codemirror.css?v=5.30">
|
<link rel="stylesheet" href="/assets/codemirror/codemirror.css?v=5.30">
|
||||||
@ -8,7 +8,7 @@
|
|||||||
{{ block script() }}
|
{{ block script() }}
|
||||||
<script src="/assets/codemirror/codemirror.js?v=5.30"></script>
|
<script src="/assets/codemirror/codemirror.js?v=5.30"></script>
|
||||||
<script src="/assets/codemirror/mode/yaml.js?v=5.30"></script>
|
<script src="/assets/codemirror/mode/yaml.js?v=5.30"></script>
|
||||||
<script>$(() => new Swirl.Stack.Archive.EditPage())</script>
|
<script>$(() => new Swirl.Stack.EditPage())</script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ block body() }}
|
{{ block body() }}
|
||||||
@ -19,20 +19,6 @@
|
|||||||
<h2 class="subtitle is-5">{{ i18n("stack.description") }}</h2>
|
<h2 class="subtitle is-5">{{ i18n("stack.description") }}</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hero-foot">
|
|
||||||
<div class="container">
|
|
||||||
<nav class="tabs is-boxed">
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="/stack/task/">{{ i18n("menu.stack.task") }}</a>
|
|
||||||
</li>
|
|
||||||
<li class="is-active">
|
|
||||||
<a href="/stack/archive/">{{ i18n("menu.stack.archive") }}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
<section class="section">
|
<section class="section">
|
||||||
<div id="div-form" class="container">
|
<div id="div-form" class="container">
|
||||||
@ -76,7 +62,7 @@
|
|||||||
<button id="btn-submit" type="submit" class="button is-primary">{{ i18n("button.submit") }}</button>
|
<button id="btn-submit" type="submit" class="button is-primary">{{ i18n("button.submit") }}</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<a href="/stack/archive/" class="button is-link">{{ i18n("button.cancel") }}</a>
|
<a href="/stack/" class="button is-link">{{ i18n("button.cancel") }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
@ -1,69 +0,0 @@
|
|||||||
{{ extends "../../_layouts/default" }}
|
|
||||||
|
|
||||||
{{ block script() }}
|
|
||||||
<script>$(() => new Swirl.Stack.Task.ListPage())</script>
|
|
||||||
{{ end }}
|
|
||||||
|
|
||||||
{{ 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("stack.title") }}</h1>
|
|
||||||
<h2 class="subtitle is-5">{{ i18n("stack.description") }}</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="hero-foot">
|
|
||||||
<div class="container">
|
|
||||||
<nav class="tabs is-boxed">
|
|
||||||
<ul>
|
|
||||||
<li class="is-active">
|
|
||||||
<a href="/stack/task/">{{ i18n("menu.stack.task") }}</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/stack/archive/">{{ i18n("menu.stack.archive") }}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section class="section">
|
|
||||||
<nav class="level">
|
|
||||||
<!-- Left side -->
|
|
||||||
<div class="level-left">
|
|
||||||
<div class="level-item">
|
|
||||||
<p class="subtitle is-5">
|
|
||||||
<strong>{{len(.Stacks)}}</strong>
|
|
||||||
<span class="is-lowercase">{{ i18n("menu.stack") }}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
<table id="table-items" class="table is-bordered is-striped is-narrow is-fullwidth">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>{{ i18n("field.name") }}</th>
|
|
||||||
<th>Services</th>
|
|
||||||
<th>{{ i18n("field.action") }}</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{{range .Stacks}}
|
|
||||||
<tr>
|
|
||||||
<td>{{.Name}}</td>
|
|
||||||
<td>
|
|
||||||
<div class="tags">
|
|
||||||
{{range .Services}}
|
|
||||||
<a href="/service/{{.}}/detail" class="tag is-success">{{.}}</a>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<button class="button is-small is-danger is-outlined" data-action="delete-stack">{{ i18n("button.delete") }}</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{end}}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</section>
|
|
||||||
{{ end }}
|
|
Loading…
Reference in New Issue
Block a user