old_console/checker/main.go
2024-11-02 14:12:45 +03:00

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)
}