total refactor WIP
This commit is contained in:
parent
3b10da1adb
commit
7505b4d9e4
11
README.md
11
README.md
|
|
@ -5,4 +5,13 @@
|
||||||
Каталог с файлами сервисов и их подсетей
|
Каталог с файлами сервисов и их подсетей
|
||||||
|
|
||||||
## utils
|
## utils
|
||||||
Различные скрипты и утилиты
|
Различные скрипты и утилиты
|
||||||
|
|
||||||
|
### Установка
|
||||||
|
```shell
|
||||||
|
apk add php php-session jq
|
||||||
|
```
|
||||||
|
|
||||||
|
```shell
|
||||||
|
chmod -R +x ./bin
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
import {JSONRPC} from "./jrpc.js";
|
||||||
|
|
||||||
|
const App = {
|
||||||
|
config: {},
|
||||||
|
networks: {},
|
||||||
|
RPC: JSONRPC
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
App.auth = async function () {
|
||||||
|
let authorized = await JSONRPC.__invoke("auth");
|
||||||
|
if (!authorized) {
|
||||||
|
do {
|
||||||
|
let pass = prompt("Password");
|
||||||
|
authorized = await JSONRPC.__invoke("auth", {
|
||||||
|
"password": pass
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!authorized) {
|
||||||
|
alert("Wrong password");
|
||||||
|
}
|
||||||
|
} while (!authorized);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.config = await JSONRPC.__invoke("getConfig");
|
||||||
|
this.networks = await JSONRPC.__invoke("getNetworks");
|
||||||
|
|
||||||
|
|
||||||
|
for (const key of this.config.plugins) {
|
||||||
|
$("body").append("<" + "script type='module' src='/plugins/" + key + "/plugin.js'><" + "/script>");
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#loading").hide();
|
||||||
|
|
||||||
|
this.fillNetworks();
|
||||||
|
|
||||||
|
$("#panel").show();
|
||||||
|
}
|
||||||
|
|
||||||
|
App.fillNetworks = function () {
|
||||||
|
const that = this;
|
||||||
|
let proto = $("#net-table tr");
|
||||||
|
proto.detach();
|
||||||
|
|
||||||
|
for (const net in this.networks) {
|
||||||
|
let item = proto.clone();
|
||||||
|
item.find("input").prop('checked', this.config.networks.indexOf(net) !== -1).change(function () {
|
||||||
|
if ($(this).prop('checked')) {
|
||||||
|
that.config.networks.push(net);
|
||||||
|
} else {
|
||||||
|
that.config.networks = that.config.networks.filter(e => e !== net);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
item.find("span").text(net);
|
||||||
|
$("#net-table").append(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
App.render = async function () {
|
||||||
|
await this.auth();
|
||||||
|
$("#save").click(function () {
|
||||||
|
const self = $(this);
|
||||||
|
self.prop("disabled", true);
|
||||||
|
(async function () {
|
||||||
|
await JSONRPC.__invoke("setConfig", App.config);
|
||||||
|
alert("Config saved!");
|
||||||
|
self.prop("disabled", false);
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
$("#restart-quagga").click(function () {
|
||||||
|
if (confirm("Are you sure?")) {
|
||||||
|
const self = $(this);
|
||||||
|
self.prop("disabled", true);
|
||||||
|
(async function () {
|
||||||
|
try {
|
||||||
|
alert(await JSONRPC.__invoke("restart_quagga"));
|
||||||
|
} finally {
|
||||||
|
setTimeout(() => {
|
||||||
|
self.prop("disabled", false);
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export {App};
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Routing config</title>
|
||||||
|
<script src="jquery-3.7.1.min.js" type="text/javascript"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<span id="loading">Loading...</span>
|
||||||
|
|
||||||
|
<div style="display: none" id="panel">
|
||||||
|
Selected networks:
|
||||||
|
<table id="net-table">
|
||||||
|
<tr>
|
||||||
|
<td><input type="checkbox"></td>
|
||||||
|
<td><span>Network name</span></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div id="buttons">
|
||||||
|
<button id="save">Save</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import {App} from "./App.js";
|
||||||
|
|
||||||
|
$(document).ready(function () {
|
||||||
|
App.render();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
#!/bin/sh
|
||||||
|
SELFDIR=`dirname $0`
|
||||||
|
ROOT=`realpath $SELFDIR/..`
|
||||||
|
CFGFILE=$ROOT/config.json
|
||||||
|
|
||||||
|
if ! test -f "$CFGFILE"; then
|
||||||
|
echo Config file $CFGFILE not found
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
HOST=`jq -r .web.host $CFGFILE`
|
||||||
|
PORT=`jq -r .web.port $CFGFILE`
|
||||||
|
php -S $HOST:$PORT $ROOT/server.php
|
||||||
|
|
@ -16,7 +16,7 @@ class Config implements ArrayAccess
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->path = dirname(__DIR__, 2) . "/config.json";
|
$this->path = dirname(__DIR__) . "/config.json";
|
||||||
}
|
}
|
||||||
|
|
||||||
public function read(): void
|
public function read(): void
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
interface IPluggable
|
||||||
|
{
|
||||||
|
const PROTECTED_NAMES = ["enable", "disable", "render", "init"];
|
||||||
|
|
||||||
|
public function enable();
|
||||||
|
|
||||||
|
public function disable();
|
||||||
|
|
||||||
|
|
||||||
|
public function init();
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
|
||||||
class NetworkConfigReader
|
class NetworkConfigReader
|
||||||
{
|
{
|
||||||
private array $configs = [];
|
private array $configs = [];
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$path = dirname(__DIR__, 2) . "/networks";
|
$path = dirname(__DIR__) . "/networks";
|
||||||
foreach (new IteratorIterator(new DirectoryIterator($path)) as $file) {
|
foreach (new IteratorIterator(new DirectoryIterator($path)) as $file) {
|
||||||
/**
|
/**
|
||||||
* @var SplFileInfo $file
|
* @var SplFileInfo $file
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class RPC
|
||||||
|
{
|
||||||
|
private Config $config;
|
||||||
|
private array $plugins = [];
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->config = new Config();
|
||||||
|
$this->config->read();
|
||||||
|
|
||||||
|
foreach ($this->config["plugins"] as $plugin) {
|
||||||
|
try {
|
||||||
|
$meta = $this->getPluginMetadata($plugin);
|
||||||
|
$this->loadPlugin($plugin, $meta["class"]);
|
||||||
|
} catch (Error $e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getPluginMetadata($name): array
|
||||||
|
{
|
||||||
|
$root = dirname(__DIR__) . "/plugins/" . $name;
|
||||||
|
if (!file_exists($root)) {
|
||||||
|
throw new RuntimeException("Plugin $name dir not found");
|
||||||
|
}
|
||||||
|
$metafile = $root . "/metadata.json";
|
||||||
|
if (!file_exists($metafile)) {
|
||||||
|
throw new RuntimeException("Plugin $name metadata not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
$meta = @json_decode(@file_get_contents($metafile), true);
|
||||||
|
|
||||||
|
if ($meta === null) {
|
||||||
|
throw new RuntimeException("Unable to parse $name plugin metadata");
|
||||||
|
}
|
||||||
|
return $meta;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function loadPlugin($name, $classname): void
|
||||||
|
{
|
||||||
|
$file = dirname(__DIR__) . "/plugins/" . $name . "/" . $classname . ".php";
|
||||||
|
if (!file_exists($file)) {
|
||||||
|
throw new RuntimeException("Plugin $name class $classname not found");
|
||||||
|
}
|
||||||
|
require_once $file;
|
||||||
|
|
||||||
|
$instance = new $classname();
|
||||||
|
if (!($instance instanceof IPluggable)) {
|
||||||
|
throw new RuntimeException("Class $classname have to implement IPluggable");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->plugins[$name] = $instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConfig(): array
|
||||||
|
{
|
||||||
|
$this->checkAuth();
|
||||||
|
return $this->config->asArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setConfig($config): bool
|
||||||
|
{
|
||||||
|
$this->config->fromArray($config);
|
||||||
|
$this->config->save();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkAuth(): void
|
||||||
|
{
|
||||||
|
$auth = $_SESSION["auth"] ?? false;
|
||||||
|
if (!$auth) {
|
||||||
|
throw new RuntimeException("Unauthorized");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNetworks(): array
|
||||||
|
{
|
||||||
|
$this->checkAuth();
|
||||||
|
return (new NetworkConfigReader())->getConfigs();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function logout(): void
|
||||||
|
{
|
||||||
|
$_SESSION["auth"] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function auth($params): bool
|
||||||
|
{
|
||||||
|
if (isset($params["password"])) {
|
||||||
|
if ($this->comparePassword($params["password"])) {
|
||||||
|
return $_SESSION["auth"] = true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $_SESSION["auth"] ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function comparePassword($passwd): bool
|
||||||
|
{
|
||||||
|
$pass = $this->config["password"];
|
||||||
|
if ($pass["type"] == "plaintext") {
|
||||||
|
return $pass["data"] == $passwd;
|
||||||
|
} else if ($pass["type"] == "hash") {
|
||||||
|
return $this->hash($passwd) == $pass["data"];
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function hash($what): string
|
||||||
|
{
|
||||||
|
return md5(sha1($what) . md5($what));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __invoke($method, $args)
|
||||||
|
{
|
||||||
|
$parts = explode("::", $method);
|
||||||
|
$isPlugin = count($parts) > 1;
|
||||||
|
|
||||||
|
if ($isPlugin) {
|
||||||
|
$this->checkAuth();
|
||||||
|
$plugin = $this->plugins[$parts[0]];
|
||||||
|
$methodname = $parts[1];
|
||||||
|
if (in_array($methodname, IPluggable::PROTECTED_NAMES)) {
|
||||||
|
throw new RuntimeException("Unable to invoke internal methods");
|
||||||
|
}
|
||||||
|
return call_user_func([$plugin, $methodname], $args);
|
||||||
|
} else {
|
||||||
|
$cls = new ReflectionClass(__CLASS__);
|
||||||
|
$method = $cls->getMethod($method);
|
||||||
|
if (!$method or !$method->isPublic()) {
|
||||||
|
throw new RuntimeException("Unable to find method");
|
||||||
|
}
|
||||||
|
return $method->invoke($this, $args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
|
||||||
class RoutingTableReader
|
class RoutingTableReader
|
||||||
{
|
{
|
||||||
private $routes = [];
|
private $routes = [];
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class WebRouter
|
||||||
|
{
|
||||||
|
private string $requestBody;
|
||||||
|
private string $requestedFile;
|
||||||
|
private string $URI;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->requestBody = @file_get_contents('php://input');
|
||||||
|
$this->requestedFile = dirname(__DIR__) . "/" . $_SERVER["REQUEST_URI"];
|
||||||
|
$this->URI = $_SERVER["REQUEST_URI"];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleRequest(): void
|
||||||
|
{
|
||||||
|
@session_start();
|
||||||
|
try {
|
||||||
|
if (str_starts_with($this->URI, "/assets") or str_starts_with($this->URI, "/plugins") and str_ends_with($this->URI, ".js")) {
|
||||||
|
$this->handleAsset();
|
||||||
|
} elseif (!str_starts_with($this->URI, "/rpc")) {
|
||||||
|
$this->redirect("/assets/index.html");
|
||||||
|
} else {
|
||||||
|
$this->handleJRPC();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
session_write_close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function handleJRPC(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$request = @json_decode($this->requestBody, true);
|
||||||
|
if ($request === null) {
|
||||||
|
throw new RuntimeException("Failed to parse JRPC");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (["id", "jsonrpc", "method", "params"] as $param) {
|
||||||
|
if (!isset($request[$param])) {
|
||||||
|
throw new RuntimeException("Bad JRPC structure");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$rpc = new RPC();
|
||||||
|
$response = $rpc($request["method"], $request["params"]);
|
||||||
|
|
||||||
|
header("content-type: application/json");
|
||||||
|
|
||||||
|
echo json_encode([
|
||||||
|
"jsonrpc" => "2.0",
|
||||||
|
"id" => $request["id"] ?? 0,
|
||||||
|
"result" => $response
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (Error $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode([
|
||||||
|
"jsonrpc" => "2.0",
|
||||||
|
"id" => $request["id"],
|
||||||
|
"error" => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function handleAsset(): void
|
||||||
|
{
|
||||||
|
if (!file_exists($this->requestedFile)) {
|
||||||
|
http_response_code(404);
|
||||||
|
echo "File not found: " . $this->URI;
|
||||||
|
} else {
|
||||||
|
header("content-type: " . mime_content_type($this->requestedFile));
|
||||||
|
echo file_get_contents($this->requestedFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function redirect($where): void
|
||||||
|
{
|
||||||
|
http_response_code(302);
|
||||||
|
header("location: " . $where);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,5 +6,9 @@
|
||||||
"networks": [
|
"networks": [
|
||||||
"google"
|
"google"
|
||||||
],
|
],
|
||||||
"zebra_restart_cmd": "/etc/init.d/zebra restart"
|
"web": {
|
||||||
|
"port":8000,
|
||||||
|
"host":"0.0.0.0"
|
||||||
|
},
|
||||||
|
"plugins":[]
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
#!/usr/bin/php
|
#!/usr/bin/php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use classes\Config;
|
||||||
|
use classes\NetworkConfigReader;
|
||||||
|
|
||||||
if (!isset($argv[1])) {
|
if (!isset($argv[1])) {
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* public function restart_quagga()
|
||||||
|
{
|
||||||
|
return shell_exec("./zebracfg.php");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
class QuaggaPlugin implements IPluggable
|
||||||
|
{
|
||||||
|
public function restart()
|
||||||
|
{
|
||||||
|
return shell_exec("./zebracfg.php");
|
||||||
|
}
|
||||||
|
public function enable()
|
||||||
|
{
|
||||||
|
// TODO: Implement enable() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function disable()
|
||||||
|
{
|
||||||
|
// TODO: Implement disable() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
// TODO: Implement init() method.
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"class": "QuaggaPlugin",
|
||||||
|
"config": {
|
||||||
|
"quagga": {
|
||||||
|
"restart_cmd": "/etc/init.d/zebra restart",
|
||||||
|
"file": "/etc/quagga/zebra.conf"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
//<button id="restart-quagga">Restart quagga</button>
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Updates implements IPluggable
|
||||||
|
{
|
||||||
|
|
||||||
|
public function enable()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function disable()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render()
|
||||||
|
{
|
||||||
|
return file_get_contents(__DIR__ . "/plugin.js");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function init()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function check(): ?bool
|
||||||
|
{
|
||||||
|
$data = str_replace("=", "", @shell_exec("git --no-pager fetch --dry-run --porcelain --verbose 2>&1 | grep refs"));
|
||||||
|
$parts = explode(" ", trim($data));
|
||||||
|
if (count($parts) < 3) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $parts[0] != $parts[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function install(): string|bool|null
|
||||||
|
{
|
||||||
|
return @shell_exec("git --no-pager pull --verbose 2>&1");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"class": "Updates",
|
||||||
|
"config": {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
import {JSONRPC} from "/assets/jrpc.js";
|
||||||
|
|
||||||
|
(async function () {
|
||||||
|
$("#panel").prepend(`<div id="update-panel">Checking for updates...</div><hr>`);
|
||||||
|
let state = (await JSONRPC.__invoke("updates::check"));
|
||||||
|
if (state === null) {
|
||||||
|
$("#update-panel").html(`<span style="color:red;">Error checking updates</span>`);
|
||||||
|
} else if (state === false) {
|
||||||
|
$("#update-panel").html(`<span style="color:black;">There is no updates</span>`);
|
||||||
|
} else if (state === true) {
|
||||||
|
$("#update-panel").html(`<span style="color:green;">Some updates are available</span> <button>Update</button>`);
|
||||||
|
$("#update-panel button").click(async function () {
|
||||||
|
$("#panel").hide();
|
||||||
|
$("#loading").show().text("Installing updates...");
|
||||||
|
try {
|
||||||
|
console.log(await JSONRPC.__invoke("updates::install"));
|
||||||
|
} finally {
|
||||||
|
setTimeout(() => location.reload(), 10000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?php
|
||||||
|
require_once __DIR__ . "/loader.php";
|
||||||
|
$router = new WebRouter();
|
||||||
|
$router->handleRequest();
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>Routing config</title>
|
|
||||||
<script src="jquery-3.7.1.min.js" type="text/javascript"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<span id="loading">Loading...</span>
|
|
||||||
|
|
||||||
<div style="display: none" id="panel">
|
|
||||||
|
|
||||||
<div id="update-panel">Checking for updates...</div>
|
|
||||||
<hr>
|
|
||||||
Selected networks:
|
|
||||||
<table id="net-table">
|
|
||||||
<tr>
|
|
||||||
<td><input type="checkbox"></td>
|
|
||||||
<td><span>Network name</span></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<button id="save">Save</button>
|
|
||||||
<button id="restart-quagga">Restart quagga</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script type="module">
|
|
||||||
import {JSONRPC} from "./jrpc.js";
|
|
||||||
|
|
||||||
let config = {};
|
|
||||||
let networks = {};
|
|
||||||
|
|
||||||
async function auth() {
|
|
||||||
let authorized = await JSONRPC.__invoke("auth");
|
|
||||||
if (!authorized) {
|
|
||||||
do {
|
|
||||||
let pass = prompt("Password");
|
|
||||||
authorized = await JSONRPC.__invoke("auth", {
|
|
||||||
"password": pass
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!authorized) {
|
|
||||||
alert("Wrong password");
|
|
||||||
}
|
|
||||||
} while (!authorized);
|
|
||||||
}
|
|
||||||
|
|
||||||
config = await JSONRPC.__invoke("getConfig");
|
|
||||||
networks = await JSONRPC.__invoke("getNetworks");
|
|
||||||
|
|
||||||
$("#loading").hide();
|
|
||||||
|
|
||||||
fillNetworks();
|
|
||||||
|
|
||||||
$("#panel").show();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function fillNetworks() {
|
|
||||||
let proto = $("#net-table tr");
|
|
||||||
proto.detach();
|
|
||||||
|
|
||||||
for (const net in networks) {
|
|
||||||
let item = proto.clone();
|
|
||||||
item.find("input").prop('checked', config.networks.indexOf(net) !== -1).change(function () {
|
|
||||||
if ($(this).prop('checked')) {
|
|
||||||
config.networks.push(net);
|
|
||||||
} else {
|
|
||||||
config.networks = config.networks.filter(e => e !== net);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
item.find("span").text(net);
|
|
||||||
$("#net-table").append(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).ready(function () {
|
|
||||||
auth();
|
|
||||||
$("#save").click(function () {
|
|
||||||
const self = $(this);
|
|
||||||
self.prop("disabled", true);
|
|
||||||
(async function () {
|
|
||||||
await JSONRPC.__invoke("setConfig", config);
|
|
||||||
alert("Config saved!");
|
|
||||||
self.prop("disabled", false);
|
|
||||||
})();
|
|
||||||
});
|
|
||||||
$("#restart-quagga").click(function () {
|
|
||||||
if (confirm("Are you sure?")) {
|
|
||||||
const self = $(this);
|
|
||||||
self.prop("disabled", true);
|
|
||||||
(async function () {
|
|
||||||
try {
|
|
||||||
alert(await JSONRPC.__invoke("restart_quagga"));
|
|
||||||
} finally {
|
|
||||||
setTimeout(() => {
|
|
||||||
self.prop("disabled", false);
|
|
||||||
}, 5000);
|
|
||||||
}
|
|
||||||
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
(async function () {
|
|
||||||
let state = (await JSONRPC.__invoke("checkUpdates"));
|
|
||||||
if (state === null) {
|
|
||||||
$("#update-panel").html(`<span style="color:red;">Error checking updates</span>`);
|
|
||||||
} else if (state === false) {
|
|
||||||
$("#update-panel").html(`<span style="color:black;">There is no updates</span>`);
|
|
||||||
} else if (state === true) {
|
|
||||||
$("#update-panel").html(`<span style="color:green;">Some updates are available</span> <button>Update</button>`);
|
|
||||||
$("#update-panel button").click(async function () {
|
|
||||||
$("#panel").hide();
|
|
||||||
$("#loading").show().text("Installing updates...");
|
|
||||||
try {
|
|
||||||
console.log(await JSONRPC.__invoke("installUpdates"));
|
|
||||||
} finally {
|
|
||||||
setTimeout(() => location.reload(), 10000);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
165
utils/server.php
165
utils/server.php
|
|
@ -1,165 +0,0 @@
|
||||||
<?php
|
|
||||||
require_once __DIR__ . "/loader.php";
|
|
||||||
|
|
||||||
if (str_starts_with($_SERVER["REQUEST_URI"], "/assets")) {
|
|
||||||
$file = __DIR__ . $_SERVER["REQUEST_URI"];
|
|
||||||
if (!file_exists($file)) {
|
|
||||||
http_response_code(404);
|
|
||||||
echo "File not found: " . $_SERVER["REQUEST_URI"];
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
header("content-type: " . mime_content_type($file));
|
|
||||||
echo file_get_contents($file);
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!str_starts_with($_SERVER["REQUEST_URI"], "/rpc")) {
|
|
||||||
http_response_code(302);
|
|
||||||
header("location: /assets/index.html");
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
$request = json_decode(@file_get_contents('php://input'), true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if ($request === null) {
|
|
||||||
throw new RuntimeException("Failed to parse JRPC");
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (["id", "jsonrpc", "method", "params"] as $param) {
|
|
||||||
if (!isset($request[$param])) {
|
|
||||||
throw new RuntimeException("Bad JRPC structure");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* {"jsonrpc":"2.0","method":"auth","params":{},"id":1}
|
|
||||||
*/
|
|
||||||
|
|
||||||
@session_start();
|
|
||||||
|
|
||||||
class RPC
|
|
||||||
{
|
|
||||||
private Config $config;
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->config = new Config();
|
|
||||||
$this->config->read();
|
|
||||||
}
|
|
||||||
|
|
||||||
private function hash($what): string
|
|
||||||
{
|
|
||||||
return md5(sha1($what) . md5($what));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getConfig(): array
|
|
||||||
{
|
|
||||||
$this->checkAuth();
|
|
||||||
return $this->config->asArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public function restart_quagga()
|
|
||||||
{
|
|
||||||
return shell_exec("./zebracfg.php");
|
|
||||||
}
|
|
||||||
|
|
||||||
public function checkUpdates()
|
|
||||||
{
|
|
||||||
$this->checkAuth();
|
|
||||||
$data = str_replace("=","",@shell_exec("git --no-pager fetch --dry-run --porcelain --verbose 2>&1 | grep refs"));
|
|
||||||
$parts = explode(" ", trim($data));
|
|
||||||
if(count($parts) < 3){
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $parts[0] != $parts[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function installUpdates()
|
|
||||||
{
|
|
||||||
return @shell_exec("git --no-pager pull --verbose 2>&1");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function getNetworks(): array
|
|
||||||
{
|
|
||||||
$this->checkAuth();
|
|
||||||
return (new NetworkConfigReader())->getConfigs();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function setConfig($config): bool
|
|
||||||
{
|
|
||||||
$this->config->fromArray($config);
|
|
||||||
$this->config->save();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private function checkAuth(): void
|
|
||||||
{
|
|
||||||
$auth = $_SESSION["auth"] ?? false;
|
|
||||||
if (!$auth) {
|
|
||||||
throw new RuntimeException("Unauthorized");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function comparePassword($passwd): bool
|
|
||||||
{
|
|
||||||
$pass = $this->config["password"];
|
|
||||||
if ($pass["type"] == "plaintext") {
|
|
||||||
return $pass["data"] == $passwd;
|
|
||||||
} else if ($pass["type"] == "hash") {
|
|
||||||
return $this->hash($passwd) == $pass["data"];
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function logout(): void
|
|
||||||
{
|
|
||||||
$_SESSION["auth"] = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function auth($params): bool
|
|
||||||
{
|
|
||||||
if (isset($params["password"])) {
|
|
||||||
if ($this->comparePassword($params["password"])) {
|
|
||||||
return $_SESSION["auth"] = true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $_SESSION["auth"] ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __invoke($method, $args)
|
|
||||||
{
|
|
||||||
$cls = new ReflectionClass(__CLASS__);
|
|
||||||
$method = $cls->getMethod($method);
|
|
||||||
if (!$method or !$method->isPublic()) {
|
|
||||||
throw new RuntimeException("Unable to find method");
|
|
||||||
}
|
|
||||||
return $method->invoke($this, $args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$rpc = new RPC();
|
|
||||||
$response = $rpc($request["method"], $request["params"]);
|
|
||||||
|
|
||||||
header("content-type: application/json");
|
|
||||||
@session_write_close();
|
|
||||||
|
|
||||||
echo json_encode([
|
|
||||||
"jsonrpc" => "2.0",
|
|
||||||
"id" => $request["id"],
|
|
||||||
"result" => $response
|
|
||||||
]);
|
|
||||||
|
|
||||||
} catch (Exception $e) {
|
|
||||||
http_response_code(500);
|
|
||||||
echo $e->getMessage();
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
SELF=`dirname $0`
|
|
||||||
DIR=`realpath $SELF`
|
|
||||||
cd $DIR
|
|
||||||
killall php
|
|
||||||
php -S 0.0.0.0:8000 server.php
|
|
||||||
|
|
@ -1,5 +1,10 @@
|
||||||
#!/usr/bin/php
|
#!/usr/bin/php
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use classes\Config;
|
||||||
|
use classes\NetworkConfigReader;
|
||||||
|
use classes\RoutingTableReader;
|
||||||
|
|
||||||
require_once __DIR__ . "/loader.php";
|
require_once __DIR__ . "/loader.php";
|
||||||
const CFGFILE = "/etc/quagga/zebra.conf";
|
const CFGFILE = "/etc/quagga/zebra.conf";
|
||||||
const REM_PREFIX = "! routes from file ";
|
const REM_PREFIX = "! routes from file ";
|
||||||
Loading…
Reference in New Issue