Make more AJAXy

This commit is contained in:
Kumi 2018-11-28 16:38:35 +01:00
parent c76697f846
commit 0de4709c30
9 changed files with 105 additions and 19 deletions

2
.gitignore vendored
View file

@ -1,4 +1,4 @@
db.sqlite3 db.sqlite3
__pycache__ __pycache__
*.pyc *.pyc
static static/static_root

View file

@ -0,0 +1,33 @@
# Generated by Django 2.1.3 on 2018-11-28 14:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('manager', '0006_auto_20181126_0849'),
]
operations = [
migrations.AddField(
model_name='device',
name='reboot',
field=models.BooleanField(default=False, verbose_name='Trigger Reboot'),
),
migrations.AlterField(
model_name='network',
name='extip',
field=models.CharField(max_length=15, verbose_name='External/Public IP'),
),
migrations.AlterField(
model_name='network',
name='intip',
field=models.CharField(max_length=15, verbose_name='Internal/Private IP'),
),
migrations.AlterField(
model_name='organization',
name='name',
field=models.CharField(max_length=300, verbose_name='Organization Name'),
),
]

View file

@ -28,6 +28,7 @@ class Device(models.Model):
curip = models.CharField("Current IP Address", max_length=15, blank=True, null=True) curip = models.CharField("Current IP Address", max_length=15, blank=True, null=True)
lasttime = models.DateTimeField("Last Received Heartbeat", blank=True, null=True) lasttime = models.DateTimeField("Last Received Heartbeat", blank=True, null=True)
secret = models.CharField("Secret", default=getRandom, max_length=128) secret = models.CharField("Secret", default=getRandom, max_length=128)
reboot = models.BooleanField("Trigger Reboot", default=False)
def __str__(self): def __str__(self):
return "%s%s" % (self.serial, " (%s)" % self.curip if self.curip else "") return "%s%s" % (self.serial, " (%s)" % self.curip if self.curip else "")

View file

@ -10,6 +10,8 @@
<title>VPN360 Device Administration</title> <title>VPN360 Device Administration</title>
{% load static %}
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet"> <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous"> <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
<!-- link href="https://cdnjs.cloudflare.com/ajax/libs/datatables/1.10.12/css/dataTables.bootstrap4.min.css" rel="stylesheet"/ --!> <!-- link href="https://cdnjs.cloudflare.com/ajax/libs/datatables/1.10.12/css/dataTables.bootstrap4.min.css" rel="stylesheet"/ --!>
@ -17,8 +19,7 @@
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script> <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.bundle.min.js"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.bundle.min.js"></script>
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/datatables/1.10.12/js/jquery.dataTables.min.js"></script> <!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/datatables/1.10.12/js/jquery.dataTables.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/datatables/1.10.13/js/dataTables.bootstrap4.min.js"></script> --> <script src="https://cdnjs.cloudflare.com/ajax/libs/datatables/1.10.13/js/dataTables.bootstrap4.min.js"></script> --!>
</head> </head>

View file

@ -19,7 +19,11 @@
<option {% if choice.intip == device.network.intip %} selected {% endif %} value="{{ choice.intip }}">{{ choice.intip}} ({{ choice.extip }})</option> <option {% if choice.intip == device.network.intip %} selected {% endif %} value="{{ choice.intip }}">{{ choice.intip}} ({{ choice.extip }})</option>
{% endfor %} {% endfor %}
</select> </select>
<small id="subnetHelp" class="form-text text-muted">A network change will require a (manual) reboot of the device to be applied.</small> <small id="subnetHelp" class="form-text text-muted">A network change will require a reboot of the device to be applied.</small>
</div>
<div class="form-group form-check">
<input class="form-check-input" type="checkbox" value="True" {% if device.reboot %} checked {% endif %} id="reboot" name="reboot">
<label class="form-check-label" for="reboot">Reboot device immediately </label>
</div> </div>
<button type="submit" class="btn btn-success">Apply Changes</button> <button type="submit" class="btn btn-success">Apply Changes</button>
<a class="btn btn-danger" href="/" role="button">Cancel</a> <a class="btn btn-danger" href="/" role="button">Cancel</a>

View file

@ -20,10 +20,10 @@
{% for device in devices %} {% for device in devices %}
<tr> <tr>
<td>{{ device.serial }}</td> <td><div style="display: inline; color: grey; font-weight: bold;" id="{{ device.id }}-indicator">&#11044;</div>&nbsp;{{ device.serial }}</td>
<td>{% if device.name %}{{ device.name }}{% endif %}</td> <td>{% if device.name %}{{ device.name }}{% endif %}</td>
<td>{{ device.network }}</td> <td>{{ device.network }}</td>
<td>{% if device.curip %}{{ device.curip }} (at {{ device.lasttime }}){% endif %}</td> <td><div style="display:inline" id="{{ device.id }}-ip">{% if device.curip %}{{ device.curip }} (at {{ device.lasttime }}){% endif %}</div></td>
<td>{{ device.secret }}</td> <td>{{ device.secret }}</td>
<td><a href="/devices/{{ device.id }}/edit"><i class="fas fa-edit"></i></a></td> <td><a href="/devices/{{ device.id }}/edit"><i class="fas fa-edit"></i></a></td>
</tr> </tr>
@ -32,10 +32,17 @@
</table> </table>
</div> </div>
<script src="/js/devices.js"></script>
<script> <script>
/* $(document).ready(function() { /*$(document).ready(function() {
$('#table').DataTable(); $('#table').DataTable();
}); */ });*/
{% for device in devices %}
updateStatus({{ device.id }});
setInterval(updateStatus, 3000, {{ device.id }});
{% endfor %}
</script> </script>

View file

@ -21,33 +21,41 @@ def heartbeat(request):
device.curip = request.POST.get("ip", "") device.curip = request.POST.get("ip", "")
device.lasttime = timezone.now() device.lasttime = timezone.now()
device.save() device.save()
return HttpResponse("") return HttpResponse("reboot" if device.reboot else "")
@csrf_exempt @csrf_exempt
def hosts(request): def hosts(request):
device = get_object_or_404(Device, secret=request.POST.get("secret", "")) device = get_object_or_404(Device, secret=request.POST.get("secret", ""))
device.reboot = False
device.save()
return render(request, "manager/hosts", {"device": device}) return render(request, "manager/hosts", {"device": device})
def ping(request, device_id): def ping(request, device_id):
if request.user.is_authenticated: if request.user.is_authenticated:
device = None device = None
ajax = '{\n "status": '
for organization in Organization.objects.filter(users=request.user): for organization in Organization.objects.filter(users=request.user):
device = device or Device.objects.filter(id=device_id, organization=organization) device = device or Device.objects.filter(id=device_id, organization=organization)
if not device: if not device:
return HttpResponse("-1") ajax += "-1"
try:
socket.inet_aton(device[0].curip)
return HttpResponse(str(int(not os.WEXITSTATUS(os.system("ping -c1 -w1 " + device[0].curip + " > /dev/null 2>&1"))))) # This monster is not long enough yet.
except:
return HttpResponse("-3")
else: else:
return HttpResponse("-2") try:
socket.inet_aton(device[0].curip)
ajax += str(int(not os.WEXITSTATUS(os.system("ping -c1 -w1 " + device[0].curip + " > /dev/null 2>&1")))) # This monster is not long enough yet.
ajax += ',\n "ip": "%s"' % device[0].curip
ajax += ',\n "time": "%s"' % device[0].lasttime
except:
ajax += "-3"
else:
ajax += "-2"
ajax += "\n}"
return HttpResponse(ajax)
def devices(request): def devices(request):
if request.user.is_authenticated: if request.user.is_authenticated:
@ -89,6 +97,7 @@ def editdevice(request, device_id):
if subnet[0] in subnets: if subnet[0] in subnets:
device[0].name = request.POST.get("name", "") device[0].name = request.POST.get("name", "")
device[0].network = subnet[0] device[0].network = subnet[0]
device[0].reboot = True if request.POST.get("reboot", "0") == "True" else False
device[0].save() device[0].save()
return redirect("/") return redirect("/")

31
static/js/devices.js Normal file
View file

@ -0,0 +1,31 @@
function timeSince(obj) {
last = new Date(obj)
var seconds = Math.floor((new Date() - last) / 1000);
interval = Math.floor(seconds / 3600);
out = "";
if (interval > 24) return "at " + last.toDateString().substr(4) + " " + last.toLocaleTimeString();
if (interval > 1) out = interval + " hours ";
interval = Math.floor(seconds / 60);
if (seconds < 120) out = seconds + " seconds "
else if (interval > 1) out = out + interval + " minutes ";
return out + "ago"
}
function styleStatus(msg, device) {
device_status = $("#" + device + "-indicator");
device_ip = $("#" + device + "-ip");
device_status.css("color", msg.status == 1 ? "green" : (msg.status == 0 ? "red" : "grey"))
if (msg.hasOwnProperty("ip")) {
device_ip.text(msg.ip + (msg.status == 1 ? "" :" (" + timeSince(msg.time) + ")"));
};
};
function updateStatus(device_id) {
$.getJSON( "/devices/" + device_id + "/ping/", function(json) { styleStatus(json, device_id); });
};

View file

@ -7,7 +7,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SECRET_KEY = '=go8&h#^kh6ksr11e2z-@4qqd6t%63$x#-!s#l_yhw@oyanrys' SECRET_KEY = '=go8&h#^kh6ksr11e2z-@4qqd6t%63$x#-!s#l_yhw@oyanrys'
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False DEBUG = True
ALLOWED_HOSTS = ["admin360.kumi.host"] ALLOWED_HOSTS = ["admin360.kumi.host"]