Skip to content

Commit

Permalink
basic navigation
Browse files Browse the repository at this point in the history
  • Loading branch information
ansibleguy committed Jan 15, 2024
1 parent bf1aa34 commit ee33344
Show file tree
Hide file tree
Showing 16 changed files with 284 additions and 24 deletions.
1 change: 1 addition & 0 deletions ansible-webui/aw/config/hardcoded.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
THREAD_JOIN_TIMEOUT = 3
RELOAD_INTERVAL = 10
LOGIN_PATH = '/a/login/'
LOGOUT_PATH = '/o/'

PERMISSIONS = dict(
access='AW_ACCESS',
Expand Down
42 changes: 42 additions & 0 deletions ansible-webui/aw/config/navigation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from aw.config.hardcoded import LOGOUT_PATH

NAVIGATION = {
'left': {
'Dashboard': '/ui/',
'Jobs': {
'Manage': '/ui/job/manage/',
'Logs': '/ui/job/log/',
},
'Manage': {
'Settings': '/ui/settings/',
'System': '/ui/m/',
},
},
'right': {
'GH': {
'element': '<i class="fab fa-github-square fa-2x aw-nav-right-icon" title="GitHub"></i>',
'url': 'https://github.com/ansibleguy/ansible-webui',
'login': False,
},
'DON': {
'element': '<i class="fas fa-coins fa-2x aw-nav-right-icon" title="Sponsor"></i>',
'url': 'https://github.com/sponsors/ansibleguy',
'login': False,
},
'BUG': {
'element': '<i class="fas fa-bug fa-2x aw-nav-right-icon" title="Report bug"></i>',
'url': 'https://github.com/ansibleguy/ansible-webui/issues',
'login': False,
},
'DOC': {
'element': '<i class="fas fa-book fa-2x aw-nav-right-icon" title="Documentation"></i>',
'url': 'https://ansible-webui.readthedocs.io/',
'login': False,
},
'LO': {
'element': '<i class="fas fa-sign-out-alt fa-2x aw-nav-right-icon aw-nav-right-icon-logout" title="Logout"></i>',
'url': LOGOUT_PATH,
'login': True,
},
}
}
25 changes: 21 additions & 4 deletions ansible-webui/aw/route.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.contrib.auth.decorators import login_required, user_passes_test
from django.shortcuts import HttpResponse, redirect
from django.shortcuts import HttpResponse, redirect, render
from django.conf import settings
from django.contrib.auth.views import logout_then_login

from aw.config.hardcoded import LOGIN_PATH
from aw.permission import authorized_to_exec, authorized_to_write
Expand All @@ -25,23 +26,39 @@ def ui(request, **kwargs):
if request.method == 'PUT':
return ui_exec(request)

return HttpResponse(status=200, content=b"OK - read")
return render(request, status=200, template_name='fallback.html', context={'content': 'OK - read'})


@login_required
@user_passes_test(authorized_to_write, login_url=LOGIN_PATH)
def ui_write(request, **kwargs):
return HttpResponse(status=200, content=b"OK - write")
return render(request, status=200, template_name='fallback.html', context={'content': 'OK - write'})


@login_required
@user_passes_test(authorized_to_exec, login_url=LOGIN_PATH)
def ui_exec(request, **kwargs):
return HttpResponse(status=200, content=b"OK - exec")
return render(request, status=200, template_name='fallback.html', context={'content': 'OK - exec'})


@login_required
def manage(request, **kwargs):
return render(request, status=200, template_name='fallback.html', context={
'content': '<iframe width="100%" height="100%" marginheight="0" marginwidth="0" frameborder="0" '
'scrolling="auto" src="/m/" title="Manage"></iframe>'
})


def not_implemented(request, **kwargs):
return render(request, status=404, template_name='fallback.html', context={'content': 'Not yet implemented'})


def catchall(request, **kwargs):
if request.user.is_authenticated:
return redirect(settings.LOGIN_REDIRECT_URL)

return redirect(LOGIN_PATH)


def logout(request, **kwargs):
return logout_then_login(request)
6 changes: 4 additions & 2 deletions ansible-webui/aw/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
DEBUG = deployment_dev()
ALLOWED_HOSTS = ['*']
DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
X_FRAME_OPTIONS = 'SAMEORIGIN'

# Application definition
INSTALLED_APPS = [
Expand All @@ -22,9 +23,10 @@
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'bootstrap5',
'bootstrap_datepicker_plus',
'django_user_agents',
# styles
'bootstrap5',
'fontawesomefree',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
Expand Down
10 changes: 10 additions & 0 deletions ansible-webui/aw/static/css/aw.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
body {
height: 100vh;
}
body.light {
}

Expand Down Expand Up @@ -34,6 +37,13 @@ hr {
padding-bottom: 3vh;
}

iframe {
display: block;
width: 100%;
border: none;
overflow-y: auto;
overflow-x: hidden;
}

.aw-img-float {
width: 55%;
Expand Down
4 changes: 4 additions & 0 deletions ansible-webui/aw/templates/fallback.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{% extends "./body.html" %}
{% block content %}
{{ content | safe }}
{% endblock %}
16 changes: 12 additions & 4 deletions ansible-webui/aw/templates/head.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,17 @@
{% endif %}
<link rel="icon" type="image/svg" href="{% static 'img/ansible.svg' %}">

{% load bootstrap5 %}
{% bootstrap_javascript %}
{% bootstrap_css %}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/css/bootstrap.min.css" integrity="sha512-b2QcS5SsA8tZodcDtGRELiGv5SaKSk1vDHDaQRda0htPYWZ6046lr3kJ5bAAQdpV2mmA/4v0wQF9MyU6/pDIAg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link href="{% static 'fontawesomefree/css/fontawesome.css' %}" rel="stylesheet" type="text/css">
<link href="{% static 'fontawesomefree/css/solid.css' %}" rel="stylesheet" type="text/css">
<!--<link href="{% static 'fontawesomefree/css/brands.css' %}" rel="stylesheet" type="text/css" crossorigin="anonymous" referrerpolicy="no-referrer">-->

<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.2/js/bootstrap.min.js" integrity="sha512-WW8/jxkELe2CAiE4LvQfwm1rajOS8PHasCCx+knHG0gBHt8EXxS6T6tJRTGuDQVnluuAvMxWF4j8SNFDKceLFg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="{% static 'fontawesomefree/js/fontawesome.js' %}" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="{% static 'fontawesomefree/js/solid.js' %}" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!--<script src="{% static 'fontawesomefree/js/brands.js' %}" crossorigin="anonymous" referrerpolicy="no-referrer"></script>-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.11.8/umd/popper.min.js" integrity="sha512-TPh2Oxlg1zp+kz3nFA0C5vVC6leG/6mm1z9+mA81MI5eaUVqasPLO8Cuk4gMF4gUfP5etR73rgU/8PNMsSesoQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

<link rel="stylesheet" type="text/css" href="{% static 'css/aw.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'css/aw_mobile.css' %}">

146 changes: 146 additions & 0 deletions ansible-webui/aw/templates/nav.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
{% load util %}
{% load static %}
<div class="aw-nav">
<nav class="aw-nav-container navbar navbar-expand-lg"> <!-- navbar-dark bg-dark -->
<a class="navbar-brand" href="/">
<img src="{% static 'img/ansible.svg' %}" alt="HOME" width="40" height="40" class="aw-nav-icon">
</a>
<button class="navbar-toggler aw-nav-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<i class="navbar-toggler-icon fas fa-bars"></i>
</button>
<div class="collapse navbar-collapse mr-auto" id="navbarSupportedContent">
{% if user.is_authenticated %}
<!-- START left navbar -->
{% set_var 'left'|get_nav as nav_left %}
<ul class="navbar-nav aw-nav-left">
{% for nav_key, nav_config in nav_left.items %}
{% if nav_config|get_type == 'str' %}
<!-- START basic link -->
<li class="nav-item aw-nav-main">
<a class="nav-link aw-nav-main-a1" href="{{ nav_config }}">
{{ nav_key | safe }}
</a>
</li>
<!-- END basic link -->
{% elif nav_config|get_type == 'dict' %}
<!-- START dropdown link -->
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle aw-nav-main-a1" href="#" id="navbarDropdown{{ nav_key }}" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{ nav_key }}
</a>
<ul class="dropdown-menu aw-nav-dd1" aria-labelledby="navbarDropdown{{ nav_key }}">
{% for dd_key, dd_config in nav_config.items %}
{% if dd_config|get_type == 'dict' %}
<!-- START nested dropdown link -->
<li class="dropdown-submenu">
<a class="dropdown-toggle dropdown-item aw-nav-main-a2" onClick="display_submenu('{{ nav_key }}{{ dd_key }}')" href="#" id="navbarDropdown{{ nav_key }}{{ dd_key }}" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{ dd_key }}
</a>
<ul class="dropdown-menu aw-nav-dd2" aria-labelledby="navbarDropdown{{ nav_key }}{{ dd_key }}">
{% for key, value in dd_config.items %}
<li>
<a class="dropdown-item aw-nav-main-a3" href="{{ value }}">
{{ key | safe }}
</a>
</li>
{% endfor %}
</ul>
</li>
<!-- END nested dropdown link -->
{% else %}
<li>
<a class="dropdown-item aw-nav-main-a2" href="{{ dd_config }}">
{{ dd_key | safe }}
</a>
</li>
{% endif %}
{% endfor %}
</ul>
</li>
<!-- END dropdown link -->
{% endif %}
{% endfor %}
</ul>
<!-- END left navbar -->
{% endif %}
<!-- START right navbar -->
<ul class="navbar-nav ml-auto aw-nav-right">
<li class="nav-item aw-nav-right-li">
<a type="button" class="nav-link" id="aw-switch-colorScheme">
<i class="fas fa-adjust fa-2x aw-nav-right-icon" title="Switch Dark/Light Mode"></i>
</a>
</li>
{% set_var 'right'|get_nav as nav_right %}
{% for nav_item in nav_right.values %}
{% if not nav_item.login or user.is_authenticated %}
<li class="nav-item aw-nav-right-li">
<a class="nav-link" href="{{ nav_item.url }}">{{ nav_item.element | safe }}</a>
</li>
{% endif %}
{% endfor %}
</ul>
<!-- END right navbar -->
</div>
</nav>
</div>
<!-- START submenu script -->
<script type="text/javascript">
$('.dropdown-menu a.dropdown-toggle').on('click', function(e) {
if (!$(this).next().hasClass('show')) {
$(this).parents('.dropdown-menu').first().find('.show').removeClass("show");
}
var $subMenu = $(this).next(".dropdown-menu");
$subMenu.toggleClass('show');

$(this).parents('li.nav-item.dropdown.show').on('hidden.bs.dropdown', function(e) {
$('.dropdown-submenu .show').removeClass("show");
});

return false;
});
</script>
<!-- STOP submenu script -->
<!-- START light/dark scheme toggle script -->
<script type="text/javascript">
const colorSchemeButton = document.getElementById('aw-switch-colorScheme');
const colorScheme = document.querySelector('meta[name="color-scheme"]');
const colorSchemeVar = 'color-scheme';
const colorSchemeLight = 'light';
const colorSchemeDark = 'dark';
const colorSchemeDefault = 'none';

function getColorSchema(preference) {
if (preference !== colorSchemeDefault) {
return preference;
} else if (matchMedia('(prefers-color-scheme: light)').matches) {
return colorSchemeLight;
} else {
return colorSchemeDark;
}
}

function setColorSchema(mode) {
document.body.className = mode;
colorScheme.content = mode;
localStorage.setItem(colorSchemeVar, mode);
}

function switchColorScheme(mode) {
if (mode === colorSchemeLight) {
return colorSchemeDark;
} else {
return colorSchemeLight;
}
}

let userPreference = localStorage.getItem(colorSchemeVar) || colorSchemeDefault;
setColorSchema(getColorSchema(userPreference));

if (colorSchemeButton != null) {
colorSchemeButton.onclick = function() {
userPreference = switchColorScheme(userPreference);
setColorSchema(userPreference);
};
}
</script>
<!-- STOP light/dark scheme toggle script -->
14 changes: 8 additions & 6 deletions ansible-webui/aw/templatetags/util.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django import template

from aw.config.hardcoded import VERSION
from aw.config.navigation import NAVIGATION


register = template.Library()
Expand All @@ -21,11 +22,12 @@ def get_full_uri(request):
return request.build_absolute_uri()


# @register.filter
# def format_ts(datetime_obj):
# return datetime.strftime(datetime_obj, config.DATETIME_TS_FORMAT)
@register.filter
def get_nav(key: str) -> dict:
# serves navigation config to template
return NAVIGATION[key]


# @register.simple_tag
# def random_gif() -> str:
# return f"img/500/{randint(1, 20)}.gif"
@register.filter
def get_type(value):
return str(type(value)).replace("<class '", '').replace("'>", '')
5 changes: 4 additions & 1 deletion ansible-webui/base/webserver.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from os import environ
from multiprocessing import cpu_count
from string import ascii_letters
from random import choice as random_choice

import gunicorn
from gunicorn.app.wsgiapp import WSGIApplication

from aw.config.hardcoded import PORT_WEB
Expand Down Expand Up @@ -36,6 +38,7 @@ def load_config(self):


def create_webserver() -> WSGIApplication:
gunicorn.SERVER = ''.join(random_choice(ascii_letters) for i in range(10))
run_options = {
'workers': (cpu_count() * 2) + 1,
**OPTIONS_PROD
Expand Down
6 changes: 5 additions & 1 deletion ansible-webui/route.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from django.conf.urls import include
from django.contrib import admin

from aw.route import ui, catchall
from aw.route import ui, catchall, logout, not_implemented, manage
from aw.config.hardcoded import ENV_KEY_SERVE_STATIC
from aw.serve_static import urlpatterns_static
from aw.utils.deployment import deployment_dev
Expand All @@ -19,9 +19,13 @@
# auth
path('a/', include('django.contrib.auth.urls')), # login page
path('m/', admin.site.urls), # admin page
path('o/', logout),

# app
path('ui/', ui),
path('ui/m/', manage),
path('ui/job/', not_implemented),
path('ui/settings/', not_implemented),
re_path(r'^ui/*', ui),

# fallback
Expand Down
Loading

0 comments on commit ee33344

Please sign in to comment.