Skip to content

Commit

Permalink
acra_configui (#111)
Browse files Browse the repository at this point in the history
* config-ui init

* golang 1.5.4+ compatibily explicit struct conversion

* Acraserver test param fix

* Refactored
  • Loading branch information
mozhmike authored and Lagovas committed Mar 5, 2018
1 parent 7731384 commit 3d96f2d
Show file tree
Hide file tree
Showing 10 changed files with 451 additions and 7 deletions.
183 changes: 183 additions & 0 deletions cmd/acra_configui/acra_configui.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package main

import (
"io/ioutil"
"html/template"
"net/http"
"path/filepath"
"gopkg.in/yaml.v2"
"time"
"encoding/json"
"strconv"
"bytes"
"os"
"fmt"
"flag"
log "github.com/sirupsen/logrus"
"github.com/cossacklabs/acra/utils"
"github.com/cossacklabs/acra/cmd"
)

var acraHost *string
var acraPort *int
var debug *bool

func check(e error) {
if e != nil {
panic(e)
}
}

type paramItem struct {
Name string `yaml:"name" json:"name"`
Title string `yaml:"title" json:"title"`
ValueType string `yaml:"value_type" json:"value_type"`
InputType string `yaml:"input_type" json:"input_type"`
Values []string `yaml:"values,flow" json:"values,flow"`
Labels []string `yaml:"labels,flow" json:"labels,flow"`
}

type configParamsYAML struct {
Config []paramItem
}

type ConfigAcraServer struct {
ProxyHost string `json:"host"`
ProxyPort int `json:"port"`
DbHost string `json:"db_host"`
DbPort int `json:"db_port"`
ProxyCommandsPort int `json:"commands_port"`
Debug bool `json:"debug"`
ScriptOnPoison string `json:"poisonscript"`
StopOnPoison bool `json:"poisonshutdown"`
WithZone bool `json:"zonemode"`
}

func SubmitSettings(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
return
}

err := r.ParseForm()
if err != nil {
log.WithError(err).Errorln("Request parsing failed")
http.Error(w, "Bad request", http.StatusBadRequest)
return
}
var db_port, _ = strconv.Atoi(r.Form.Get("db_port"))
var commands_port, _ = strconv.Atoi(r.Form.Get("commands_port"))
var debug, _ = strconv.ParseBool(r.Form.Get("debug"))
var zonemode, _ = strconv.ParseBool(r.Form.Get("zonemode"))
var poisonshutdown, _ = strconv.ParseBool(r.Form.Get("poisonshutdown"))
config := ConfigAcraServer{
DbHost: r.Form.Get("db_host"),
DbPort: db_port,
ProxyCommandsPort: commands_port,
Debug: debug,
ScriptOnPoison: r.Form.Get("poisonscript"),
StopOnPoison: poisonshutdown,
WithZone: zonemode,
}
jsonToServer, err := json.Marshal(config)
if err != nil {
log.WithError(err).Errorln("/setConfig json.Marshal failed")
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
req, err := http.NewRequest("POST", fmt.Sprintf("http://%v:%v/setConfig", *acraHost, *acraPort), bytes.NewBuffer(jsonToServer))
if err != nil {
log.WithError(err).Errorln("/setConfig http.NewRequest failed")
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
log.WithError(err).Errorln("/setConfig client.Do failed")
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
resp.Body.Close()

w.Header().Set("Content-Type", "application/json")
w.Write(jsonToServer)
}

func index(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Security-Policy", "require-sri-for script style")
parsedTemplate, _ := template.ParseFiles(filepath.Join("static", "index.html"))
var outConfigParams configParamsYAML
configParamsYAML, err := ioutil.ReadFile("acraserver_config_vars.yaml")
check(err)

// get current config
var netClient = &http.Client{
Timeout: time.Second * 5,
}
serverResponse, err := netClient.Get(fmt.Sprintf("http://%v:%v/getConfig", *acraHost, *acraPort))
if err != nil {
log.WithError(err).Errorln("AcraServer api error")
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
serverConfigDataJsonString, err := ioutil.ReadAll(serverResponse.Body)
if err != nil {
log.Fatal(err.Error())
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var serverConfigData ConfigAcraServer
err = json.Unmarshal(serverConfigDataJsonString, &serverConfigData)
if err != nil {
log.WithError(err).Errorln("json.Unmarshal error")
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// end get current config

err = yaml.Unmarshal(configParamsYAML, &outConfigParams)
if err != nil {
log.Errorf("%v", utils.ErrorMessage("yaml.Unmarshal error", err))
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

res, err := json.Marshal(outConfigParams)
if err != nil {
log.Errorf("%v", utils.ErrorMessage("json.Marshal error", err))
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

parsedTemplate.Execute(w, struct {
ConfigParams string
ConfigAcraServer
}{
string(res),
serverConfigData,
})
}

func main() {
port := flag.Int("port", 8000, "Port for configUI HTTP endpoint")
acraHost = flag.String("acraHost", "localhost", "Host for Acraserver HTTP endpoint or proxy")
acraPort = flag.Int("acraPort", 9292, "Port for Acraserver HTTP endpoint or proxy")
debug = flag.Bool("d", false, "Turn on debug logging")
flag.Parse()

if *debug {
cmd.SetLogLevel(cmd.LOG_DEBUG)
} else {
cmd.SetLogLevel(cmd.LOG_VERBOSE)
}

http.HandleFunc("/index.html", index)
http.HandleFunc("/", index)
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))
http.HandleFunc("/acraserver/submit_setting", SubmitSettings)
log.Info(fmt.Sprintf("AcraConfigUI is listening @ :%d with PID %d", *port, os.Getpid()))
err := http.ListenAndServe(fmt.Sprintf(":%d", *port), nil)
check(err)
}
58 changes: 58 additions & 0 deletions cmd/acra_configui/acraserver_config_vars.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
config:
# -
# name: host
# title: Host network address to listen for incoming connections from AcraProxy or via SSL
# value_type: string
# input_type: text
# -
# name: port
# title: Port for AcraServer to listen for incoming connections from AcraProxy or via SSL
# value_type: int8
# input_type: number
-
name: db_host
title: Host for destination Postgres
value_type: string
input_type: text
-
name: db_port
title: Port for destination Postgres
value_type: int8
input_type: number
-
name: commands_port
title: Port for AcraServer's HTTP API
value_type: int8
input_type: number
-
name: debug
title: Turn on debug logging
value_type: bool
input_type: radio
values: [true, false]
labels: [Yes, No]
-
name: poisonscript
title: Execute script on detecting poison record
value_type: string
input_type: text
-
name: poisonshutdown
title: Stop on detecting poison record
value_type: bool
values: [true, false]
labels: [Yes, No]
input_type: radio

-
name: server_id
title: ID to be sent in secure session
value_type: string
input_type: text
-
name: zonemode
title: Turn on zone mode
value_type: bool
values: [true, false]
labels: [Yes, No]
input_type: radio
Binary file added cmd/acra_configui/static/img/favicon.ico
Binary file not shown.
99 changes: 99 additions & 0 deletions cmd/acra_configui/static/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<title>ACRA ConfigUI</title>
<link rel="shortcut icon" type="image/x-icon" href="/static/img/favicon.ico"/>

<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.bundle.min.js"
integrity="sha384-feJI7QwhOS+hwpX2zkaeJQjeiwlhOP+SdQDqhgvvo1DsjtiSQByFdThsxO669S2D" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jsrender/0.9.90/jsrender.js"
integrity="sha256-vnIot7uxN4jRvM5P1QDlpy3M8eiR0KwODT0QFKHDhq8=" crossorigin="anonymous"></script>
<script type="text/javascript" src="/static/js/main.js"></script>
<script type="text/javascript">
configParams = JSON.parse({{.ConfigParams}});
currentConfig = {{.ConfigAcraServer}};
</script>

<style type="text/css">
html {
font-size: 0.9rem;
}
body{
padding: 1em;
}
.template{
display: none;
}
</style>

<script id="settingsTplRow" type="text/x-jsrender" class="template">
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Value</th>
<th scope="col">Description</th>
<th scope="col">Alias</th>
</tr>
</thead>
<tbody>
{-for options itemVar="~options"-}
<tr>
<th scope="row">{-:#getIndex()+1-}</th>
<td>
{-if input_type == 'number'-}
<input type="number" name="{-:name-}" value="">
{-else input_type == 'radio'-}
{-for labels-}
<input type="radio" name="{-:~options.name-}" value="{-:~options.values[#index]-}" id="{-:~options.name-}_{->#data-}">
<label for="{-:~options.name-}_{->#data-}">{->#data-}</label>
{-/for-}
{-else-}
<input type="text" name="{-:name-}" value="">
{-/if-}
</td>
<td>{-:title-}</td>
<td>{-:name-}</td>
</tr>
{-/for-}
</tbody>
</table>
<button onclick="javascript: save();">Save</button>
</script>

</head>

<body>

<div class="row">
<div class="col-3">
<div class="nav flex-column nav-pills" id="v-pills-tab" role="tablist" aria-orientation="vertical">
<a class="nav-link active show" id="v-pills-settings-tab" data-toggle="pill" href="#v-pills-settings" role="tab" aria-controls="v-pills-settings" aria-selected="false">AcraServer Settings</a>
<a class="nav-link" id="v-pills-profile-tab" data-toggle="pill" href="#v-pills-profile" role="tab" aria-controls="v-pills-profile" aria-selected="true">Intrusion detection</a>
<a class="nav-link" id="v-pills-messages-tab" data-toggle="pill" href="#v-pills-messages" role="tab" aria-controls="v-pills-messages" aria-selected="false">Zones</a>
</div>
</div>
<div class="col-9">
<div class="tab-content" id="v-pills-tabContent">
<div class="tab-pane fade active show" id="v-pills-settings" role="tabpanel" aria-labelledby="v-pills-settings-tab">

</div>
<div class="tab-pane fade" id="v-pills-profile" role="tabpanel" aria-labelledby="v-pills-profile-tab">
<p>Coming soon...</p>
</div>
<div class="tab-pane fade" id="v-pills-messages" role="tabpanel" aria-labelledby="v-pills-messages-tab">
<p>Coming soon...</p>
</div>
</div>
</div>
</div>

</body>
</html>
52 changes: 52 additions & 0 deletions cmd/acra_configui/static/js/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
$(document).ready(function () {
$.views.settings.delimiters("{-", "-}");
var options = [];
$.each(configParams.Config, function (i, item) {
options.push(item);
});
var tpl = $($.templates('#settingsTplRow').render({
options: options
}));
tpl.appendTo($('#v-pills-settings'));

// set checkox values
$.each(configParams['Config'], function (i, item) {
if (item.input_type == 'radio') {
if (currentConfig[item.name] == undefined) {
$('#v-pills-settings').find('input[type="radio"][name="' + item.name + '"][value="' + item.value + '"]').attr('checked', 'checked');
}
else {
var v = currentConfig[item.name] ? 1 : 0;
$('#v-pills-settings').find('input[type="radio"][name="' + item.name + '"][value="' + currentConfig[item.name] + '"]').attr('checked', 'checked');
}
}
else {
$('#v-pills-settings').find('input[name="' + item.name + '"]').val(currentConfig[item.name]);
}
});

$('#v-pills-tab a').on('click', function (e) {
e.preventDefault();
$(this).tab('show');
})
});

var save = function () {
var data = {};
$.each(configParams['Config'], function (i, item) {
if (item.input_type == 'radio') {
data[item.name] = $('#v-pills-settings').find('input:checked[type="radio"][name="' + item.name + '"]').val();
}
else {
data[item.name] = $('#v-pills-settings').find('input[name="' + item.name + '"]').val();
}
});

$.ajax({
method: 'POST',
url: "/acraserver/submit_setting",
data: data
}).done(function () {
$(this).addClass("done");
});
};
Loading

0 comments on commit 3d96f2d

Please sign in to comment.