Create server-side stuff

This commit is contained in:
Rob Vella 2020-07-30 21:30:36 -07:00
parent b61ef71cdb
commit 6bcd05dc8a
5 changed files with 212 additions and 44 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
yarn-error.log
storage/*

102
app.js
View File

@ -1,19 +1,18 @@
Vue.component('lt-startstop', {
template: '<button class="pure-button pure-button-primary" @click="toggleTail" :class="{ \'button-error\': buttonState }">' +
'{{ buttonState ? "Stop" : "Start" }} Tail</button>',
Vue.component('lt-node-link', {
template: `<a :href='uri' target="_blank">{{ node }}</a>`,
props: ['node', 'type'],
data() {
return {
buttonState: false
uri: '',
urlMap: {
0: '#',
1: 'http://stats.allstarlink.org/nodeinfo.cgi?node=',
2: 'http://www.irlp.net/status/index.php?nodeid='
}
}
},
mounted() {
this.toggleTail();
},
methods: {
toggleTail() {
this.buttonState = !this.buttonState;
this.$emit('click', this.buttonState);
}
this.uri = this.urlMap[this.type] + this.node;
}
});
@ -25,9 +24,13 @@ let App = new Vue({
intervalTimer: null,
fetchEvery: 2000,
// uri = 'http://www3.winsystem.org/monitor/ajax-logtail.php'
uri: 'testdata.txt',
uri: 'fetchData.php',
lastData: null,
logs: [],
nodes: {
1: [],
2: []
},
nodeTypeLabels: {
0: 'Unknown',
1: 'Allstar',
@ -39,9 +42,12 @@ let App = new Vue({
}
}
},
mounted() {
this.toggleTail();
},
methods: {
toggleTail(state) {
this.enabled = state;
toggleTail() {
this.enabled = !this.enabled;
if (this.enabled) {
this.start();
@ -63,29 +69,42 @@ let App = new Vue({
},
fetchLog() {
axios.get(this.uri).then(({data}) => {
// Force re-render component so it updates callsigns & node info
this.$forceUpdate();
axios.get(this.uri + '?cmd=log').then(({data}) => {
this.lastData = data;
this.parseLogData();
});
},
parseLogData() {
this.logs = [];
let rows = this.lastData.split("\n").reverse();
getNodeInfo(node, type) {
if (typeof this.nodes[type][node] !== "undefined") {
let info = this.nodes[type][node];
if (typeof info.callsign === "undefined") return;
return `${info.callsign} ${info.desc} ${info.location}`;
} else {
return '';
}
},
rows.forEach((v) => {
parseLogData() {
let rows = this.lastData.split("\n");
rows.forEach(async (v) => {
let match = v.match(/([A-Za-z]+ [0-9]+ [0-9]+\:[0-9]+\:[0-9]+) (rpt|stn)([A-Za-z0-9]+) .*? (?:\[(?:via) ([0-9]+))?/);
if (!match) return;
let type = this.getNodeType(match[2]);
this.addEntry(
{
node: match[3],
via: match[4],
type: type,
typeLabel: this.getNodeTypeLabel(type),
desc: this.fetchNodeInfo(match[3], type),
dateTime: match[1],
info: this.fetchNodeInfo(match[3], type),
dateTime: moment(match[1], "MMM DD hh:mm:ss"),
}
);
});
@ -100,22 +119,41 @@ let App = new Vue({
},
fetchNodeInfo(node, type) {
return type === 1 ? this.fetchNodeInfoAllstar(node)
: (type === 2 ? this.fetchNodeInfoIRLP(node) : null);
},
if (type === 0) return;
fetchNodeInfoAllstar(node) {
let info;
return info;
},
// Bind it and recurse, fetch it again
// if (typeof this.nodes[type][node] === "undefined") {
// this.nodes[type][node] = null;
// return this.fetchNodeInfo(node, type);
if (this.nodes[type][node]) {
// Don't even call fetchNode**
return this.nodes[type][node];
}
fetchNodeInfoIRLP(node) {
let info;
return info;
axios.get(this.uri + '?cmd=node&type='+type+'&node='+node).then(({data}) => {
this.nodes[type][node] = data;
});
},
addEntry(log) {
this.logs.push(log);
// Generate unique ID for this entry
log.uniqId = (log.node + log.dateTime.unix()).hashCode();
if (!_.findWhere(this.logs, { uniqId: log.uniqId })) {
this.logs.unshift(log);
}
},
}
});
String.prototype.hashCode = function() {
let hash = 0;
if (this.length === 0) {
return hash;
}
for (var i = 0; i < this.length; i++) {
let char = this.charCodeAt(i);
hash = ((hash<<5)-hash)+char;
hash = hash & hash; // Convert to 32bit integer
}
return Math.abs(hash);
};

View File

@ -1,5 +1,6 @@
.header {
padding: 1em;
width: 100%;
}
.text-center {
@ -16,7 +17,32 @@
}
table.logs {
width: 85%;
margin: 0 auto;
}
@media screen and (max-width: 64em) {
table.logs {
width: 100%;
}
}
table.logs th {
background-color: #3e6eb6;
color: #fff;
width: 1%;
}
table.logs th.desc {
width: 100%;
}
table.logs tbody tr:nth-child(even){
background-color: #cbcbcb;
}
table.logs tr td.when{
white-space: nowrap;
}
/* We want to give the content area some more padding */

99
fetchData.php Normal file
View File

@ -0,0 +1,99 @@
<?php
$cmd = $_GET['cmd'] ?? null;
$node = intval($_GET['node'] ?? null);
$type = intval($_GET['type'] ?? null);
if ($cmd === "log" || $cmd == "logText") {
$data = file_get_contents("http://www3.winsystem.org/monitor/ajax-logtail.php");
// Just output the raw data
if ($cmd === "log") {
echo $data;
return;
}
} else if ($cmd === "node") {
if (empty($node)) return;
if ($type === 1) {
$info = fetchNodeInfoAllstar($node);
} else if ($type === 2) {
$info = fetchNodeInfoIRLP($node);
}
echo json_encode($info, JSON_UNESCAPED_SLASHES);
}
return;
function fetchNodeInfoAllstar($node) {
$db = fetchAllStarDb();
if (empty($node)) return [];
$db = explode("\n", $db);
foreach ($db as $row) {
$row = explode("|", $row);
if (intval($row[0]) !== $node) continue;
return [
'node' => $node,
'callsign' => $row[1],
'desc' => $row[2],
'location' => $row[3]
];
}
return [
'node' => $node
];
}
function fetchNodeInfoIRLP($node) {
$db = fetchIRLPDb();
$db = explode("\n", $db);
foreach ($db as $row) {
$line = str_getcsv($row, "\t");
if ($line[0] != $node) continue;
return [
'node' => $node,
'callsign' => $line[1],
'desc' => $line[15],
'location' => "{$line[2]}, {$line[3]}"
];
}
return [
'node' => $node,
'callsign' => $node,
'desc' => '',
'location' => ''
];
}
function fetchAllStarDb() {
$dbFile = __DIR__.'/storage/allstar.txt';
if (!file_exists($dbFile)) {
$db = file_get_contents("http://allmondb.allstarlink.org");
file_put_contents($dbFile, $db);
return $db;
}
return file_get_contents($dbFile);
}
function fetchIRLPDb() {
$dbFile = __DIR__.'/storage/irlp.txt';
if (!file_exists($dbFile)) {
$db = file_get_contents("http://status.irlp.net/nohtmlstatus.txt.zip");
file_put_contents("${dbFile}.zip", $db);
shell_exec("/usr/bin/unzip -p ${dbFile}.zip > ${dbFile}");
}
return file_get_contents($dbFile);
}

View File

@ -14,29 +14,32 @@
<div class="header">
<div class="text-center">
<h1>WIN System Log Tail</h1>
<lt-startstop @click="toggleTail"/>
<button v-cloak class="pure-button pure-button-primary" @click="toggleTail" :class="{ 'button-error': enabled }">
{{ enabled ? "Stop" : "Start" }} Tail
</button>
</div>
</div>
<div class="content-wrapper">
<div class="content">
<table class="pure-table logs">
<thead>
<tr>
<th>Node #</th>
<th>Via Node</th>
<th>Node#</th>
<th>Via</th>
<th>Type</th>
<th>Description</th>
<th>Date/Time</th>
<th>When</th>
<th class="desc">Description</th>
</tr>
</thead>
<tbody v-cloak>
<tr v-for="row in logs">
<td>{{ row.node }}</td>
<td>{{ row.via }}</td>
<td><lt-node-link :node="row.node" :type="row.type" /></td>
<td><lt-node-link :node="row.via" :type="row.type" /></td>
<td>{{ row.typeLabel }}</td>
<td>{{ fetchNodeInfo(row.node) }}</td>
<td>{{ row.dateTime }}</td>
<td class="when">{{ row.dateTime.format('hh:mm:ss MM/DD/YY') }}</td>
<td>
{{ getNodeInfo(row.node, row.type) }}
</td>
</tr>
</tbody>
</table>
@ -46,6 +49,7 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.10.2/underscore-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.2/axios.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.27.0/moment.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="app.js" type="text/javascript"></script>
</body>