Add a simple built-in web interface
This moves the existing root "/" to "/api/" and replaces it with a simple built-in web interface. It is not meant to be a replacement for ara-web but serves as a base for generating static reports without needing to install ara-web and it's dependencies. Change-Id: I2b86e54757892ab886d94c54736989d18605c6ec
This commit is contained in:
parent
9ecf5db227
commit
5ecbb4c9c6
@ -21,7 +21,7 @@ from rest_framework.test import APITestCase
|
||||
|
||||
class RootTestCase(APITestCase):
|
||||
def test_root_endpoint(self):
|
||||
result = self.client.get("/")
|
||||
result = self.client.get("/api/")
|
||||
self.assertEqual(set(result.data.keys()), set(["kind", "version", "api"]))
|
||||
self.assertEqual(result.data["kind"], "ara")
|
||||
self.assertEqual(result.data["version"], pkg_resources.get_distribution("ara").version)
|
||||
|
@ -138,6 +138,7 @@ INSTALLED_APPS = [
|
||||
"rest_framework",
|
||||
"django_filters",
|
||||
"ara.api",
|
||||
"ara.ui",
|
||||
"ara.server.apps.AraAdminConfig",
|
||||
]
|
||||
|
||||
|
@ -33,13 +33,14 @@ class APIRoot(APIView):
|
||||
"api": list(map(lambda x: urllib.parse.urljoin(
|
||||
request.build_absolute_uri(), x),
|
||||
[
|
||||
"api/v1/",
|
||||
"v1/",
|
||||
]))
|
||||
})
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path("", APIRoot.as_view()),
|
||||
path("", include("ara.ui.urls")),
|
||||
path("api/", APIRoot.as_view()),
|
||||
path("api/v1/", include("ara.api.urls")),
|
||||
path("admin/", admin.site.urls),
|
||||
]
|
||||
|
0
ara/ui/__init__.py
Normal file
0
ara/ui/__init__.py
Normal file
22
ara/ui/apps.py
Normal file
22
ara/ui/apps.py
Normal file
@ -0,0 +1,22 @@
|
||||
# Copyright (c) 2019 Red Hat, Inc.
|
||||
#
|
||||
# This file is part of ARA Records Ansible.
|
||||
#
|
||||
# ARA 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.
|
||||
#
|
||||
# ARA 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 ARA. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class UiConfig(AppConfig):
|
||||
name = "ara.ui"
|
8
ara/ui/static/css/README.rst
Normal file
8
ara/ui/static/css/README.rst
Normal file
@ -0,0 +1,8 @@
|
||||
Vendored patternfly assets
|
||||
==========================
|
||||
|
||||
https://unpkg.com/@patternfly/patternfly@2.21.5/patternfly.min.css
|
||||
https://unpkg.com/@patternfly/patternfly@2.21.5/assets/fonts/overpass-webfont/overpass-semibold.woff2
|
||||
https://unpkg.com/@patternfly/patternfly@2.21.5/assets/fonts/overpass-webfont/overpass-light.woff2
|
||||
https://unpkg.com/@patternfly/patternfly@2.21.5/assets/fonts/overpass-webfont/overpass-regular.woff2
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
2
ara/ui/static/css/patternfly.min.css
vendored
Normal file
2
ara/ui/static/css/patternfly.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
ara/ui/static/images/favicon.ico
Normal file
BIN
ara/ui/static/images/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
1
ara/ui/static/images/logo.svg
Normal file
1
ara/ui/static/images/logo.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 6.0 KiB |
62
ara/ui/templates/base.html
Normal file
62
ara/ui/templates/base.html
Normal file
@ -0,0 +1,62 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="layout-pf">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
{% if page == "index" %}
|
||||
<link rel="stylesheet" href="static/css/patternfly.min.css">
|
||||
<link rel="shortcut icon" href="static/images/favicon.ico">
|
||||
{% else %}
|
||||
<link rel="stylesheet" href="../static/css/patternfly.min.css">
|
||||
<link rel="shortcut icon" href="../static/images/favicon.ico">
|
||||
{% endif %}
|
||||
{% block head %}
|
||||
{% endblock %}
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="pf-c-page">
|
||||
<header role="banner" class="pf-c-page__header">
|
||||
<div class="pf-c-page__header-brand">
|
||||
{% if page == "index" %}
|
||||
<a class="pf-c-page__header-brand-link" href="">
|
||||
<img class="pf-c-brand" src="static/images/logo.svg" alt="ARA Records Ansible">
|
||||
{% else %}
|
||||
<a class="pf-c-page__header-brand-link" href="../">
|
||||
<img class="pf-c-brand" src="../static/images/logo.svg" alt="ARA Records Ansible">
|
||||
{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="pf-c-page__header-nav">
|
||||
<nav class="pf-c-nav" aria-label="Global">
|
||||
<button class="pf-c-nav__scroll-button" aria-label="Scroll left">
|
||||
<i class="fas fa-angle-left" aria-hidden="true"></i>
|
||||
</button>
|
||||
<ul class="pf-c-nav__horizontal-list">
|
||||
<li class="pf-c-nav__item">
|
||||
{% if page == "index" %}
|
||||
<a href="" class="pf-c-nav__link pf-m-current" aria-current="page">Playbooks</a>
|
||||
{% else %}
|
||||
<a href="../" class="pf-c-nav__link">Playbooks</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
<li class="pf-c-nav__item">
|
||||
<a href="https://ara.readthedocs.io" class="pf-c-nav__link" target="_blank">Docs</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
<main role="main" class="pf-c-page__main">
|
||||
<section class="pf-c-page__main-section pf-m-light">
|
||||
{% block body %}
|
||||
{% endblock %}
|
||||
</section>
|
||||
<section class="pf-c-page__main-section">
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
13
ara/ui/templates/file.html
Normal file
13
ara/ui/templates/file.html
Normal file
@ -0,0 +1,13 @@
|
||||
{% extends "base.html" %}
|
||||
{% block body %}
|
||||
<div class="pf-c-card" style="margin: 1em 0;" id="properties">
|
||||
<div class="pf-c-card__header pf-c-title pf-m-md">
|
||||
File: {{ file.path }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-card" style="margin: 1em 0;" id="properties">
|
||||
<div class="pf-c-card__body">
|
||||
<pre>{{ file.content | linebreaksbr }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
28
ara/ui/templates/host.html
Normal file
28
ara/ui/templates/host.html
Normal file
@ -0,0 +1,28 @@
|
||||
{% extends "base.html" %}
|
||||
{% block body %}
|
||||
<div class="pf-c-card" style="margin: 1em 0;">
|
||||
<div class="pf-c-card__header pf-c-title pf-m-md">
|
||||
Host: {{ host.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-card" style="margin: 1em 0;">
|
||||
<div class="pf-c-card__body">
|
||||
<table class="pf-c-table pf-m-grid-md" role="grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Fact</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for fact, value in host.facts.items %}
|
||||
<tr>
|
||||
<td id="{{ fact }}" style="white-space: nowrap"><a href="#{{ fact }}">{{ fact }}</a></td>
|
||||
<td>{{ value | safe }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
41
ara/ui/templates/index.html
Normal file
41
ara/ui/templates/index.html
Normal file
@ -0,0 +1,41 @@
|
||||
{% extends "base.html" %}
|
||||
{% block body %}
|
||||
<div class="pf-c-card">
|
||||
<table class="pf-c-table pf-m-grid-md" role="grid" id="playbooks">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
<th>Started</th>
|
||||
<th>Duration</th>
|
||||
<th>Plays</th>
|
||||
<th>Tasks</th>
|
||||
<th>Results</th>
|
||||
<th>Hosts</th>
|
||||
<th>Files</th>
|
||||
<th>Records</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for playbook in playbooks %}
|
||||
<tr>
|
||||
<td title="{{ playbook.path }}">
|
||||
<a href="playbook/{{ playbook.id }}.html">
|
||||
{% if playbook.name is not None %}{{ playbook.name }}{% else %}{{ playbook.path | truncatechars:30 }}{% endif %}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ playbook.status }}</td>
|
||||
<td>{{ playbook.started }}</td>
|
||||
<td>{{ playbook.duration }}</td>
|
||||
<td>{{ playbook.items.plays }}</td>
|
||||
<td>{{ playbook.items.tasks }}</td>
|
||||
<td>{{ playbook.items.results }}</td>
|
||||
<td>{{ playbook.items.hosts }}</td>
|
||||
<td>{{ playbook.items.files }}</td>
|
||||
<td>{{ playbook.items.records }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
121
ara/ui/templates/playbook.html
Normal file
121
ara/ui/templates/playbook.html
Normal file
@ -0,0 +1,121 @@
|
||||
{% extends "base.html" %}
|
||||
{% block body %}
|
||||
<div class="pf-c-card" style="margin: 1em 0;" id="properties">
|
||||
<div class="pf-c-card__header pf-c-title pf-m-md">
|
||||
<a href="#properties">Playbook properties</a>
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
<ul class="pf-c-list">
|
||||
<li>Status: {{ playbook.status }}</li>
|
||||
<li>Name: {{ playbook.name }}</li>
|
||||
<li>Path: {{ playbook.path }}</li>
|
||||
<li>Started: {{ playbook.started }}</li>
|
||||
<li>Ended: {{ playbook.ended }}</li>
|
||||
<li>Duration: {{ playbook.duration }}</li>
|
||||
<li>Ansible version: {{ playbook.ansible_version }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pf-c-card" style="margin: 1em 0;" id="arguments">
|
||||
<div class="pf-c-card__header pf-c-title pf-m-md">
|
||||
<a href="#arguments">Playbook arguments</a>
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
<ul class="pf-c-list">
|
||||
{% for arg, value in playbook.arguments.items %}
|
||||
<li>{{ arg }}: {{ value }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pf-c-card" style="margin: 1em 0;" id="hosts">
|
||||
<div class="pf-c-card__header pf-c-title pf-m-md">
|
||||
<a href="#hosts">Playbook hosts</a>
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
<table class="pf-c-table pf-m-grid-md" role="grid" id="host-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Host</td>
|
||||
<td>Changed</td>
|
||||
<td>Failed</td>
|
||||
<td>Ok</td>
|
||||
<td>Skipped</td>
|
||||
<td>Unreachable</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for host in playbook.hosts %}
|
||||
<tr>
|
||||
<td><a href="../host/{{ host.id }}.html">{{ host.name }}</a></td>
|
||||
<td>{{ host.changed }}</td>
|
||||
<td>{{ host.failed }}</td>
|
||||
<td>{{ host.ok }}</td>
|
||||
<td>{{ host.skipped }}</td>
|
||||
<td>{{ host.unreachable }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pf-c-card" style="margin: 1em 0;" id="results">
|
||||
<div class="pf-c-card__header pf-c-title pf-m-md">
|
||||
<a href="#results">Playbook task results</a>
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
<table class="pf-c-table pf-m-grid-md" role="grid" id="result-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Task</td>
|
||||
<td>Action</td>
|
||||
<td>Status</td>
|
||||
<td>Changed</td>
|
||||
<td>Host</td>
|
||||
<td>Path</td>
|
||||
<td>Handler</td>
|
||||
<td>Started</td>
|
||||
<td>Duration</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for play in playbook.plays %}
|
||||
{% for task in play.tasks %}
|
||||
{% for result in task.results %}
|
||||
<tr>
|
||||
<td><a href="../result/{{ result.id }}.html">{{ task.name }}</a></td>
|
||||
<td>{{ task.action }}</td>
|
||||
<td>{{ result.status }}</td>
|
||||
<td>{{ result.changed }}</td>
|
||||
<td>{{ result.host.name }}</td>
|
||||
<td>
|
||||
<a href="../file/{{ task.file.id }}.html">{{ task.file.path | truncatechars:30 }}:{{ task.lineno }}</a>
|
||||
</td>
|
||||
<td>{{ task.handler }}</td>
|
||||
<td>{{ result.started }}</td>
|
||||
<td>{{ result.duration }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pf-c-card" style="margin: 1em 0;" id="files">
|
||||
<div class="pf-c-card__header pf-c-title pf-m-md">
|
||||
<a href="#files">Playbook files</a>
|
||||
</div>
|
||||
<div class="pf-c-card__body">
|
||||
<ul class="pf-c-list">
|
||||
{% for file in playbook.files %}
|
||||
<li><a href="../file/{{ file.id }}.html">{{ file.path }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
28
ara/ui/templates/result.html
Normal file
28
ara/ui/templates/result.html
Normal file
@ -0,0 +1,28 @@
|
||||
{% extends "base.html" %}
|
||||
{% block body %}
|
||||
<div class="pf-c-card" style="margin: 1em 0;">
|
||||
<div class="pf-c-card__header pf-c-title pf-m-md">
|
||||
Result: {{ result.task.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="pf-c-card" style="margin: 1em 0;">
|
||||
<div class="pf-c-card__body">
|
||||
<table class="pf-c-table pf-m-grid-md" role="grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Value</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for field, value in result.content.items %}
|
||||
<tr>
|
||||
<td id="{{ field }}" style="white-space: nowrap"><a href="#{{ field }}">{{ field }}</a></td>
|
||||
<td>{{ value | safe }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
12
ara/ui/urls.py
Normal file
12
ara/ui/urls.py
Normal file
@ -0,0 +1,12 @@
|
||||
from django.urls import path
|
||||
|
||||
from ara.ui import views
|
||||
|
||||
app_name = "ui"
|
||||
urlpatterns = [
|
||||
path("", views.index, name="index"),
|
||||
path("playbook/<int:playbook_id>.html", views.playbook, name="playbook"),
|
||||
path("result/<int:result_id>.html", views.result, name="result"),
|
||||
path("file/<int:file_id>.html", views.file, name="file"),
|
||||
path("host/<int:host_id>.html", views.host, name="host"),
|
||||
]
|
30
ara/ui/views.py
Normal file
30
ara/ui/views.py
Normal file
@ -0,0 +1,30 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
from ara.clients.offline import AraOfflineClient
|
||||
|
||||
client = AraOfflineClient(run_sql_migrations=False)
|
||||
|
||||
|
||||
def index(request):
|
||||
playbooks = client.get("/api/v1/playbooks")
|
||||
return render(request, "index.html", {"page": "index", "playbooks": playbooks["results"]})
|
||||
|
||||
|
||||
def playbook(request, playbook_id):
|
||||
playbook = client.get("/api/v1/playbooks/%s" % playbook_id)
|
||||
return render(request, "playbook.html", {"playbook": playbook})
|
||||
|
||||
|
||||
def host(request, host_id):
|
||||
host = client.get("/api/v1/hosts/%s" % host_id)
|
||||
return render(request, "host.html", {"host": host})
|
||||
|
||||
|
||||
def file(request, file_id):
|
||||
file = client.get("/api/v1/files/%s" % file_id)
|
||||
return render(request, "file.html", {"file": file})
|
||||
|
||||
|
||||
def result(request, result_id):
|
||||
result = client.get("/api/v1/results/%s" % result_id)
|
||||
return render(request, "result.html", {"result": result})
|
Loading…
x
Reference in New Issue
Block a user