mirror of
https://github.com/RaidMax/IW4M-Admin.git
synced 2025-06-10 23:31:13 -05:00
final changes for 2.0 release
This commit is contained in:
@ -1,11 +1,15 @@
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from apscheduler.triggers.interval import IntervalTrigger
|
||||
from master.context.history import History
|
||||
|
||||
from master.schema.instanceschema import InstanceSchema
|
||||
|
||||
import time
|
||||
|
||||
class Base():
|
||||
def __init__(self):
|
||||
self.history = History()
|
||||
self.instance_list = {}
|
||||
self.server_list = {}
|
||||
self.token_list = {}
|
||||
self.scheduler = BackgroundScheduler()
|
||||
self.scheduler.start()
|
||||
@ -16,6 +20,23 @@ class Base():
|
||||
name='Remove stale instances if no heartbeat in 120 seconds',
|
||||
replace_existing=True
|
||||
)
|
||||
self.scheduler.add_job(
|
||||
func=self._update_history_count,
|
||||
trigger=IntervalTrigger(seconds=30),
|
||||
id='update history',
|
||||
name='update client and instance count every 30 seconds',
|
||||
replace_existing=True
|
||||
)
|
||||
|
||||
def _update_history_count(self):
|
||||
servers = [instance.servers for instance in self.instance_list.values()]
|
||||
servers = [inner for outer in servers for inner in outer]
|
||||
client_num = 0
|
||||
# force it being a number
|
||||
for server in servers:
|
||||
client_num += server.clientnum
|
||||
self.history.add_client_history(client_num)
|
||||
self.history.add_instance_history(len(self.instance_list))
|
||||
|
||||
def _remove_staleinstances(self):
|
||||
for key, value in list(self.instance_list.items()):
|
||||
@ -28,9 +49,6 @@ class Base():
|
||||
def get_instances(self):
|
||||
return self.instance_list.values()
|
||||
|
||||
def get_server_count(self):
|
||||
return self.server_list.count
|
||||
|
||||
def get_instance_count(self):
|
||||
return self.instance_list.count
|
||||
|
||||
|
23
Master/master/context/history.py
Normal file
23
Master/master/context/history.py
Normal file
@ -0,0 +1,23 @@
|
||||
import time
|
||||
from random import randint
|
||||
|
||||
class History():
|
||||
def __init__(self):
|
||||
self.client_history = list()
|
||||
self.instance_history = list()
|
||||
|
||||
def add_client_history(self, client_num):
|
||||
if len(self.client_history) > 1440:
|
||||
self.client_history = self.client_history[1:]
|
||||
self.client_history.append({
|
||||
'count' : client_num,
|
||||
'time' : int(time.time())
|
||||
})
|
||||
|
||||
def add_instance_history(self, instance_num):
|
||||
if len(self.instance_history) > 1440:
|
||||
self.instance_history = self.instance_history[1:]
|
||||
self.instance_history.append({
|
||||
'count' : instance_num,
|
||||
'time' : int(time.time())
|
||||
})
|
46
Master/master/resources/history_graph.py
Normal file
46
Master/master/resources/history_graph.py
Normal file
@ -0,0 +1,46 @@
|
||||
from flask_restful import Resource
|
||||
from pygal.style import Style
|
||||
from master import ctx
|
||||
import pygal
|
||||
import timeago
|
||||
from math import ceil
|
||||
|
||||
class HistoryGraph(Resource):
|
||||
def get(self, history_count):
|
||||
try:
|
||||
custom_style = Style(
|
||||
background='transparent',
|
||||
plot_background='transparent',
|
||||
foreground='rgba(109, 118, 126, 0.3)',
|
||||
foreground_strong='rgba(109, 118, 126, 0.3)',
|
||||
foreground_subtle='rgba(109, 118, 126, 0.3)',
|
||||
opacity='0.1',
|
||||
opacity_hover='0.2',
|
||||
transition='100ms ease-in',
|
||||
colors=('#007acc', '#749363')
|
||||
)
|
||||
|
||||
graph = pygal.StackedLine(
|
||||
interpolate='cubic',
|
||||
interpolation_precision=3,
|
||||
#x_labels_major_every=100,
|
||||
#x_labels_major_count=500,
|
||||
stroke_style={'width': 0.4},
|
||||
show_dots=False,
|
||||
show_legend=False,
|
||||
fill=True,
|
||||
style=custom_style,
|
||||
disable_xml_declaration=True)
|
||||
|
||||
instance_count = [history['time'] for history in ctx.history.instance_history][-history_count:]
|
||||
|
||||
if len(instance_count) > 0:
|
||||
graph.x_labels = [ timeago.format(instance_count[0])]
|
||||
|
||||
graph.add('Instance Count', [history['count'] for history in ctx.history.instance_history][-history_count:])
|
||||
graph.add('Client Count', [history['count'] for history in ctx.history.client_history][-history_count:])
|
||||
return { 'message' : graph.render(),
|
||||
'data_points' : len(instance_count)
|
||||
}, 200
|
||||
except Exception as e:
|
||||
return { 'message' : str(e) }, 500
|
@ -4,8 +4,10 @@ from master.resources.null import Null
|
||||
from master.resources.instance import Instance
|
||||
from master.resources.authenticate import Authenticate
|
||||
from master.resources.version import Version
|
||||
from master.resources.history_graph import HistoryGraph
|
||||
|
||||
api.add_resource(Null, '/null')
|
||||
api.add_resource(Instance, '/instance/', '/instance/<string:id>')
|
||||
api.add_resource(Version, '/version')
|
||||
api.add_resource(Authenticate, '/authenticate')
|
||||
api.add_resource(Authenticate, '/authenticate')
|
||||
api.add_resource(HistoryGraph, '/history/', '/history/<int:history_count>')
|
@ -1,5 +1,46 @@
|
||||
{% extends "layout.html" %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<figure>
|
||||
<div id="history_graph">{{history_graph|safe}}</div>
|
||||
<figcaption class="float-right pr-3 mr-4">
|
||||
<span id="history_graph_zoom_out" class="h4 oi oi-zoom-out text-muted" style="cursor:pointer;"></span>
|
||||
<span id="history_graph_zoom_in" class="h4 oi oi-zoom-in text-muted" style="cursor:pointer;"></span>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script type="text/javascript" src="http://kozea.github.com/pygal.js/latest/pygal-tooltips.min.js"></script>
|
||||
<script>
|
||||
let dataPoints = {{data_points}};
|
||||
let zoomLevel = Math.ceil(dataPoints / 2);
|
||||
//console.log(dataPoints);
|
||||
|
||||
function updateHistoryGraph() {
|
||||
$.get('/history/' + zoomLevel)
|
||||
.done(function (content) {
|
||||
$('#history_graph').html(content.message);
|
||||
});
|
||||
}
|
||||
setInterval(updateHistoryGraph, 30000);
|
||||
|
||||
$('#history_graph_zoom_out').click(function () {
|
||||
// console.log(zoomLevel);
|
||||
zoomLevel = zoomLevel * 2 < dataPoints ? Math.ceil(zoomLevel * 2) : dataPoints;
|
||||
updateHistoryGraph();
|
||||
});
|
||||
|
||||
$('#history_graph_zoom_in').click(function () {
|
||||
// console.log(zoomLevel);
|
||||
zoomLevel = zoomLevel / 2 > 2 ? Math.ceil(zoomLevel / 2) : 2;
|
||||
updateHistoryGraph();
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@ -1,44 +1,32 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html class="bg-dark">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>IW4MAdmin Master | {{ title }}</title>
|
||||
<link rel="stylesheet" type="text/css" href="/static/content/bootstrap.min.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/static/content/site.css" />
|
||||
<script src="/static/scripts/modernizr-2.6.2.js"></script>
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/open-iconic/1.1.1/font/css/open-iconic-bootstrap.min.css" integrity="sha256-BJ/G+e+y7bQdrYkS2RBTyNfBHpA9IuGaPmf9htub5MQ=" crossorigin="anonymous" />
|
||||
<style type="text/css">
|
||||
.active {
|
||||
stroke-width: initial !important;
|
||||
}
|
||||
|
||||
.oi:hover {
|
||||
color: #fff !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="navbar navbar-inverse navbar-fixed-top">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a href="/" class="navbar-brand">Application name</a>
|
||||
</div>
|
||||
<div class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="{{ url_for('home') }}">Home</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<body class="bg-dark">
|
||||
|
||||
<div class="container body-content">
|
||||
<div class="container body-content bg-dark">
|
||||
{% block content %}{% endblock %}
|
||||
<hr />
|
||||
<footer>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/static/scripts/jquery-1.10.2.js"></script>
|
||||
<script src="/static/scripts/bootstrap.js"></script>
|
||||
<script src="/static/scripts/respond.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
|
||||
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
|
||||
crossorigin="anonymous"></script>
|
||||
{% block scripts %}{% endblock %}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
@ -5,13 +5,14 @@ Routes and views for the flask application.
|
||||
from datetime import datetime
|
||||
from flask import render_template
|
||||
from master import app
|
||||
from master.resources.history_graph import HistoryGraph
|
||||
|
||||
@app.route('/')
|
||||
@app.route('/home')
|
||||
def home():
|
||||
"""Renders the home page."""
|
||||
_history_graph = HistoryGraph().get(500)
|
||||
return render_template(
|
||||
'index.html',
|
||||
title='Home Page',
|
||||
year=datetime.now().year,
|
||||
)
|
||||
title='API Overview',
|
||||
history_graph = _history_graph[0]['message'],
|
||||
data_points = _history_graph[0]['data_points']
|
||||
)
|
||||
|
Reference in New Issue
Block a user