397 lines
15 KiB
Go
397 lines
15 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"html/template"
|
|
"net/http"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
|
|
"github.com/gorilla/mux"
|
|
log "github.com/sirupsen/logrus"
|
|
"golang.org/x/text/language"
|
|
"golang.org/x/text/message"
|
|
)
|
|
|
|
const page = `<!DOCTYPE html>
|
|
<html>
|
|
|
|
<head>
|
|
<title>{{ .PageTitle }}</title>
|
|
|
|
<meta charset="utf-8">
|
|
<meta http-equiv="X-UA-COMPATIBLE" content="IE=edge">
|
|
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
|
|
<link rel="shortcut icon" type="image/png" href="/static/adminlte/images/favicon.png" />
|
|
|
|
<link rel="stylesheet" type="text/css" href="/static/adminlte/plugins/fontawesome-free/css/all.min.css">
|
|
<link rel="stylesheet" type="text/css" href="/static/adminlte/additions/ionicons.min.css">
|
|
<link rel="stylesheet" type="text/css" href="/static/adminlte/plugins/jquery-ui/jquery-ui.css">
|
|
<link rel="stylesheet" type="text/css" href="/static/adminlte/plugins/ion-rangeslider/css/ion.rangeSlider.min.css">
|
|
<link rel="stylesheet" type="text/css" href="/static/adminlte/plugins/bootstrap-slider/css/bootstrap-slider.min.css">
|
|
<link rel="stylesheet" type="text/css" href="/static/adminlte/plugins/icheck-bootstrap/icheck-bootstrap.min.css">
|
|
<link rel="stylesheet" type="text/css" href="/static/adminlte/plugins/daterangepicker/daterangepicker.css">
|
|
<link rel="stylesheet" type="text/css" href="/static/adminlte/plugins/toastr/toastr.min.css">
|
|
<link rel="stylesheet" type="text/css" href="/static/adminlte/plugins/select2/css/select2.min.css">
|
|
<link rel="stylesheet" type="text/css" href="/static/adminltemod/plugins/datatables.min.css" />
|
|
<link rel="stylesheet" type="text/css" href="/static/adminltemod/css/armaDatatables.css" />
|
|
<link rel="stylesheet" type="text/css" href="/static/adminltemod/css/bootstrap-dialog.css" />
|
|
<link rel="stylesheet" type="text/css" href="/static/adminlte/dist/css/adminlte.css">
|
|
<link rel="stylesheet" type="text/css" href="/static/adminltemod/css/adminltemod.css">
|
|
<link rel="stylesheet" type="text/css" href="/static/adminlte/additions/googleapis.css">
|
|
<link rel="stylesheet" type="text/css" href="/static/adminltemod/css/bootstrap-datetimepicker.css">
|
|
|
|
</head>
|
|
|
|
<body class="hold-transition login-page" style="height: auto;">
|
|
|
|
<div class="login-box">
|
|
<div class="login-logo">
|
|
<a href="#"><img class="w-100" src="/static/adminlte/images/logo_lg.svg" alt="logo" ></a>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-body login-card-body">
|
|
<div class="row">
|
|
<div class="col">
|
|
<p class="login-box-msg">{{ .CardTitle }}</p>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col">
|
|
<div class="progress">
|
|
<div id="progress" class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 10%" aria-valuenow="10"
|
|
aria-valuemin="0" aria-valuemax="100"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="row">
|
|
<div class="col" style="margin-top: 1em;">
|
|
<button class="btn btn-primary .btn-sm" type="button" data-toggle="collapse" data-target="#collapseExample" aria-expanded="false" aria-controls="collapseExample">
|
|
{{ .Details }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="collapse" id="collapseExample">
|
|
<div class="row">
|
|
<div class="col">
|
|
<table id="serviceList" class="table table-hover dataTable">
|
|
<thead>
|
|
<th>{{ .ServiceName }}</th>
|
|
<th>{{ .Status }}</th>
|
|
<th>{{ .SubStatus }}</th>
|
|
</thead>
|
|
<tbody></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- /.login-card-body -->
|
|
</div>
|
|
</div>
|
|
<!-- /.login-box -->
|
|
<script src="/static/adminlte/plugins/jquery/jquery.min.js"></script>
|
|
<script src="/static/adminlte/plugins/bootstrap/js/bootstrap.bundle.min.js "></script>
|
|
<script src="/static/adminlte/plugins/moment/moment-with-locales.min.js"></script>
|
|
<script src="/static/adminlte/plugins/inputmask/min/jquery.inputmask.bundle.min.js"></script>
|
|
<script src="/static/adminlte/plugins/bs-custom-file-input/bs-custom-file-input.min.js "></script>
|
|
<script src="/static/adminlte/plugins/bootstrap-switch/js/bootstrap-switch.min.js"></script>
|
|
<script src="/static/adminlte/plugins/select2/js/select2.full.min.js "></script>
|
|
|
|
<script src="/static/adminlte/plugins/toastr/toastr.min.js"></script>
|
|
<script src="/static/adminlte/plugins/jquery-ui/jquery-ui.min.js"></script>
|
|
<script src="/static/adminltemod/plugins/daterangepicker/daterangepicker.js"></script>
|
|
<script src="/static/adminlte/plugins/ion-rangeslider/js/ion.rangeSlider.min.js"></script>
|
|
<script src="/static/adminlte/plugins/bootstrap-slider/bootstrap-slider.min.js"></script>
|
|
<script src="/static/adminltemod/js/overlay.js"></script>
|
|
<!-- Resolve conflict in jQuery UI tooltip with Bootstrap tooltip -->
|
|
<script>
|
|
$.widget.bridge('uibutton', $.ui.button);
|
|
</script>
|
|
<script src="/static/adminltemod/js/bootstrap-dialog.js"></script>
|
|
<script src="/static/adminltemod/js/bootstrap3-typeahead.js"></script>
|
|
<script src="/static/adminltemod/js/multitypeahead.js"></script>
|
|
|
|
<script src="/static/adminltemod/plugins/datatables.min.js"></script>
|
|
<script src="/static/adminltemod/js/infoTable.js"></script>
|
|
|
|
<script src="/static/adminlte/dist/js/adminlte.js"></script>
|
|
<script src="/static/adminltemod/js/adminltemod.js"></script>
|
|
<script src="/static/adminltemod/js/bootstrap-dialog-trans.js"></script>
|
|
<script src="/static/adminltemod/js/quick_edit.js"></script>
|
|
<!-- timepicker -->
|
|
<script type="text/javascript" src="/static/adminltemod/js/bootstrap-datetimepicker.js"></script>
|
|
<script type="text/javascript" src="/static/adminltemod/js/jquery.collapser.min.js"></script>
|
|
|
|
<script type="text/javascript">
|
|
$(document).ready(function () {
|
|
let table = $('#serviceList').DataTable({
|
|
ajax: {
|
|
url: window.location.origin + "/state/",
|
|
dataSrc: "items"
|
|
},
|
|
paging: false,
|
|
info: false,
|
|
searching: false,
|
|
ordering: false,
|
|
drawCallback: function (settings) {
|
|
let total = $('#serviceList').find('tr').length-1;
|
|
let good = $('#serviceList').find('.badge-count').length;
|
|
|
|
$("#progress").attr("aria-valuemax", total);
|
|
$("#progress").attr("aria-valuenow", good);
|
|
$("#progress").attr("style", "width: " + (good * 100) / total + "%");
|
|
|
|
// Need to check that we can get login page
|
|
if (total === good) {
|
|
$.ajax({
|
|
url: window.location.origin,
|
|
complete: function (xhr, textStatus) {
|
|
if (xhr.status == 200) {
|
|
window.location.replace(window.location.origin);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
},
|
|
columns: [
|
|
{
|
|
data: "name",
|
|
render: function (data, type, row, meta) {
|
|
return data.split(".")[0].replace("amc", "");;
|
|
}
|
|
},
|
|
{
|
|
data: "active_state",
|
|
render: function (data, type, row, meta) {
|
|
var css_class = "badge-danger";
|
|
if (data === "active") {
|
|
css_class = "badge-success badge-count";
|
|
} else if (data === "activating") {
|
|
css_class = "badge-warning";
|
|
} else if (data === "inactive") {
|
|
css_class = "bg-warning disabled";
|
|
}
|
|
return '<span class="badge ' + css_class +'"">' + data +'</span>';
|
|
}
|
|
},
|
|
{
|
|
data: "sub_state",
|
|
render: function (data, type, row, meta) {
|
|
var css_class = "badge-danger";
|
|
if (data === "running") {
|
|
css_class = "badge-success";
|
|
} else if (data === "start"){
|
|
css_class = "badge-warning";
|
|
} else if (data === "dead") {
|
|
css_class = "bg-warning disabled";
|
|
}
|
|
return '<span class="badge ' + css_class +'"">' + data +'</span>';
|
|
}
|
|
}
|
|
]
|
|
});
|
|
|
|
setInterval(function () {
|
|
table.ajax.reload();
|
|
}, 1000);
|
|
});
|
|
</script>
|
|
</body>
|
|
|
|
</html>`
|
|
|
|
// Context for rendering template
|
|
type Context struct {
|
|
PageTitle string
|
|
CardTitle string
|
|
Details string
|
|
ServiceName string
|
|
Status string
|
|
SubStatus string
|
|
}
|
|
|
|
type Item struct {
|
|
Name string `json:"name"`
|
|
State string `json:"active_state"`
|
|
SubState string `json:"sub_state"`
|
|
}
|
|
|
|
type Response struct {
|
|
Status string `json:"status"`
|
|
Reason string `json:"reason"`
|
|
Items []Item `json:"items"`
|
|
}
|
|
|
|
func (response Response) Send(w http.ResponseWriter) {
|
|
bytes, err := json.Marshal(response)
|
|
if err != nil {
|
|
log.Errorf("Can't serialize stat: %v\n", err.Error())
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Write(bytes)
|
|
}
|
|
|
|
const (
|
|
StatusOK = "ok"
|
|
StatusErr = "error"
|
|
)
|
|
|
|
func checkService(w http.ResponseWriter, r *http.Request) {
|
|
var response Response
|
|
|
|
items, err := checkServices()
|
|
if err != nil {
|
|
log.Errorf("Can't get status: %v\n", err.Error())
|
|
|
|
response.Status = StatusErr
|
|
response.Reason = err.Error()
|
|
response.Send(w)
|
|
return
|
|
}
|
|
|
|
response.Status = StatusOK
|
|
response.Items = items
|
|
|
|
response.Send(w)
|
|
}
|
|
|
|
func renderPage(w http.ResponseWriter, r *http.Request) {
|
|
t, _, err := language.ParseAcceptLanguage(r.Header.Get("Accept-Language"))
|
|
if err != nil {
|
|
log.Errorf("Got error parsing Accept-Language header: %v", err.Error())
|
|
}
|
|
log.Infof("Got languages: %v", t)
|
|
|
|
templ, err := template.New("page").Parse(page)
|
|
if err != nil {
|
|
log.Errorf("Can't parse template: %v", err.Error())
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
var print *message.Printer
|
|
if len(t) > 0 {
|
|
for _, cur := range t {
|
|
if fmt.Sprintf("%v", cur)[:2] == fmt.Sprintf("%v", language.Russian) {
|
|
log.Debugf("Creating printer for lang %v", cur)
|
|
print = message.NewPrinter(cur)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if print == nil {
|
|
log.Infof("Create default printer")
|
|
print = message.NewPrinter(language.English)
|
|
}
|
|
|
|
context := Context{
|
|
PageTitle: print.Sprintf("PageTitle", "Loading"),
|
|
CardTitle: print.Sprintf("CardTitle", "Please wait, services are loading"),
|
|
Details: print.Sprintf("Details", "Show details"),
|
|
ServiceName: print.Sprintf("ServiceName", "Service name"),
|
|
Status: print.Sprintf("Status", "Status"),
|
|
SubStatus: print.Sprintf("SubStatus", "Sub status"),
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
err = templ.Execute(&buf, context)
|
|
|
|
if err != nil {
|
|
log.Errorf("Can't render template: %v", err.Error())
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Write(buf.Bytes())
|
|
}
|
|
|
|
func httpMatch(r *http.Request, rm *mux.RouteMatch) bool {
|
|
// This check prevent non localhost requests
|
|
if r.RemoteAddr[:5] == "[::1]" {
|
|
return true
|
|
} else if r.RemoteAddr[:9] == "127.0.0.1" {
|
|
return true
|
|
} else if r.RemoteAddr[:9] == "localhost" {
|
|
return true
|
|
}
|
|
log.Error("Bad remote addr")
|
|
|
|
return false
|
|
}
|
|
|
|
func init() {
|
|
message.SetString(language.Russian, "PageTitle", "Загрузка")
|
|
message.SetString(language.Russian, "CardTitle", "Пожалуйста, подождите, сервисы загружаются")
|
|
message.SetString(language.Russian, "Details", "Подробности")
|
|
message.SetString(language.Russian, "ServiceName", "Сервис")
|
|
message.SetString(language.Russian, "Status", "Статус")
|
|
message.SetString(language.Russian, "SubStatus", "Подстатус")
|
|
|
|
message.SetString(language.English, "PageTitle", "Loading")
|
|
message.SetString(language.English, "CardTitle", "Please wait, services are loading")
|
|
message.SetString(language.English, "Details", "Show details")
|
|
message.SetString(language.English, "ServiceName", "Service name")
|
|
message.SetString(language.English, "Status", "Status")
|
|
message.SetString(language.English, "SubStatus", "Sub status")
|
|
}
|
|
|
|
// Services to check it's state
|
|
var services = [...]string{"amccore.service", "amccelery.service", "amccelerybeat.service", "amccorrelator.service", "amcclient.service", "elasticsearch.service", "amcvector.service"}
|
|
|
|
func checkServices() ([]Item, error) {
|
|
result := make([]Item, 0)
|
|
|
|
for _, service := range services {
|
|
// Get service status
|
|
var out1 bytes.Buffer
|
|
cmd1 := exec.Command("systemctl", "show", "-p", "ActiveState", "--value", service)
|
|
cmd1.Stdout = &out1
|
|
err := cmd1.Run()
|
|
if err != nil {
|
|
log.Errorf("Can't get service active state: %v", err.Error())
|
|
return nil, err
|
|
}
|
|
|
|
var out2 bytes.Buffer
|
|
cmd2 := exec.Command("systemctl", "show", "-p", "SubState", "--value", service)
|
|
cmd2.Stdout = &out2
|
|
err = cmd2.Run()
|
|
if err != nil {
|
|
log.Errorf("Can't get service sub state: %v", err.Error())
|
|
return nil, err
|
|
}
|
|
|
|
result = append(result, Item{
|
|
Name: service,
|
|
State: strings.Replace(out1.String(), "\n", "", 1),
|
|
SubState: strings.Replace(out2.String(), "\n", "", 1),
|
|
})
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func main() {
|
|
log.SetFormatter(&log.TextFormatter{})
|
|
log.SetOutput(os.Stdout)
|
|
|
|
port := flag.Int("port", 9080, "Port for work")
|
|
flag.Parse()
|
|
|
|
log.Info("Starting")
|
|
router := mux.NewRouter()
|
|
router.HandleFunc("/", checkService).Methods("GET").MatcherFunc(httpMatch)
|
|
router.HandleFunc("/page", renderPage).Methods("GET").MatcherFunc(httpMatch)
|
|
|
|
http.ListenAndServe(fmt.Sprintf(":%v", *port), router)
|
|
}
|