delete support

master
codeskyblue 8 years ago
parent f00268de0f
commit 5e14107315

@ -33,3 +33,7 @@ body {
small.user { small.user {
color: #cccccc; color: #cccccc;
} }
.color-red {
color: red;
}

@ -79,14 +79,17 @@
<span class="glyphicon glyphicon-stop"></span> Stop <span class="glyphicon glyphicon-stop"></span> Stop
</button> </button>
<button class="btn btn-default btn-xs" v-on:click="cmdTail(p.program.name)"> <button class="btn btn-default btn-xs" v-on:click="cmdTail(p.program.name)">
<span class="glyphicon glyphicon-text-size"></span> Tail <span class="fa fa-file-text-o"></span> Log
</button> </button>
<a href="/settings/{{p.program.name}}" class="btn btn-default btn-xs"> <a href="/settings/{{p.program.name}}" class="btn btn-default btn-xs">
<span class="glyphicon glyphicon-cog"></span> More <span class="fa fa-bar-chart"></span> Profiles
</a> </a>
<button class="btn btn-default btn-xs" data-toggle="tooltip" title="{{p.program.command}}" > <button class="btn btn-default btn-xs" data-toggle="tooltip" title="{{p.program.command}}">
<span class="glyphicon glyphicon-info-sign"></span> Info <span class="glyphicon glyphicon-info-sign"></span> Info
</button> </button>
<button class="btn btn-default btn-xs" v-on:click="cmdDelete(p.program.name)">
<span class="color-red glyphicon glyphicon-trash"></span> Delete
</button>
</td> </td>
</tr> </tr>
<!-- <tr class="success"> <!-- <tr class="success">
@ -124,7 +127,9 @@
<div class="modal-content"> <div class="modal-content">
<form id="formNewProgram" action="/api/programs" method="post"> <form id="formNewProgram" action="/api/programs" method="post">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">New program</h4> <h4 class="modal-title">New program</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
@ -167,7 +172,9 @@
<div class="modal-content"> <div class="modal-content">
<form id="formNewProgram" action="/api/programs" method="post"> <form id="formNewProgram" action="/api/programs" method="post">
<div class="modal-header"> <div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button> <button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Tail</h4> <h4 class="modal-title">Tail</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">

@ -2,238 +2,250 @@
var W = {}; var W = {};
var testPrograms = [{ var testPrograms = [{
program: { program: {
name: "gggg", name: "gggg",
command: "", command: "",
dir: "", dir: "",
autoStart: true, autoStart: true,
}, },
status: "running", status: "running",
}]; }];
var vm = new Vue({ var vm = new Vue({
el: "#app", el: "#app",
data: { data: {
isConnectionAlive: true, isConnectionAlive: true,
log: { log: {
content: '', content: '',
follow: true, follow: true,
line_count: 0, line_count: 0,
},
programs: [],
}, },
methods: { programs: [],
addNewProgram: function() { },
console.log("Add") methods: {
var form = $("#formNewProgram"); addNewProgram: function() {
form.submit(function(e) { console.log("Add")
console.log("HellO") var form = $("#formNewProgram");
e.preventDefault(); form.submit(function(e) {
console.log(e); console.log("HellO")
$("#newProgram").modal('hide') e.preventDefault();
return false; console.log(e);
}); $("#newProgram").modal('hide')
// console.log(this.program.name); return false;
}, });
updateBreadcrumb: function() { // console.log(this.program.name);
var pathname = decodeURI(location.pathname || "/"); },
var parts = pathname.split('/'); updateBreadcrumb: function() {
this.breadcrumb = []; var pathname = decodeURI(location.pathname || "/");
if (pathname == "/") { var parts = pathname.split('/');
return this.breadcrumb; this.breadcrumb = [];
} if (pathname == "/") {
var i = 2; return this.breadcrumb;
for (; i <= parts.length; i += 1) { }
var name = parts[i - 1]; var i = 2;
var path = parts.slice(0, i).join('/'); for (; i <= parts.length; i += 1) {
this.breadcrumb.push({ var name = parts[i - 1];
name: name + (i == parts.length ? ' /' : ''), var path = parts.slice(0, i).join('/');
path: path this.breadcrumb.push({
}) name: name + (i == parts.length ? ' /' : ''),
} path: path
return this.breadcrumb; })
}, }
refresh: function() { return this.breadcrumb;
// ws.send("Hello") },
$.ajax({ refresh: function() {
url: "/api/programs", // ws.send("Hello")
success: function(data) { $.ajax({
vm.programs = data; url: "/api/programs",
Vue.nextTick(function(){ success: function(data) {
$('[data-toggle="tooltip"]').tooltip() vm.programs = data;
}) Vue.nextTick(function() {
} $('[data-toggle="tooltip"]').tooltip()
}); })
}, }
reload: function() { });
$.ajax({ },
url: "/api/reload", reload: function() {
method: "POST", $.ajax({
success: function(data) { url: "/api/reload",
if (data.status == 0) { method: "POST",
alert("reload success"); success: function(data) {
} else { if (data.status == 0) {
alert(data.value); alert("reload success");
} } else {
} alert(data.value);
}); }
}, }
test: function() { });
console.log("test"); },
}, test: function() {
cmdStart: function(name) { console.log("test");
console.log(name); },
$.ajax({ cmdStart: function(name) {
url: "/api/programs/" + name + "/start", console.log(name);
method: 'post', $.ajax({
success: function(data) { url: "/api/programs/" + name + "/start",
console.log(data); method: 'post',
} success: function(data) {
}) console.log(data);
}, }
cmdStop: function(name) { })
$.ajax({ },
url: "/api/programs/" + name + "/stop", cmdStop: function(name) {
method: 'post', $.ajax({
success: function(data) { url: "/api/programs/" + name + "/stop",
console.log(data); method: 'post',
} success: function(data) {
}) console.log(data);
}, }
cmdTail: function(name) { })
var that = this; },
if (W.wsLog) { cmdTail: function(name) {
W.wsLog.close() var that = this;
} if (W.wsLog) {
W.wsLog = newWebsocket("/ws/logs/" + name, { W.wsLog.close()
onopen: function(evt) { }
that.log.content = ""; W.wsLog = newWebsocket("/ws/logs/" + name, {
that.log.line_count = 0; onopen: function(evt) {
}, that.log.content = "";
onmessage: function(evt) { that.log.line_count = 0;
// strip ansi color
// console.log("DT:", evt.data)
that.log.content += evt.data.replace(/\033\[[0-9;]*m/g, "");
that.log.line_count = $.trim(that.log.content).split(/\r\n|\r|\n/).length;
if (that.log.follow) {
var pre = $(".realtime-log")[0];
setTimeout(function() {
pre.scrollTop = pre.scrollHeight - pre.clientHeight;
}, 1);
}
}
});
this.log.follow = true;
$("#modalTailf").modal({
show: true,
keyboard: true,
// keyboard: false,
// backdrop: 'static',
})
},
canStop: function(status) {
switch (status) {
case "running":
case "retry wait":
return true;
}
}, },
} onmessage: function(evt) {
// strip ansi color
// console.log("DT:", evt.data)
that.log.content += evt.data.replace(/\033\[[0-9;]*m/g, "");
that.log.line_count = $.trim(that.log.content).split(/\r\n|\r|\n/).length;
if (that.log.follow) {
var pre = $(".realtime-log")[0];
setTimeout(function() {
pre.scrollTop = pre.scrollHeight - pre.clientHeight;
}, 1);
}
}
});
this.log.follow = true;
$("#modalTailf").modal({
show: true,
keyboard: true,
// keyboard: false,
// backdrop: 'static',
})
},
cmdDelete: function(name) {
if (!confirm("Confirm delete \"" + name + "\"")) {
return
}
$.ajax({
url: "/api/programs/" + name,
method: 'delete',
success: function(data) {
console.log(data);
}
})
},
canStop: function(status) {
switch (status) {
case "running":
case "retry wait":
return true;
}
},
}
}) })
Vue.filter('fromNow', function(value) { Vue.filter('fromNow', function(value) {
return moment(value).fromNow(); return moment(value).fromNow();
}) })
Vue.filter('formatBytes', function(value) { Vue.filter('formatBytes', function(value) {
var bytes = parseFloat(value); var bytes = parseFloat(value);
if (bytes < 0) return "-"; if (bytes < 0) return "-";
else if (bytes < 1024) return bytes + " B"; else if (bytes < 1024) return bytes + " B";
else if (bytes < 1048576) return (bytes / 1024).toFixed(0) + " KB"; else if (bytes < 1048576) return (bytes / 1024).toFixed(0) + " KB";
else if (bytes < 1073741824) return (bytes / 1048576).toFixed(1) + " MB"; else if (bytes < 1073741824) return (bytes / 1048576).toFixed(1) + " MB";
else return (bytes / 1073741824).toFixed(1) + " GB"; else return (bytes / 1073741824).toFixed(1) + " GB";
}) })
Vue.filter('colorStatus', function(value) { Vue.filter('colorStatus', function(value) {
var makeColorText = function(text, color) { var makeColorText = function(text, color) {
return "<span class='status' style='background-color:" + color + "'>" + text + "</span>"; return "<span class='status' style='background-color:" + color + "'>" + text + "</span>";
} }
switch (value) { switch (value) {
case "stopping": case "stopping":
return makeColorText(value, "#996633"); return makeColorText(value, "#996633");
case "running": case "running":
return makeColorText(value, "green"); return makeColorText(value, "green");
case "fatal": case "fatal":
return makeColorText(value, "red"); return makeColorText(value, "red");
default: default:
return makeColorText(value, "gray"); return makeColorText(value, "gray");
} }
return value; return value;
}) })
Vue.directive('disable', function(value) { Vue.directive('disable', function(value) {
this.el.disabled = !!value this.el.disabled = !!value
}) })
$(function() { $(function() {
vm.refresh(); vm.refresh();
$("#formNewProgram").submit(function(e) { $("#formNewProgram").submit(function(e) {
var url = "/api/programs", var url = "/api/programs",
data = $(this).serialize(); data = $(this).serialize();
$.ajax({ $.ajax({
type: "POST", type: "POST",
url: url, url: url,
data: data, data: data,
success: function(data) { success: function(data) {
if (data.status === 0) { if (data.status === 0) {
$("#newProgram").modal('hide'); $("#newProgram").modal('hide');
} else { } else {
window.alert(data.error); window.alert(data.error);
} }
}, },
error: function(err) { error: function(err) {
console.log(err.responseText); console.log(err.responseText);
} }
}) })
e.preventDefault() e.preventDefault()
}); });
console.log("HEE") console.log("HEE")
function newEventWatcher() { function newEventWatcher() {
W.events = newWebsocket("/ws/events", { W.events = newWebsocket("/ws/events", {
onopen: function(evt) { onopen: function(evt) {
vm.isConnectionAlive = true; vm.isConnectionAlive = true;
}, },
onmessage: function(evt) { onmessage: function(evt) {
console.log("response:" + evt.data); console.log("response:" + evt.data);
vm.refresh(); vm.refresh();
}, },
onclose: function(evt) { onclose: function(evt) {
W.events = null; W.events = null;
vm.isConnectionAlive = false; vm.isConnectionAlive = false;
console.log("Reconnect after 3s") console.log("Reconnect after 3s")
setTimeout(newEventWatcher, 3000) setTimeout(newEventWatcher, 3000)
} }
}); });
}; };
newEventWatcher(); newEventWatcher();
// cancel follow log if people want to see the original data // cancel follow log if people want to see the original data
$(".realtime-log").bind('mousewheel', function(evt) { $(".realtime-log").bind('mousewheel', function(evt) {
if (evt.originalEvent.wheelDelta >= 0) { if (evt.originalEvent.wheelDelta >= 0) {
vm.log.follow = false; vm.log.follow = false;
} }
}) })
$('#modalTailf').on('hidden.bs.modal', function() { $('#modalTailf').on('hidden.bs.modal', function() {
// do something… // do something…
console.log("Hiddeen") console.log("Hiddeen")
if (W.wsLog) { if (W.wsLog) {
console.log("wsLog closed") console.log("wsLog closed")
W.wsLog.close() W.wsLog.close()
} }
}) })
}); });

@ -37,7 +37,7 @@ func init() {
type Supervisor struct { type Supervisor struct {
ConfigDir string ConfigDir string
names []string names []string // order of programs
pgMap map[string]Program pgMap map[string]Program
procMap map[string]*Process procMap map[string]*Process
mu sync.Mutex mu sync.Mutex
@ -138,7 +138,6 @@ func (s *Supervisor) addOrUpdateProgram(pg Program) error {
} }
}() }()
} else { } else {
// s.pgs = append(s.pgs, &pg)
s.names = append(s.names, pg.Name) s.names = append(s.names, pg.Name)
s.pgMap[pg.Name] = pg s.pgMap[pg.Name] = pg
s.procMap[pg.Name] = s.newProcess(pg) s.procMap[pg.Name] = s.newProcess(pg)
@ -190,12 +189,13 @@ func (s *Supervisor) loadDB() error {
if visited[pg.Name] { if visited[pg.Name] {
continue continue
} }
name := pg.Name s.removeProgram(pg.Name)
log.Printf("stop before delete program: %s", name) // name := pg.Name
s.stopAndWait(name) // log.Printf("stop before delete program: %s", name)
delete(s.procMap, name) // s.stopAndWait(name)
delete(s.pgMap, name) // delete(s.procMap, name)
s.broadcastEvent(pg.Name + " deleted") // delete(s.pgMap, name)
// s.broadcastEvent(name + " deleted")
} }
return nil return nil
} }
@ -210,6 +210,22 @@ func (s *Supervisor) saveDB() error {
return ioutil.WriteFile(s.programPath(), data, 0644) return ioutil.WriteFile(s.programPath(), data, 0644)
} }
func (s *Supervisor) removeProgram(name string) {
names := make([]string, 0, len(s.names))
for _, pName := range s.names {
if pName == name {
continue
}
names = append(names, pName)
}
s.names = names
log.Printf("stop before delete program: %s", name)
s.stopAndWait(name)
delete(s.procMap, name)
delete(s.pgMap, name)
s.broadcastEvent(name + " deleted")
}
type WebConfig struct { type WebConfig struct {
User string User string
Version string Version string
@ -360,6 +376,26 @@ func (s *Supervisor) hAddProgram(w http.ResponseWriter, r *http.Request) {
w.Write(data) w.Write(data)
} }
func (s *Supervisor) hDelProgram(w http.ResponseWriter, r *http.Request) {
name := mux.Vars(r)["name"]
w.Header().Set("Content-Type", "application/json")
var data []byte
if _, ok := s.pgMap[name]; !ok {
data, _ = json.Marshal(map[string]interface{}{
"status": 1,
"error": fmt.Sprintf("Program %s not exists", strconv.Quote(name)),
})
} else {
s.removeProgram(name)
s.saveDB()
data, _ = json.Marshal(map[string]interface{}{
"status": 0,
})
}
w.Write(data)
}
func (s *Supervisor) hStartProgram(w http.ResponseWriter, r *http.Request) { func (s *Supervisor) hStartProgram(w http.ResponseWriter, r *http.Request) {
name := mux.Vars(r)["name"] name := mux.Vars(r)["name"]
proc, ok := s.procMap[name] proc, ok := s.procMap[name]
@ -593,6 +629,7 @@ func newSupervisorHandler() (suv *Supervisor, hdlr http.Handler, err error) {
r.HandleFunc("/api/programs", suv.hGetProgramList).Methods("GET") r.HandleFunc("/api/programs", suv.hGetProgramList).Methods("GET")
r.HandleFunc("/api/programs/{name}", suv.hGetProgram).Methods("GET") r.HandleFunc("/api/programs/{name}", suv.hGetProgram).Methods("GET")
r.HandleFunc("/api/programs/{name}", suv.hDelProgram).Methods("DELETE")
r.HandleFunc("/api/programs", suv.hAddProgram).Methods("POST") r.HandleFunc("/api/programs", suv.hAddProgram).Methods("POST")
r.HandleFunc("/api/programs/{name}/start", suv.hStartProgram).Methods("POST") r.HandleFunc("/api/programs/{name}/start", suv.hStartProgram).Methods("POST")
r.HandleFunc("/api/programs/{name}/stop", suv.hStopProgram).Methods("POST") r.HandleFunc("/api/programs/{name}/stop", suv.hStopProgram).Methods("POST")

Loading…
Cancel
Save