parent
390f319f22
commit
a748ea5ccf
@ -1 +1,29 @@
|
|||||||
# MMDVMHost-Websocketboard
|
# MMDVMHost-Websocketboard
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
This is a very first development version of my new MMDVMDash using websockets-technology to get rid of high load on Raspberry Pi and others and keep those temperature down.
|
||||||
|
|
||||||
|
Also this should improve user experience.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
You'll need to install several python3 modules. A concrete list will follow here later.
|
||||||
|
|
||||||
|
### Installation steps
|
||||||
|
* clone this repository to your home-directory
|
||||||
|
* create directory with `sudo mkdir /opt/MMDVMDash`
|
||||||
|
* change ownership to your user for example with `sudo chown -R pi /opt/MMDVMDash`
|
||||||
|
* copy all files from repository into this folder
|
||||||
|
* modify *logtailer.ini* to fit your needs
|
||||||
|
* copy files in */opt/MMDVMDash/systemd* to */etc/systemd/system* or similar corresponding to your system
|
||||||
|
* modify both scripts to fit your needs
|
||||||
|
* enable services with following commmands, this results in starting both automatically after reboot:
|
||||||
|
* `sudo systemctl enable http.server.service`
|
||||||
|
* `sudo systemctl enable logtailer.service`
|
||||||
|
* start services with following commmands:
|
||||||
|
* `sudo systemctl enable http.server.service`
|
||||||
|
* `sudo systemctl enable logtailer.service`
|
||||||
|
|
||||||
|
Finally you should be able to get the new Dashboard calling the hostname of your hotspot
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
![Screenshot of MMDVMDash Websocketboard](img/Screenshot.png "Screenshot of MMDVMDash Websocketboard")
|
Binary file not shown.
@ -0,0 +1,160 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<meta name="description" content="MMDVM-Dashboard by DG9VH">
|
||||||
|
<meta name="author" content="DG9VH">
|
||||||
|
<!-- So refresh works every time -->
|
||||||
|
<meta http-equiv="expires" content="0">
|
||||||
|
|
||||||
|
<title>MMDVM-Dashboard by DG9VH</title>
|
||||||
|
|
||||||
|
<!-- Bootstrap core CSS -->
|
||||||
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
|
||||||
|
<!-- Bootstrap core JavaScript -->
|
||||||
|
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
|
||||||
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
|
||||||
|
<!-- Datatables -->
|
||||||
|
<link rel="stylesheet" href="https://cdn.datatables.net/1.10.21/css/jquery.dataTables.min.css">
|
||||||
|
<script type="text/javascript" src="https://cdn.datatables.net/1.10.21/js/jquery.dataTables.min.js"></script>
|
||||||
|
<script type="text/javascript" src="js/functions.js"></script>
|
||||||
|
<style>
|
||||||
|
nowrap {
|
||||||
|
white-space:nowrap
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<title>DG9VH - MMDVM-Dashboard by DG9VH</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<!-- Navigation -->
|
||||||
|
<nav class="navbar navbar-expand-lg navbar-dark bg-dark static-top">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<span class="float:left">
|
||||||
|
<a class="navbar-brand" href="#">MMDVM-Dashboard by DG9VH</a>
|
||||||
|
</span>
|
||||||
|
<span class="navbar-brand float:right">Websocket-Based</span>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link active" id="lastheard-tab" data-toggle="tab" href="#lastheard" role="tab" aria-controls="lastheard" aria-selected="true">Last Heard</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" id="localheard-tab" data-toggle="tab" href="#localheard" role="tab" aria-controls="localheard" aria-selected="true">Local Heard</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" id="qso-tab" data-toggle="tab" href="#qso" role="tab" aria-controls="qso" aria-selected="false">In QSO</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="tab-content" id="myTabContent">
|
||||||
|
<div class="tab-pane fade show active" id="lastheard" role="tabpanel" aria-labelledby="lastheard-tab">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<!-- Standard-Panel-Inhalt -->
|
||||||
|
<div class="panel-heading">Last Heard List of today's callsigns.<span class="pull-right clickable"><i class="glyphicon glyphicon-chevron-up"></i></span></div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<!-- Tabelle -->
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="lastHeard" class="table lastHeard table-condensed table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Time (UTC)</th>
|
||||||
|
<th>Mode</th>
|
||||||
|
<th>Callsign</th>
|
||||||
|
<th>Target</th>
|
||||||
|
<th>Source</th>
|
||||||
|
<th>Dur (s)</th>
|
||||||
|
<th>Loss</th>
|
||||||
|
<th>BER</th>
|
||||||
|
<th>QSO</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="localheard" role="tabpanel" aria-labelledby="localheard-tab">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<!-- Standard-Panel-Inhalt -->
|
||||||
|
<div class="panel-heading">Local Heard List of today<span class="pull-right clickable"><i class="glyphicon glyphicon-chevron-up"></i></span></div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<!-- Tabelle -->
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table id="localHeard" class="table localHeard table-condensed table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Time (UTC)</th>
|
||||||
|
<th>Mode</th>
|
||||||
|
<th>Callsign</th>
|
||||||
|
<th>Target</th>
|
||||||
|
<th>Source</th>
|
||||||
|
<th>Dur (s)</th>
|
||||||
|
<th>BER</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="qso" role="tabpanel" aria-labelledby="qso-tab">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<!-- Standard-Panel-Inhalt -->
|
||||||
|
<div class="panel-heading">Currently in QSO<span class="pull-right clickable"><i class="glyphicon glyphicon-chevron-up"></i></span></div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<!-- Tabelle -->
|
||||||
|
<div class="table-responsive">
|
||||||
|
<button id="button">Delete selected row</button>
|
||||||
|
<table id="inQSO" class="table inQSO table-condensed table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Callsign</th>
|
||||||
|
<th>Added at</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var t_lh = $('#lastHeard').DataTable( {
|
||||||
|
"order": [[ 0, "desc" ]]
|
||||||
|
} );
|
||||||
|
|
||||||
|
var t_localh = $('#localHeard').DataTable( {
|
||||||
|
"order": [[ 0, "desc" ]]
|
||||||
|
} );
|
||||||
|
var ws = new WebSocket("ws://" + window.location.hostname + ":5678");
|
||||||
|
ws.onmessage = function (event) {
|
||||||
|
getLastHeard(document, event);
|
||||||
|
getLocalHeard(document, event);
|
||||||
|
};
|
||||||
|
t_lh.order( [ 0, 'desc' ] ).draw();
|
||||||
|
t_localh.order( [ 0, 'desc' ] ).draw();
|
||||||
|
|
||||||
|
|
||||||
|
var t_qso = $('#inQSO').DataTable( {
|
||||||
|
"order": [[ 0, "asc" ]]
|
||||||
|
} );
|
||||||
|
|
||||||
|
$('#inQSO tbody').on( 'click', 'tr', function () {
|
||||||
|
if ( $(this).hasClass('selected') ) {
|
||||||
|
$(this).removeClass('selected');
|
||||||
|
} else {
|
||||||
|
t_qso.$('tr.selected').removeClass('selected');
|
||||||
|
$(this).addClass('selected');
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
$('#button').click( function () {
|
||||||
|
t_qso.row('.selected').remove().draw( false );
|
||||||
|
} );
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,159 @@
|
|||||||
|
// 00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333
|
||||||
|
// 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
|
||||||
|
// M: 2020-11-01 21:33:27.454 YSF, received network data from DG2MAS to DG-ID 0 at DG2MAS
|
||||||
|
// M: 2020-11-01 21:33:35.025 YSF, received network end of transmission from DG2MAS to DG-ID 0, 7.7 seconds, 0% packet loss, BER: 0.0%
|
||||||
|
function getTimestamp(logline) {
|
||||||
|
return logline.substring(3,22);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMode(logline) {
|
||||||
|
return logline.substring(27, logline.indexOf(","));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCallsign(logline) {
|
||||||
|
return logline.substring(logline.indexOf("from") + 5, logline.indexOf("to"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTarget(logline) {
|
||||||
|
if(logline.indexOf("at") > 0) {
|
||||||
|
return logline.substring(logline.indexOf("to") + 3, logline.lastIndexOf("at"));
|
||||||
|
} else {
|
||||||
|
return logline.substring(logline.indexOf("to") + 3, logline.substring(logline.indexOf("to") + 3).indexOf(",") + logline.indexOf("to") + 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSource(logline) {
|
||||||
|
val = logline.substring(logline.indexOf("received") + 9);
|
||||||
|
val = val.substring(0, val.indexOf(" "));
|
||||||
|
if (val == "network")
|
||||||
|
val = "Net";
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDuration(logline) {
|
||||||
|
if(logline.lastIndexOf("seconds") > 0) {
|
||||||
|
val = logline.substring(0, logline.lastIndexOf("seconds"));
|
||||||
|
val = val.substring(val.lastIndexOf(",") + 2);
|
||||||
|
return val;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLoss(logline) {
|
||||||
|
if(logline.lastIndexOf("seconds") > 0) {
|
||||||
|
val = logline.substring(logline.lastIndexOf("seconds") + 9, logline.indexOf("%"));
|
||||||
|
if (val.indexOf("BER") == -1) {
|
||||||
|
return val;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBER(logline) {
|
||||||
|
if(logline.lastIndexOf("BER") > 0) {
|
||||||
|
return logline.substring(logline.lastIndexOf("BER") + 4);
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getAddToQSO(logline) {
|
||||||
|
retval = '<div class="bd-clipboard"><button type="button" class="btn-cpQSO" title="Copy to QSO" id="' + getCallsign(logline) + '" onclick="copyToQSO(\'' + getCallsign(logline) + '\')">Copy</button></div>';
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clocktime() {
|
||||||
|
var now = new Date(),
|
||||||
|
h = now.getHours(),
|
||||||
|
m = now.getMinutes(),
|
||||||
|
s = now.getSeconds();
|
||||||
|
m = leadingZero(m);
|
||||||
|
s = leadingZero(s);
|
||||||
|
return h + ':' + m + ':' + s;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function leadingZero(zahl) {
|
||||||
|
zahl = (zahl < 10 ? '0' : '' )+ zahl;
|
||||||
|
return zahl;
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyToQSO(callsign) {
|
||||||
|
$(document).ready(function() {
|
||||||
|
t_qso.row.add( [
|
||||||
|
callsign,
|
||||||
|
new Date().toUTCString()
|
||||||
|
] ).draw();
|
||||||
|
});
|
||||||
|
alert("" + callsign + " added to in QSO-Tab");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLastHeard(document, event) {
|
||||||
|
// 00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333
|
||||||
|
// 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
|
||||||
|
// M: 2020-11-01 21:33:27.454 YSF, received network data from DG2MAS to DG-ID 0 at DG2MAS
|
||||||
|
// M: 2020-11-01 21:33:35.025 YSF, received network end of transmission from DG2MAS to DG-ID 0, 7.7 seconds, 0% packet loss, BER: 0.0%
|
||||||
|
$(document).ready(function() {
|
||||||
|
var rowIndexes = [];
|
||||||
|
t_lh.rows( function ( idx, data, node ) {
|
||||||
|
if(data[2] === getCallsign(event.data)){
|
||||||
|
rowIndexes.push(idx);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
if (rowIndexes[0]) {
|
||||||
|
newData = [
|
||||||
|
getTimestamp(event.data),
|
||||||
|
getMode(event.data),
|
||||||
|
getCallsign(event.data),
|
||||||
|
getTarget(event.data),
|
||||||
|
getSource(event.data),
|
||||||
|
getDuration(event.data),
|
||||||
|
getLoss(event.data),
|
||||||
|
getBER(event.data),
|
||||||
|
getAddToQSO(event.data)
|
||||||
|
]
|
||||||
|
t_lh.row(rowIndexes[0]).data( newData ).draw();
|
||||||
|
} else {
|
||||||
|
t_lh.row.add( [
|
||||||
|
getTimestamp(event.data),
|
||||||
|
getMode(event.data),
|
||||||
|
getCallsign(event.data),
|
||||||
|
getTarget(event.data),
|
||||||
|
getSource(event.data),
|
||||||
|
getDuration(event.data),
|
||||||
|
getLoss(event.data),
|
||||||
|
getBER(event.data),
|
||||||
|
getAddToQSO(event.data)
|
||||||
|
] ).draw();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocalHeard(document, event) {
|
||||||
|
// 00000000001111111111222222222233333333334444444444555555555566666666667777777777888888888899999999990000000000111111111122222222223333333333
|
||||||
|
// 01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789
|
||||||
|
// M: 2020-11-01 21:33:27.454 YSF, received network data from DG2MAS to DG-ID 0 at DG2MAS
|
||||||
|
// M: 2020-11-01 21:33:35.025 YSF, received network end of transmission from DG2MAS to DG-ID 0, 7.7 seconds, 0% packet loss, BER: 0.0%
|
||||||
|
$(document).ready(function() {
|
||||||
|
if (getSource(event.data) == "RF") {
|
||||||
|
if (getDuration(event.data) !== "") {
|
||||||
|
t_localh.row.add( [
|
||||||
|
getTimestamp(event.data),
|
||||||
|
getMode(event.data),
|
||||||
|
getCallsign(event.data),
|
||||||
|
getTarget(event.data),
|
||||||
|
getSource(event.data),
|
||||||
|
getDuration(event.data),
|
||||||
|
getBER(event.data)
|
||||||
|
] ).draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
After Width: | Height: | Size: 70 KiB |
@ -0,0 +1,9 @@
|
|||||||
|
[DEFAULT]
|
||||||
|
Host=0.0.0.0
|
||||||
|
Port=5678
|
||||||
|
Webport=8080
|
||||||
|
|
||||||
|
[MMDVMHost]
|
||||||
|
Logdir=/mnt/ramdisk/
|
||||||
|
Prefix=MMDVM
|
||||||
|
Num_Lines=10000
|
@ -0,0 +1,102 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
#import time
|
||||||
|
import datetime
|
||||||
|
import os.path
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
import argparse
|
||||||
|
import websockets
|
||||||
|
import http.server
|
||||||
|
import socketserver
|
||||||
|
import configparser
|
||||||
|
import os
|
||||||
|
from collections import deque
|
||||||
|
from urllib.parse import urlparse, parse_qs
|
||||||
|
from ansi2html import Ansi2HTMLConverter
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
current_dir = os.getcwd()
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config.read(current_dir + '/logtailer.ini')
|
||||||
|
|
||||||
|
NUM_LINES = int(config['MMDVMHost']['Num_Lines'])
|
||||||
|
|
||||||
|
# init
|
||||||
|
logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s', level=logging.INFO)
|
||||||
|
conv = Ansi2HTMLConverter(inline=True)
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def view_log(websocket, path):
|
||||||
|
global config
|
||||||
|
logging.info('Connected, remote={}, path={}'.format(websocket.remote_address, path))
|
||||||
|
|
||||||
|
try:
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
year = str(now.year)
|
||||||
|
month = str(now.month)
|
||||||
|
if len(month) == 1:
|
||||||
|
month = "0" + month
|
||||||
|
day = str(now.day)
|
||||||
|
if len(day) == 1:
|
||||||
|
day = "0" + day
|
||||||
|
|
||||||
|
file_path = config['MMDVMHost']['Logdir']+config['MMDVMHost']['Prefix']+"-"+year+"-"+month+"-"+day+".log"
|
||||||
|
|
||||||
|
if not os.path.isfile(file_path):
|
||||||
|
raise ValueError('Not found')
|
||||||
|
|
||||||
|
path = file_path
|
||||||
|
with open(file_path) as f:
|
||||||
|
|
||||||
|
content = ''.join(deque(f, NUM_LINES))
|
||||||
|
content = conv.convert(content, full=False)
|
||||||
|
lines = content.split("\n")
|
||||||
|
for line in lines:
|
||||||
|
if line.find('received') >0 and not line.find('network watchdog') > 0:
|
||||||
|
yield from websocket.send(line)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
content = f.read()
|
||||||
|
if content:
|
||||||
|
content = conv.convert(content, full=False)
|
||||||
|
yield from websocket.send(content)
|
||||||
|
else:
|
||||||
|
yield from asyncio.sleep(1)
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
try:
|
||||||
|
yield from websocket.send('<font color="red"><strong>{}</strong></font>'.format(e))
|
||||||
|
yield from websocket.close()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
log_close(websocket, path, e)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
log_close(websocket, path, e)
|
||||||
|
|
||||||
|
else:
|
||||||
|
log_close(websocket, path)
|
||||||
|
|
||||||
|
|
||||||
|
def log_close(websocket, path, exception=None):
|
||||||
|
message = 'Closed, remote={}, path={}'.format(websocket.remote_address, path)
|
||||||
|
if exception is not None:
|
||||||
|
message += ', exception={}'.format(exception)
|
||||||
|
logging.info(message)
|
||||||
|
|
||||||
|
|
||||||
|
def websocketserver():
|
||||||
|
start_server = websockets.serve(view_log, config['DEFAULT']['Host'], config['DEFAULT']['Port'])
|
||||||
|
asyncio.get_event_loop().run_until_complete(start_server)
|
||||||
|
asyncio.get_event_loop().run_forever()
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
websocketserver()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
@ -0,0 +1,13 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Python3 http.server
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStartPre=/bin/sleep 30
|
||||||
|
Type=simple
|
||||||
|
Restart=always
|
||||||
|
# Modify for different location of Python3 or other port
|
||||||
|
ExecStart=/usr/bin/python3 -m http.server 8000 --directory /opt/MMDVMDash/html
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
@ -0,0 +1,14 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Python3 logtailer for MMDVMDash
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStartPre=/bin/sleep 30
|
||||||
|
Type=simple
|
||||||
|
Restart=always
|
||||||
|
# Modify for different location of Python3 or other port
|
||||||
|
WorkingDirectory=/opt/MMDVMDash/
|
||||||
|
ExecStart=/usr/bin/python3 /opt/MMDVMDash/logtailer.py
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
Loading…
Reference in new issue