mirror of
https://github.com/GNS3/gns3-registry.git
synced 2025-02-20 09:16:17 +00:00
.json => gns3a
This commit is contained in:
parent
57b1029aac
commit
66ac6fd002
19
README.rst
19
README.rst
@ -2,7 +2,8 @@ GNS3-registry
|
||||
================
|
||||
|
||||
|
||||
This is the GNS3 registry.
|
||||
This is the GNS3 registry where user can share
|
||||
appliances configurations.
|
||||
|
||||
|
||||
Add a new appliance
|
||||
@ -29,22 +30,6 @@ All tools require python3 and the installation of dependencies via:
|
||||
pip3 install -r requirements.txt
|
||||
|
||||
|
||||
Build website
|
||||
--------------
|
||||
|
||||
.. code:: bash
|
||||
|
||||
python3 build.py
|
||||
|
||||
|
||||
Run website
|
||||
-------------
|
||||
|
||||
.. code:: bash
|
||||
|
||||
python3 server.py
|
||||
|
||||
|
||||
Check appliance files
|
||||
-----------------------
|
||||
|
||||
|
163
build.py
163
build.py
@ -1,163 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2015 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
import copy
|
||||
import base64
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from check import check_schema
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
log.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
|
||||
handler.setFormatter(formatter)
|
||||
log.addHandler(handler)
|
||||
|
||||
|
||||
if os.path.exists('build'):
|
||||
for file in os.listdir('build'):
|
||||
if os.path.isdir(os.path.join('build', file)):
|
||||
shutil.rmtree(os.path.join('build', file))
|
||||
else:
|
||||
os.remove(os.path.join('build', file))
|
||||
else:
|
||||
os.mkdir('build')
|
||||
os.mkdir(os.path.join('build', 'appliances'))
|
||||
os.mkdir(os.path.join('build', 'images'))
|
||||
|
||||
|
||||
def human_filesize(num):
|
||||
for unit in ['B','KB','MB','GB']:
|
||||
if abs(num) < 1024.0:
|
||||
return "%3.1f%s" % (num, unit)
|
||||
num /= 1024.0
|
||||
return "%.1f%s" % (num, 'TB')
|
||||
|
||||
|
||||
def render(template_file, out, **kwargs):
|
||||
log.info('Build %s', out)
|
||||
directory = os.path.dirname(out)
|
||||
os.makedirs(os.path.join('build', directory), exist_ok=True)
|
||||
env = Environment(loader=FileSystemLoader('templates'))
|
||||
env.filters['nl2br'] = lambda s: s.replace('\n', '<br />')
|
||||
env.filters['jsonify'] = json.dumps
|
||||
env.filters['b64encode'] = lambda s: base64.b64encode(s.encode()).decode("utf-8")
|
||||
env.filters['human_filesize'] = human_filesize
|
||||
template = env.get_template(template_file)
|
||||
template.stream(**kwargs).dump(os.path.join('build', out))
|
||||
|
||||
|
||||
def keep_only_version_with_appliance(md5sum, appliance):
|
||||
"""
|
||||
Filter appliance version in order to keep only the
|
||||
version where the image is present.
|
||||
|
||||
:param md5sum: Md5sum of the image
|
||||
:param appliance: Appliance hash
|
||||
:returns: List of version
|
||||
"""
|
||||
|
||||
new_versions = []
|
||||
for version in appliance["versions"]:
|
||||
found = False
|
||||
for image in version["images"].values():
|
||||
if image["md5sum"] == md5sum:
|
||||
found = True
|
||||
break
|
||||
if found:
|
||||
new_versions.append(version)
|
||||
return new_versions
|
||||
|
||||
|
||||
def generate_appliances_pages(group_by, appliances):
|
||||
"""
|
||||
This will generate page by grouping appliances
|
||||
|
||||
:param: Group appliance by this key
|
||||
:param: Array of appliances
|
||||
"""
|
||||
|
||||
keys = set()
|
||||
for appliance in appliances:
|
||||
keys.add(appliance[group_by])
|
||||
|
||||
for key in keys:
|
||||
filtered_appliances = []
|
||||
for appliance in appliances:
|
||||
if appliance[group_by] == key:
|
||||
filtered_appliances.append(appliance)
|
||||
render('appliances.html', os.path.join('appliances', group_by, key + '.html'), appliances=filtered_appliances, title=key)
|
||||
render('group_by.html', os.path.join('appliances', group_by, 'index.html'), keys=keys, group_by=group_by)
|
||||
|
||||
render('index.html', 'index.html')
|
||||
render('chat.html', 'chat.html')
|
||||
render('downloads.html', 'downloads.html')
|
||||
render('myimages.html', 'myimages.html')
|
||||
|
||||
|
||||
appliances = []
|
||||
for appliance_file in os.listdir('appliances'):
|
||||
log.info("Check the schema for " + appliance_file)
|
||||
check_schema(appliance_file)
|
||||
|
||||
log.info("Process " + appliance_file)
|
||||
out_filename = appliance_file[:-5]
|
||||
with open(os.path.join('appliances', appliance_file)) as f:
|
||||
appliance = json.load(f)
|
||||
appliance['id'] = out_filename
|
||||
|
||||
# Resolve version image to the corresponding file
|
||||
for version in appliance['versions']:
|
||||
for image_type, filename in version['images'].items():
|
||||
found = False
|
||||
for file in appliance['images']:
|
||||
if file['filename'] == filename:
|
||||
version['images'][image_type] = copy.copy(file)
|
||||
version['images'][image_type]["type"] = image_type
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
log.critical('Image for {} {} with filename {} is missing'.format(appliance["name"], version["name"], file['filename']))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
render('appliance.html', os.path.join('appliances', out_filename + '.html'), appliance=appliance)
|
||||
appliances.append(appliance)
|
||||
|
||||
# Build a page named with the md5sum of each file of the appliance
|
||||
# it's allow to get the appliance informations via HTTP with just an md5sum
|
||||
# it's what powered the import feature
|
||||
for image in appliance['images']:
|
||||
# We keep only version with this image in the page
|
||||
image_appliance = copy.copy(appliance)
|
||||
image_appliance['versions'] = keep_only_version_with_appliance(image['md5sum'], appliance)
|
||||
render('appliance.html', os.path.join('images', image['md5sum'] + '.html'), appliance=image_appliance)
|
||||
|
||||
render('appliances.html', os.path.join('appliances', 'index.html'), appliances=appliances, title="All", show_filter=True)
|
||||
|
||||
generate_appliances_pages("category", appliances)
|
||||
generate_appliances_pages("status", appliances)
|
||||
generate_appliances_pages("vendor_name", appliances)
|
||||
|
@ -1,3 +0,0 @@
|
||||
-rrequirements.txt
|
||||
|
||||
pytest==2.6.4
|
@ -1,2 +1 @@
|
||||
Jinja2==2.7.3
|
||||
json-schema==2.4.0
|
||||
|
29
server.py
29
server.py
@ -1,29 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2015 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import http.server
|
||||
import socketserver
|
||||
|
||||
PORT = 8001
|
||||
|
||||
os.chdir('build')
|
||||
Handler = http.server.SimpleHTTPRequestHandler
|
||||
httpd = socketserver.TCPServer(('', PORT), Handler)
|
||||
|
||||
print('Serving at port', PORT)
|
||||
httpd.serve_forever()
|
@ -1,98 +0,0 @@
|
||||
{% extends "layout/default.html" %}
|
||||
|
||||
{% block script %}
|
||||
|
||||
function download(appliance, md5sum) {
|
||||
if (gns3_button(function() {
|
||||
return gns3.download(appliance, md5sum)
|
||||
})) {
|
||||
gns3_notif("success", "You can see the download progress in the <a href=\"/downloads.html\">Downloads</a> section");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var installable_versions = gns3.installableVersions("{{appliance|jsonify|b64encode}}");
|
||||
|
||||
var images_md5sum = [];
|
||||
var images = gns3.images();
|
||||
for (var i in images) {
|
||||
images_md5sum.push(images[i].md5sum);
|
||||
}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="jumbotron">
|
||||
{% if appliance["status"] == "broken" %}
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
|
||||
<span class="sr-only">Error:</span>
|
||||
This appliance is actually not working
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if appliance["status"] == "experimental" %}
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
|
||||
<span class="sr-only">Error:</span>
|
||||
This appliance is actually experimental
|
||||
</div>
|
||||
{% endif %}
|
||||
<h1>{{ appliance["name"] }}</h1>
|
||||
Category {{ appliance["category"] }}<br />
|
||||
Product: <a href="{{ appliance["product_url"] }}">{{ appliance["product_name"] }}</a><br />
|
||||
Vendor: <a href="{{ appliance["vendor_url"] }}">{{ appliance["vendor_name"] }}</a><br />
|
||||
Documentation: <a href="{{ appliance["documentation_url"] }}">{{ appliance["documentation_url"] }}</a><br />
|
||||
Status: {{ appliance["status"] }}<br />
|
||||
Maintainer: <a href="mailto:{{ appliance["maintainer_email"] }}">{{ appliance["maintainer"] }}</a>
|
||||
|
||||
<p>{{ appliance["description"] | nl2br }}</p>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
{% if appliance["usage"] %}
|
||||
<h2>Usage</h2>
|
||||
<p>{{ appliance["usage"] | nl2br }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if "qemu" in appliance %}
|
||||
<h2>Qemu settings</h2>
|
||||
{% for key in appliance["qemu"] %}
|
||||
{{ key }}: {{ appliance["qemu"][key] }}<br />
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% for version in appliance["versions"] | reverse %}
|
||||
<h2>{{ appliance["name"] }} {{version["name"]}}</h2>
|
||||
<button class="btn btn-primary btn-lg" id="install_button_{{loop.index}}" type="button">Install</button>
|
||||
<script>
|
||||
gns3_show_if("#install_button_{{loop.index}}", installable_versions.indexOf("{{version["name"]}}") != -1)
|
||||
</script>
|
||||
<h3>Require files</h3>
|
||||
|
||||
{% set version_id = loop.index %}
|
||||
{% for image in version.images.values() %}
|
||||
<h4>{{image["filename"]}}</h4>
|
||||
Slot: {{image["type"]}}<br />
|
||||
File Version: {{image["version"]}}<br />
|
||||
Size: {{ image["filesize"] | human_filesize}}<br />
|
||||
Checksum: {{image["md5sum"]}}<br />
|
||||
Download url: <a href="{{image["download_url"]}}">{{image["download_url"]}}</a><br />
|
||||
|
||||
{% if "direct_download_url" in image %}
|
||||
Direct download url: <a href="{{image["direct_download_url"]}}">{{image["direct_download_url"]}}</a><br />
|
||||
<button id="download_button_{{version_id}}_{{loop.index}}" class="btn btn-primary btn-lg" type="button" onclick='return download("{{appliance|jsonify|b64encode}}", "{{image["md5sum"]}}")'>Download</button>
|
||||
{% endif %}
|
||||
|
||||
<button id="import_button_{{version_id}}_{{loop.index}}" class="btn btn-primary btn-lg" type="button" onclick="gns3_button(function() { return gns3.importAppliance(); })">Import file</button>
|
||||
|
||||
<script>
|
||||
gns3_show_if("#download_button_{{version_id}}_{{loop.index}}", images_md5sum.indexOf("{{image["md5sum"]}}") == -1)
|
||||
gns3_show_if("#import_button_{{version_id}}_{{loop.index}}", images_md5sum.indexOf("{{image["md5sum"]}}") == -1)
|
||||
</script>
|
||||
|
||||
<hr />
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endblock %}
|
@ -1,17 +0,0 @@
|
||||
{% extends "layout/default.html" %}
|
||||
{% block body %}
|
||||
{% if show_filter %}
|
||||
<h1>Filter by</h1>
|
||||
<ul>
|
||||
<li><a href="/appliances/category">Category</a></li>
|
||||
<li><a href="/appliances/status">Status</a></li>
|
||||
<li><a href="/appliances/vendor_name">Vendor</a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
<h1>{{title | capitalize | replace("_", " ")}} appliances</h1>
|
||||
<ul>
|
||||
{% for appliance in appliances|sort(attribute='name') %}
|
||||
<li><a href="/appliances/{{appliance["id"]}}.html">{{appliance["name"]}}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
@ -1,5 +0,0 @@
|
||||
{% extends "layout/default.html" %}
|
||||
{% block body %}
|
||||
<iframe src="https://kiwiirc.com/client/irc.freenode.org/gns3" frameborder="0" style="width: 100%; height: 100%; position: absolute">
|
||||
</iframe>
|
||||
{% endblock %}
|
@ -1,48 +0,0 @@
|
||||
{% extends "layout/default.html" %}
|
||||
{% block script %}
|
||||
|
||||
function update_progress_download_div(div, download_info) {
|
||||
var percent = Math.round(100 / download_info.total * download_info.received)
|
||||
div.text(percent + "% "+ humanFileSize(download_info.received) + " / " + humanFileSize(download_info.total));
|
||||
div.attr("aria-valuenow", percent);
|
||||
div.attr("style", "width: " + percent + "%");
|
||||
if (percent == 100) {
|
||||
div.removeClass("active");
|
||||
div.removeClass("progress-bar-striped");
|
||||
}
|
||||
}
|
||||
|
||||
$(function() {
|
||||
var downloads_div = $("#downloads");
|
||||
var downloads = gns3.downloads().reverse();
|
||||
for (var i in downloads) {
|
||||
download_info = downloads[i];
|
||||
var parent_div = $("<div>");
|
||||
parent_div.text(download_info.url);
|
||||
|
||||
var progress_div = $("<div>");
|
||||
progress_div.addClass("progress");
|
||||
var div = $("<div>");
|
||||
div.addClass("progress-bar progress-bar-striped active");
|
||||
div.attr("id", "download" + download_info.id);
|
||||
div.attr("role", "progressbar");
|
||||
div.attr("aria-valuemin", 0);
|
||||
div.attr("aria-valuemax", 100);
|
||||
update_progress_download_div(div, download_info);
|
||||
progress_div.append(div);
|
||||
parent_div.append(progress_div);
|
||||
downloads_div.append(parent_div);
|
||||
}
|
||||
|
||||
gns3.downloadProgress.connect(function(download_info) {
|
||||
var div = $("#download" + download_info.id);
|
||||
update_progress_download_div(div, download_info);
|
||||
});
|
||||
});
|
||||
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
<div id="downloads">
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
@ -1,9 +0,0 @@
|
||||
{% extends "layout/default.html" %}
|
||||
{% block body %}
|
||||
<h1>{{group_by|replace("_", " ")}}</h1>
|
||||
<ul>
|
||||
{% for key in keys|sort %}
|
||||
<li><a href="/appliances/{{group_by}}/{{key}}.html">{{key|replace("_", " ")}}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
@ -1,12 +0,0 @@
|
||||
{% extends "layout/default.html" %}
|
||||
{% block body %}
|
||||
<h1>Hello Networker!</h1>
|
||||
<div>
|
||||
<a href="http://www.spiceworks.com/gns3/download-free-tftp-server-for-network-configuration-management/?utm_function=acq&utm_channel=aff&medium=aff&utm_source=150158&utm_campaign=&utm_content=tftp-cobrand-980x324"><img src="http://adn.impactradius.com/display-ad/2435-216679"></a>
|
||||
</div
|
||||
<p>
|
||||
<a class="btn btn-primary btn-lg" href="/appliances" role="button">Show appliances</a>
|
||||
|
||||
<button class="btn btn-primary btn-lg" type="button" onclick="gns3_button(function() { return gns3.importAppliance(); })">Import appliance</button>
|
||||
</p>
|
||||
{% endblock %}
|
@ -1,123 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=appliance-width, initial-scale=1">
|
||||
<title></title>
|
||||
<!-- Latest compiled and minified CSS -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
|
||||
|
||||
<!-- Optional theme -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
|
||||
|
||||
<!-- Latest compiled and minified JavaScript -->
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
|
||||
|
||||
<style>
|
||||
#notif {
|
||||
position: fixed;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
z-index: 10;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
function gns3_notif(type, msg) {
|
||||
var notif = $("#notif")
|
||||
notif.append("<div class=\"alert alert-" + type + " alert-dismissible fade in\" role=\"alert\"><button type=\"button\" class=\"close\" data-dismiss=\"alert\" aria-label=\"Close\"><span aria-hidden=\"true\">×</span></button>" + msg + "</div>")
|
||||
}
|
||||
|
||||
if (typeof gns3 !== "undefined") {
|
||||
gns3.error.connect(function(msg) {
|
||||
gns3_notif("danger", msg);
|
||||
});
|
||||
|
||||
gns3.success.connect(function(msg) {
|
||||
gns3_notif("success", msg);
|
||||
});
|
||||
|
||||
if (gns3.marketplaceApiVersion() != 1) {
|
||||
alert("Your client is out of date please update it");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Deactivate the button after success click
|
||||
*/
|
||||
gns3_button = function(callback) {
|
||||
var button = event.target;
|
||||
$(button).removeClass("btn-primary");
|
||||
if (callback()) {
|
||||
$(button).addClass("btn-success");
|
||||
button.onclick = null;
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
$(button).addClass("btn-danger");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Show or hide element depending of the test
|
||||
*/
|
||||
gns3_show_if = function(id, test) {
|
||||
if (test) {
|
||||
$(id).show();
|
||||
}
|
||||
else {
|
||||
$(id).hide();
|
||||
}
|
||||
}
|
||||
|
||||
function humanFileSize(bytes) {
|
||||
var exp = Math.log(bytes) / Math.log(1024) | 0;
|
||||
var result = (bytes / Math.pow(1024, exp)).toFixed(2);
|
||||
|
||||
return result + ' ' + (exp == 0 ? 'bytes': 'KMGTPEZY'[exp - 1] + 'B');
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<script>
|
||||
{% block script %}{% endblock %}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="notif">
|
||||
</div>
|
||||
<nav class="navbar navbar-default">
|
||||
<div class="container-fluid">
|
||||
<!-- Brand and toggle get grouped for better mobile display -->
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="/">GNS3</a>
|
||||
</div>
|
||||
<!-- Collect the nav links, forms, and other content for toggling -->
|
||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="/appliances">Appliances</a></li>
|
||||
<li><a href="/downloads.html">Downloads</a></li>
|
||||
<li><a href="/myimages.html">My images</a></li>
|
||||
<li><a href="/chat.html">Chat</a></li>
|
||||
<li><a href="https://community.gns3.com">Community</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container-fluid">
|
||||
{% block body %}{% endblock %}
|
||||
</div>
|
||||
<small id="powered"></small>
|
||||
</body>
|
||||
</html>
|
@ -1,33 +0,0 @@
|
||||
{% extends "layout/default.html" %}
|
||||
{% block script %}
|
||||
|
||||
$(function() {
|
||||
var table = $("#images");
|
||||
var images = gns3.images();
|
||||
for (var i in images) {
|
||||
image_info = images[i];
|
||||
var tr = $("<tr>");
|
||||
tr.append($("<td>").text(image_info.filename));
|
||||
tr.append($("<td>").text(image_info.md5sum));
|
||||
tr.append($("<td>").text(humanFileSize(image_info.filesize)));
|
||||
var a = $("<a>");
|
||||
a.attr("href", "/images/" + image_info.md5sum + ".html");
|
||||
a.append($("<button>").attr("class", "btn btn-primary btn-lg").text("Search appliance"));
|
||||
tr.append($("<td>").append(a))
|
||||
|
||||
table.append(tr);
|
||||
}
|
||||
});
|
||||
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
<table id="images" class="table table-striped">
|
||||
<tr>
|
||||
<th>Filename</th>
|
||||
<th>MD5 sum</th>
|
||||
<th>Size</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user