1
0
Fork 0

Add web server

This commit is contained in:
Gregory Eremin 2015-08-29 19:34:30 +03:00
parent 655a9e34df
commit 1241a9b44d
4 changed files with 427 additions and 1 deletions

View File

@ -2,13 +2,30 @@ package secondly
import ( import (
"encoding/json" "encoding/json"
"io/ioutil"
"log" "log"
"net/http" "net/http"
"github.com/GeertJohan/go.rice"
) )
func startServer(addr string) { func startServer(addr string) {
staticHandler := http.FileServer(rice.MustFindBox("static").HTTPBox())
mux := http.NewServeMux() mux := http.NewServeMux()
mux.HandleFunc("/fields.json", fieldsHandler) mux.HandleFunc("/fields.json", fieldsHandler)
mux.HandleFunc("/save", saveHandler)
// Static
mux.Handle("/app.js", staticHandler)
mux.Handle("/app.css", staticHandler)
mux.Handle("/config.html", staticHandler)
// Redirect from root to a static file. Ugly yet effective.
mux.HandleFunc("/", func(rw http.ResponseWriter, req *http.Request) {
if req.RequestURI == "/" {
http.Redirect(rw, req, "/config.html", http.StatusMovedPermanently)
}
})
log.Println("Starting configuration server on", addr) log.Println("Starting configuration server on", addr)
go http.ListenAndServe(addr, mux) go http.ListenAndServe(addr, mux)
@ -16,6 +33,30 @@ func startServer(addr string) {
func fieldsHandler(rw http.ResponseWriter, req *http.Request) { func fieldsHandler(rw http.ResponseWriter, req *http.Request) {
fields := extractFields(config, "") fields := extractFields(config, "")
body, _ := json.Marshal(fields) body, err := json.Marshal(fields)
if err != nil {
panic(err)
}
rw.Write(body)
}
func saveHandler(rw http.ResponseWriter, req *http.Request) {
cbody, err := ioutil.ReadAll(req.Body)
if err != nil {
panic(err)
}
updateConfig(cbody)
writeConfig()
resp := struct {
Success bool `json:"success"`
Msg string `json:"msg"`
}{
Success: true,
Msg: "Config successfully updated",
}
body, _ := json.Marshal(resp)
rw.Write(body) rw.Write(body)
} }

67
static/app.css Normal file
View File

@ -0,0 +1,67 @@
body {
margin: 0;
padding: 0;
}
* {
line-height: 40px;
font-size: 16px;
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
.container {
margin: 50px 0 0 50px;
font-weight: 400;
}
h1 {
font-size: 32px;
font-weight: 400;
line-height: 32px;
margin: 0;
padding: 0;
}
h2 {
line-height: 40px;
font-size: 18px;
margin: 0;
padding: 0;
font-weight: 600;
}
h3 {
font-size: 18px;
font-weight: 400;
line-height: 30px;
margin: 0 0 30px;
padding: 0;
color: #aaa;
}
label {
display: inline-block;
width: 100px;
}
input {
font-size: 16px;
padding: 0 5px;
width: 250px;
line-height: 26px;
}
button {
float: left;
margin: 20px 0;
padding: 0 15px;
line-height: 30px;
}
.padding {
float: left;
width: 5px;
height: 40px;
margin: 0 15px 0 0;
background-color: #ade;
}
#notice {
margin: 15px 0 0 100px;
}
.success {
color: #0a0;
}
.error {
color: #a00;
}

293
static/app.js Normal file
View File

@ -0,0 +1,293 @@
/*
* Secondly: Configuration manager for Go language apps
* Copyright (c) 2015 Gregory Eremin
*
* Source: https://github.com/localhots/secondly
* Licence: https://github.com/localhots/secondly/blob/master/LICENSE
*/
function loadFields(callback) {
var xhr = new XMLHttpRequest();
xhr.open("GET", "/fields.json", true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
var fields = JSON.parse(xhr.responseText);
callback(fields);
}
}
};
xhr.send(null);
}
function saveFields(payload, callback) {
var xhr = new XMLHttpRequest();
xhr.open("POST", "/save", true);
xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8");
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
callback(JSON.parse(xhr.responseText));
} else {
callback({"success": false, "msg": "Failed to save config"})
}
}
};
xhr.send(JSON.stringify(payload));
}
function drawForm(fields) {
var elems = [];
var curLevel = 0;
var titlesPrinted = {};
for (var i = 0; i < fields.length; i++) {
var field = fields[i];
var tokens = field.path.split(".");
var section = tokens.slice(0, -1).join(".");
if (section != "" && !titlesPrinted[section]) {
titlesPrinted[section] = 1;
elems.push({
level: tokens.length - 1,
nodes: [makeSectionNode("/"+ section)],
});
}
elems.push({
level: tokens.length - 1,
nodes: makeFieldNode(field),
});
}
render(elems);
}
function render(elems) {
var fields = document.getElementById("fields");
for (var i = 0; i < elems.length; i++) {
var row = elems[i];
var nodes = row.nodes;
for (var j = 0; j < row.level; j++) {
nodes.unshift(makePaddingNode())
}
fields.appendChild(makeRow(nodes));
}
}
function makePaddingNode() {
var div = document.createElement("div");
div.setAttribute("class", "padding");
return div;
}
function makeRow(nodes) {
var div = document.createElement("div");
div.setAttribute("class", "row");
for (var i = 0; i < nodes.length; i++) {
div.appendChild(nodes[i]);
}
return div;
}
function makeFieldNode(field) {
var formGroup = [],
label = makeLabelNode(field.path, field.name),
input = document.createElement("input");
input.setAttribute("id", field.path);
if (field.kind !== "bool") {
input.value = field.value;
} else {
if (field.value) {
input.setAttribute("checked", "checked");
}
}
input.setAttribute("data-type", field.kind);
switch (field.kind) {
case "string":
input.setAttribute("type", "text");
formGroup.push(label);
formGroup.push(input);
break;
case "bool":
input.setAttribute("type", "checkbox");
label.innerHTML = "";
label.appendChild(document.createTextNode(field.name));
formGroup.push(label);
formGroup.push(input);
break;
case "int":
case "int8":
case "int16":
case "int32":
case "int64":
case "uint":
case "uint8":
case "uint16":
case "uint32":
case "uint64":
case "float32":
case "float64":
input.setAttribute("type", "number");
switch (field.kind) {
case "int8":
input.setAttribute("min", "-128");
input.setAttribute("max", "127");
break;
case "int16":
input.setAttribute("min", "-32768");
input.setAttribute("max", "32767");
break;
case "int32":
input.setAttribute("min", "-2147483648");
input.setAttribute("max", "2147483647");
break;
case "int": // Assuming x86-64 architecture
case "int64":
input.setAttribute("min", "-9223372036854775808");
input.setAttribute("max", "9223372036854775807");
break;
case "uint8":
input.setAttribute("min", "0");
input.setAttribute("max", "255");
break;
case "uint16":
input.setAttribute("min", "0");
input.setAttribute("max", "65535");
break;
case "uint32":
input.setAttribute("min", "0");
input.setAttribute("max", "4294967295");
break;
case "uint": // Assuming x86-64 architecture
case "uint64":
input.setAttribute("min", "0");
input.setAttribute("max", "18446744073709551615");
break;
case "float32":
case "float64":
input.setAttribute("step", "any");
break;
}
formGroup.push(label);
formGroup.push(input);
break;
default:
console.log("Invalid field type: "+ field.kind, field.path)
}
return formGroup;
}
function makeSectionNode(section) {
var h2 = document.createElement("h2"),
contents = document.createTextNode(section);
h2.appendChild(contents);
return h2;
}
function makeDivNode(classes) {
var div = document.createElement("div");
div.setAttribute("class", classes);
return div;
}
function makeLabelNode(forId, text) {
var label = document.createElement("label"),
contents = document.createTextNode(text);
label.setAttribute("for", forId);
label.appendChild(contents);
return label;
}
function makePayload(elems) {
var payload = {};
for (path in elems) {
var value = elems[path],
tokens = path.split('.'),
parents = tokens.slice(0, -1),
key = tokens.slice(-1)[0],
parent = payload;
for (var i = 0; i < parents.length; i++) {
var pkey = parents[i];
if (!parent[pkey]) {
parent[pkey] = {}
}
parent = parent[pkey];
}
parent[key] = value;
}
return payload;
}
document.getElementById("config").addEventListener("submit", function(e){
e.preventDefault();
var elems = {},
inputs = document.getElementsByTagName("input");
for (var i = 0; i < inputs.length; i++) {
var input = inputs[i],
type = input.getAttribute("data-type"),
path = input.getAttribute("id"),
value = input.value;
switch (type) {
case "string":
elems[path] = value;
break;
case "bool":
elems[path] = input.checked;
break;
case "int":
case "int8":
case "int16":
case "int32":
case "int64":
case "uint":
case "uint8":
case "uint16":
case "uint32":
case "uint64":
elems[path] = parseInt(value, 10);
break;
case "float32":
case "float64":
elems[path] = parseFloat(value);
break;
}
}
saveFields(makePayload(elems), function(resp){
var notice = document.getElementById("notice");
notice.innerHTML = resp.msg;
if (resp.success) {
notice.setAttribute("class", "success");
} else {
notice.setAttribute("class", "error");
}
notice.style.display = "block";
window.setTimeout(function() {
notice.style.display = "none";
}, 2000);
});
return false;
});
loadFields(drawForm);

25
static/config.html Normal file
View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Application Configuration</title>
<link rel="stylesheet" href="/app.css">
</head>
<body>
<div class="container">
<h1>Secondly<h1>
<h3>Application Configuration</h3>
<form id="config">
<div id="fields"></div>
<button type="submit" class="btn btn-default">Save</button>
<div id="notice"></div>
</form>
</div>
<script src="/app.js"></script>
</body>
</html>