Browse Source

- Implement Node and NetInterface classes

- Add Parsedown dependency for rendering the node documentation
- Rename templates/locations/ to templates/location/
- Modify URIs for locations and nodes
master
Bernhard Fröhlich 2 years ago
parent
commit
f840ab6ca3

+ 3
- 0
LEGAL View File

@@ -28,6 +28,9 @@ js/popper.min.js:
js/jquery.slim.min.js:
"jQuery" used under MIT License

vendor/erusev:
"Parsedown" used under MIT License

vendor/slim:
"Slim Framework" used under MIT License


+ 1
- 0
composer.json View File

@@ -4,6 +4,7 @@
"homepage": "https://github.com/decke/nodeman",
"license": "BSD-2-Clause",
"require": {
"erusev/parsedown": "1.*",
"slim/slim": "^3.0",
"slim/flash": "^0.2.0",
"slim/php-view": "~2.0",

+ 43
- 1
composer.lock View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"content-hash": "b7dbff0428f0a12ed2b5854719dee5cc",
"content-hash": "424e00446aae856d9b313003b561f793",
"packages": [
{
"name": "container-interop/container-interop",
@@ -37,6 +37,48 @@
"homepage": "https://github.com/container-interop/container-interop",
"time": "2017-02-14T19:40:03+00:00"
},
{
"name": "erusev/parsedown",
"version": "1.6.3",
"source": {
"type": "git",
"url": "https://github.com/erusev/parsedown.git",
"reference": "728952b90a333b5c6f77f06ea9422b94b585878d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/erusev/parsedown/zipball/728952b90a333b5c6f77f06ea9422b94b585878d",
"reference": "728952b90a333b5c6f77f06ea9422b94b585878d",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"type": "library",
"autoload": {
"psr-0": {
"Parsedown": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Emanuil Rusev",
"email": "hello@erusev.com",
"homepage": "http://erusev.com"
}
],
"description": "Parser for Markdown.",
"homepage": "http://parsedown.org",
"keywords": [
"markdown",
"parser"
],
"time": "2017-05-14T14:47:48+00:00"
},
{
"name": "nikic/fast-route",
"version": "v1.2.0",

+ 75
- 5
index.php View File

@@ -160,7 +160,7 @@ $app->get('/map.js', function ($request, $response) use ($session) {
});

/* Locations */
$app->get('/locations/', function ($request, $response) use ($session) {
$app->get('/locations', function ($request, $response) use ($session) {
$loc = new Location();

return $this->view->render($response, 'locations.html', array(
@@ -168,20 +168,20 @@ $app->get('/locations/', function ($request, $response) use ($session) {
));
});

$app->get('/locations/add', function ($request, $response) use ($session) {
$app->get('/location/add', function ($request, $response) use ($session) {
if (!$session->isAuthenticated()) {
$this->flash->addMessage('error', 'Please login first');

return $response->withStatus(302)->withHeader('Location', '/');
}

return $this->view->render($response, 'locations/add.html', array(
return $this->view->render($response, 'location/add.html', array(
'css' => array('/css/leaflet.css'),
'js' => array('/js/leaflet.js', '/js/grazmap.js')
));
});

$app->post('/locations/add', function ($request, $response) use ($session) {
$app->post('/location/add', function ($request, $response) use ($session) {
if (!$session->isAuthenticated()) {
$this->flash->addMessage('error', 'Please login first');

@@ -232,11 +232,81 @@ $app->post('/locations/add', function ($request, $response) use ($session) {
'address' => $request->getParam('address')
);

return $this->view->render($response, 'locations/add.html', array(
return $this->view->render($response, 'location/add.html', array(
'data' => $data,
'css' => array('/css/leaflet.css'),
'js' => array('/js/leaflet.js', '/js/grazmap.js')
));
});


/* Nodes */
$app->get('/location/{locationid}/add', function ($request, $response, $args) use ($session) {
if (!$session->isAuthenticated()) {
$this->flash->addMessage('error', 'Please login first');

return $response->withStatus(302)->withHeader('Location', '/');
}
return $this->view->render($response, 'location/node/add.html', array(
'data' => array('locationid' => $args['locationid'])
));
});

$app->post('/location/{locationid}/add', function ($request, $response, $args) use ($session) {
if (!$session->isAuthenticated()) {
$this->flash->addMessage('error', 'Please login first');

return $response->withStatus(302)->withHeader('Location', '/');
}

$location = new Location($args['locationid']);
if ($location->owner != $session->getUser()->userid) {
$this->flash->addMessage('error', 'Permission denied');

return $response->withStatus(302)->withHeader('Location', '/');
}

if (!preg_match('/^[0-9A-Za-z]{3,50}$/', $request->getParam('name'))) {
$this->flash->addMessage('error', 'Node name is invalid. Length from 3-50. Allowed characters only 0-9, A-Z, a-z');
}
if (strlen($request->getParam('documentation')) > 16384) {
$this->flash->addMessage('error', 'Documentation is too long (max 16K)');
}

$location = new Location($args['locationid']);
if ($location->nodeExists($request->getParam('name'))) {
$this->flash->addMessage('error', 'Node name already exists');
}

/* HACK: Slim-Flash hasMessage('error') does not see messages for next request */
if (!isset($_SESSION['slimFlash']['error'])) {
$node = new node();
$node->name = $request->getParam('name');
$node->owner = $session->getUser()->userid;
$node->location = $args['locationid'],
$node->hardware = 0;
$node->documentation = $request->getParam('documentation');

if ($node->save()) {
$this->flash->addMessage('success', 'Node created');

return $response->withStatus(302)->withHeader('Location', '/location/'.$node->location.'/node/'.$node->nodeid.'/');
} else {
$this->flash->addMessage('error', 'Location creation failed');
}
}

$data = array(
'name' => $request->getParam('name'),
'documentation' => $request->getParam('documentation'),
'locationid' => $args['locationid']
);

return $this->view->render($response, 'location/node/add.html', array(
'data' => $data
));
});


$app->run();

+ 114
- 0
lib/FunkFeuer/Nodeman/Interface.php View File

@@ -0,0 +1,114 @@
<?php

namespace FunkFeuer\Nodeman;

/**
* Network Interface with assigned IP Address.
*
* @author Bernhard Froehlich <decke@bluelife.at>
* @copyright 2017 Bernhard Froehlich
* @license BSD License (2 Clause)
*
* @link https://github.com/decke/nodeman
*/
class NetInterface
{
private $_handle;
private $_data = array(
'interfaceid' => null,
'name' => null,
'node' => null,
'category' => null,
'type' => null,
'address' => null,
'status' => null,
'ping' => null,
'comment' => null
);

public function __construct($interfaceid = null)
{
$this->_handle = Config::getDbHandle();

if ($interfaceid !== null) {
$this->load($interfaceid);
}
}

public function __get($name)
{
if (array_key_exists($name, $this->_data)) {
return $this->_data[$name];
}

throw new \Exception('Undefined property '.$name.' in class '.__CLASS__);
}

public function __set($name, $value)
{
if (array_key_exists($name, $this->_data)) {
$this->_data[$name] = $value;

return true;
}

throw new \Exception('Undefined property '.$name.' in class '.__CLASS__);
}

public function load($id)
{
$stmt = $this->_handle->prepare('SELECT interfaceid, name, node, category, type, address,
status, ping, comment FROM interfaces WHERE interfaceid = ?');
if (!$stmt->execute(array($id))) {
return false;
}

while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
$this->_data = $row;

return true;
}

return false;
}

public function loadByName($name)
{
$stmt = $this->_handle->prepare('SELECT interfaceid, name, node, category, type, address,
status, ping, comment FROM interfaces WHERE name = ?');
if (!$stmt->execute(array($name))) {
return false;
}

while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
$this->_data = $row;

return true;
}

return false;
}

public function save()
{
if (!$this->locationid) {
$stmt = $this->_handle->prepare('INSERT INTO interfaces (name, node, category, type, address,
status, ping, comment) VALUES (?, ?, ?, ?, ?, ?, ?, ?)');

if ($stmt->execute(array($this->name, $this->node, $this->category, $this->type,
$this->address, $this->status, $this->ping, $this->comment))) {
$this->interfaceid = $this->_handle->lastInsertId();

return true;
}
} else {
$stmt = $this->_handle->prepare('UPDATE interfaces SET name = ?, node = ?, category = ?,
type = ?, address = ?, status = ?, ping = ?, comment = ? WHERE interfaceid = ?');

return $stmt->execute(array($this->name, $this->node, $this->category, $this->type,
$this->address, $this->status, $this->ping, $this->comment, $this->interfaceid));
}

return false;
}
}

+ 26
- 0
lib/FunkFeuer/Nodeman/Location.php View File

@@ -142,4 +142,30 @@ class Location

return $stmt->fetch(\PDO::FETCH_BOTH)[0];
}

public function getAllNodes()
{
$data = array();

$stmt = $this->_handle->prepare('SELECT nodeid FROM nodes WHERE (location = ? OR ? IS NULL) ORDER BY nodeid');
if (!$stmt->execute(array($this->location, $this->location))) {
return $data;
}

while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
$data[] = new Node($row['nodeid']);
}

return $data;
}

public function nodeExists($name)
{
$stmt = $this->_handle->prepare('SELECT count(*) FROM nodes WHERE location = ? AND LOWER(name) = LOWER(?)');
if (!$stmt->execute(array($this->location, $name))) {
return false;
}

return $stmt->fetch(\PDO::FETCH_BOTH)[0] > 0;
}
}

+ 127
- 0
lib/FunkFeuer/Nodeman/Node.php View File

@@ -0,0 +1,127 @@
<?php

namespace FunkFeuer\Nodeman;

/**
* Node implementation which groups together various interfaces.
*
* @author Bernhard Froehlich <decke@bluelife.at>
* @copyright 2017 Bernhard Froehlich
* @license BSD License (2 Clause)
*
* @link https://github.com/decke/nodeman
*/
class Node
{
private $_handle;
private $_data = array(
'nodeid' => null,
'name' => null,
'owner' => null,
'location' => null,
'hardware' => null,
'documentation' => null
);

public function __construct($nodeid = null)
{
$this->_handle = Config::getDbHandle();

if ($nodeid !== null) {
$this->load($nodeid);
}
}

public function __get($name)
{
if (array_key_exists($name, $this->_data)) {
return $this->_data[$name];
}

throw new \Exception('Undefined property '.$name.' in class Node');
}

public function __set($name, $value)
{
if (array_key_exists($name, $this->_data)) {
$this->_data[$name] = $value;

return true;
}

throw new \Exception('Undefined property '.$name.' in class Node');
}

public function load($id)
{
$stmt = $this->_handle->prepare('SELECT nodeid, name, owner, location, hardware,
documentation FROM nodes WHERE nodeid = ?');
if (!$stmt->execute(array($id))) {
return false;
}

while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
$this->_data = $row;

return true;
}

return false;
}

public function loadByName($name)
{
$stmt = $this->_handle->prepare('SELECT nodeid, name, owner, location, hardware,
documentation FROM nodes WHERE name = ?');
if (!$stmt->execute(array($name))) {
return false;
}

while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
$this->_data = $row;

return true;
}

return false;
}

public function save()
{
if (!$this->locationid) {
$stmt = $this->_handle->prepare('INSERT INTO nodes (name, owner, location, hardware,
documentation) VALUES (?, ?, ?, ?, ?)');

if ($stmt->execute(array($this->name, $this->owner, $this->location, $this->hardware,
$this->documentation))) {
$this->nodeid = $this->_handle->lastInsertId();

return true;
}
} else {
$stmt = $this->_handle->prepare('UPDATE nodes SET name = ?, owner = ?, location = ?,
hardware = ?, documentation = ? WHERE nodeid = ?');

return $stmt->execute(array($this->name, $this->owner, $this->location, $this->hardware,
$this->documentation, $this->nodeid));
}

return false;
}

public function getAllInterfaces()
{
$data = array();

$stmt = $this->_handle->prepare('SELECT interfaceid FROM interfaces WHERE (node = ? OR ? IS NULL) ORDER BY interfacid');
if (!$stmt->execute(array($this->node, $this->node))) {
return $data;
}

while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
$data[] = new Interface($row['interfaceid']);
}

return $data;
}
}

+ 5
- 2
templates/_header.html View File

@@ -28,16 +28,19 @@
<li class="nav-item active">
<a class="nav-link" href="/">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<li class="nav-item active">
<a class="nav-link" href="/map">Map</a>
</li>
<li class="nav-item active">
<a class="nav-link" href="/locations">Locations</a>
</li>
{% if not session.isAuthenticated() %}
<li class="nav-item active">
<a class="nav-link" href="/register">Register</a>
</li>
{% else %}
<li class="nav-item active">
<a class="nav-link" href="/locations/add">Add Location</a>
<a class="nav-link" href="/location/add">Add Location</a>
</li>
{% endif %}
</ul>

templates/locations/add.html → templates/location/add.html View File


+ 32
- 0
templates/location/node/add.html View File

@@ -0,0 +1,32 @@
{% include('_header.html') %}

<div class="container">

<h1>Create new Node</h1>

{% for msg in flash.getMessage('error') %}
<div class="alert alert-danger" role="alert"><strong>Sorry</strong> {{ msg }}</div>
{% endfor %}

<form action="/location/{{ data['locationid'] }}/add" method="post">
<div class="form-group">
<label for="name">Node name</label>
<input type="text" id="name" name="name" class="form-control" aria-describedby="nameHelp" value="{{ data['name'] }}">
<small id="nameHelp" class="form-text text-muted">
Length from 3-50. Allowed characters only 0-9, A-Z, a-z.
</small>
</div>

<div class="form-group">
<label for="documentation">Documentation</label>
<textarea id="documentation" name="documentation" class="form-control" aria-describedby="documentationHelp">{{ data['documentation'] }}</textarea>
<small id="documentationHelp" class="form-text text-muted">
Please use Markdown Syntax to format your node documentation. <a href="https://daringfireball.net/projects/markdown/basics" target="_blank">Markdown examples</a> and <a href="https://github.com/adam-p/markdown-here/wiki/Markdown-Here-Cheatsheet" target="_blank">more examples</a>.</p>
</small>
</div>

<button type="submit" class="btn btn-primary">Create</button>
</form>
</div>

{% include('_footer.html') %}

Loading…
Cancel
Save