1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2024-11-01 03:54:15 +00:00

Add Settings page: Move theme & lang changer to settings page

This commit is contained in:
catborise 2020-05-24 19:20:43 +03:00 committed by catborise
parent 7409c197ed
commit 2d5c701789
16 changed files with 279 additions and 60 deletions

View file

@ -112,44 +112,7 @@
</div>
</div>
</form>
{% if request.user.is_superuser %}
<h3 class="page-header">{% trans "Themes" %}</h3>
<form method="post" action="" role="form">{% csrf_token %}
<div class="form-group">
<label class="col-sm-2 col-form-label">{% trans "Themes" %}</label>
<div class="col-sm-4">
<select class="custom-select" name="theme_select" id="theme_select">
{% for theme in themes_list %}
<option value="{{ theme }}">{{ theme }}</option>
{% endfor %}
</select>
<label>{% trans "Current Theme" %}:</label>
<label id="active_theme"></label>
</div>
<p class="text-muted">{% trans "After change please full refresh page with Ctrl + F5 "%}</p>
</div>
<div class="form-group">
<div class="col-sm-10">
<button type="submit" class="btn btn-primary" name="change_theme" onclick="reloadStylesheets()">{% trans "Change" %}</button>
</div>
</div>
</form>
{% endif %}
</div>
</div>
{% endblock %}
{% block script %}
<script>
$(document).ready(function (){
$('#active_theme').text(localStorage.getItem('active_theme'));
});
function reloadStylesheets() {
var selected_theme = $('#theme_select option').filter(':selected').val();
var queryString = '?reload=' + selected_theme;
$('#wvc_css').each(function () {
this.href = this.href.replace(/\?.*|$/, queryString);
});
localStorage.setItem('active_theme', selected_theme);
}
</script>
{% endblock %}

0
appsettings/__init__.py Normal file
View file

3
appsettings/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

5
appsettings/apps.py Normal file
View file

@ -0,0 +1,5 @@
from django.apps import AppConfig
class AppsettingsConfig(AppConfig):
name = 'appsettings'

View file

@ -0,0 +1,24 @@
# Generated by Django 2.2.12 on 2020-05-23 15:53
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='AppSettings',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=25)),
('key', models.CharField(max_length=50, unique=True)),
('value', models.CharField(max_length=25)),
('description', models.CharField(max_length=100, null=True)),
],
),
]

View file

@ -0,0 +1,80 @@
# Generated by Django 2.2.12 on 2020-05-23 12:05
from django.db import migrations
def add_default_settings(apps, schema_editor):
setting = apps.get_model("appsettings", "AppSettings")
db_alias = schema_editor.connection.alias
setting.objects.using(db_alias).bulk_create([
setting(1, "Console Type", "QEMU_CONSOLE_DEFAULT_TYPE", "vnc", "Default console type"),
setting(2, "Libvirt K.alive Intrval", "LIBVIRT_KEEPALIVE_INTERVAL", "5", "libvirt keepalive interval"),
setting(3, "Libvirt K.alive Count", "LIBVIRT_KEEPALIVE_COUNT", "5", "libvirt keepalive count"),
setting(4, "Multiple Owner for VM ", "ALLOW_INSTANCE_MULTIPLE_OWNER", "True"),
setting(5, "VM Clone Name Prefix", "CLONE_INSTANCE_DEFAULT_PREFIX", "instance"),
setting(6, "VM Clone Auto Name", "CLONE_INSTANCE_AUTO_NAME", "False"),
setting(7, "VM Clone Auto Migrate", "CLONE_INSTANCE_AUTO_MIGRATE", "False"),
setting(8, "Logs per Page", "LOGS_PER_PAGE", "100"),
setting(9, "Quota Debug", "QUOTA_DEBUG", "True"),
setting(10, "Empty Password", "ALLOW_EMPTY_PASSWORD", "True"),
setting(11, "Account View Style", "VIEW_ACCOUNTS_STYLE", "grid", "available: default (grid), list"),
setting(12, "Instance List View", "VIEW_INSTANCES_LIST_STYLE", "grouped", "available instance list style: default (grouped), nongrouped"),
setting(13, "Show Bottom Bar", "VIEW_INSTANCE_DETAIL_BOTTOM_BAR", "True", "available options: True, False"),
setting(14, "Disk Format", "INSTANCE_VOLUME_DEFAULT_FORMAT", "qcow2", "available volume format: raw, qcow2, qcow"),
setting(15, "Disk Bus", "INSTANCE_VOLUME_DEFAULT_BUS", "virtio", "available bus types: virtio, scsi, ide, usb, sata"),
setting(16, "Disk SCSI Controller", "INSTANCE_VOLUME_DEFAULT_SCSI_CONTROLLER", "virtio-scsi", "SCSI Types: virtio-scsi, lsilogic"),
setting(17, "Disk Cache", "INSTANCE_VOLUME_DEFAULT_CACHE", "directsync", " Volume cache: default, directsync, none, unsafe, writeback, writethrough"),
setting(18, "Disk IO Type", "INSTANCE_VOLUME_DEFAULT_IO", "default", "Volume io mode: default, native, threads"),
setting(19, "Disk Detect Zeroes", "INSTANCE_VOLUME_DEFAULT_DETECT_ZEROES", "default", "Volume detect zeroes mode: default, on, off, unmap"),
setting(20, "Disk Discard", "INSTANCE_VOLUME_DEFAULT_DISCARD", "default", "Volume discard mode: default, unmap, ignore"),
setting(21, "Disk Owner UID", "INSTANCE_VOLUME_DEFAULT_OWNER_UID", "0", "up to os, 0=root, 107=qemu or libvirt-bin(for ubuntu)"),
setting(22, "Disk Owner GID", "INSTANCE_VOLUME_DEFAULT_OWNER_GID", "0", "up to os, 0=root, 107=qemu or libvirt-bin(for ubuntu)"),
setting(23, "CPU Mode", "INSTANCE_CPU_DEFAULT_MODE", "host-model", "Cpu modes: no-model, host-model, host-passthrough, custom"),
setting(24, "Machine Type", "INSTANCE_MACHINE_DEFAULT_TYPE", "q35", "Chipset/Machine: pc or q35 for x86_64"),
setting(25, "Firmware Type", "INSTANCE_FIRMWARE_DEFAULT_TYPE", "BIOS", "Firmware: BIOS or UEFI for x86_64"),
setting(26, "Architecture Type", "INSTANCE_ARCH_DEFAULT_TYPE", "x86_64", "Architecture: x86_64, i686, etc"),
setting(27, "Theme", "BOOTSTRAP_THEME", "", "Bootstrap CSS & Bootswatch"),
setting(28, "Sass Path", "SASS_DIR", "", "Bootstrap SASS & Bootswatch SASS "),
])
def del_default_settings(apps, schema_editor):
setting = apps.get_model("appsettings", "AppSettings")
db_alias = schema_editor.connection.alias
setting.objects.using(db_alias).filter(id=1, key="QEMU_CONSOLE_DEFAULT_TYPE").delete()
setting.objects.using(db_alias).filter(id=2, key="LIBVIRT_KEEPALIVE_INTERVAL").delete()
setting.objects.using(db_alias).filter(id=3, key="LIBVIRT_KEEPALIVE_COUNT").delete()
setting.objects.using(db_alias).filter(id=4, key="ALLOW_INSTANCE_MULTIPLE_OWNER").delete()
setting.objects.using(db_alias).filter(id=5, key="CLONE_INSTANCE_DEFAULT_PREFIX").delete()
setting.objects.using(db_alias).filter(id=6, key="CLONE_INSTANCE_AUTO_NAME").delete()
setting.objects.using(db_alias).filter(id=7, key="CLONE_INSTANCE_AUTO_MIGRATE").delete()
setting.objects.using(db_alias).filter(id=8, key="LOGS_PER_PAGE").delete()
setting.objects.using(db_alias).filter(id=9, key="QUOTA_DEBUG").delete()
setting.objects.using(db_alias).filter(id=10, key="ALLOW_EMPTY_PASSWORD").delete()
setting.objects.using(db_alias).filter(id=11, key="VIEW_ACCOUNTS_STYLE").delete()
setting.objects.using(db_alias).filter(id=12, key="VIEW_INSTANCES_LIST_STYLE").delete()
setting.objects.using(db_alias).filter(id=13, key="VIEW_INSTANCE_DETAIL_BOTTOM_BAR").delete()
setting.objects.using(db_alias).filter(id=14, key="INSTANCE_VOLUME_DEFAULT_FORMAT").delete()
setting.objects.using(db_alias).filter(id=15, key="INSTANCE_VOLUME_DEFAULT_BUS").delete()
setting.objects.using(db_alias).filter(id=16, key="INSTANCE_VOLUME_DEFAULT_SCSI_CONTROLLER").delete()
setting.objects.using(db_alias).filter(id=17, key="INSTANCE_VOLUME_DEFAULT_CACHE").delete()
setting.objects.using(db_alias).filter(id=18, key="INSTANCE_VOLUME_DEFAULT_IO").delete()
setting.objects.using(db_alias).filter(id=19, key="INSTANCE_VOLUME_DEFAULT_DETECT_ZEROES").delete()
setting.objects.using(db_alias).filter(id=20, key="INSTANCE_VOLUME_DEFAULT_DISCARD").delete()
setting.objects.using(db_alias).filter(id=21, key="INSTANCE_VOLUME_DEFAULT_OWNER_UID").delete()
setting.objects.using(db_alias).filter(id=22, key="INSTANCE_VOLUME_DEFAULT_OWNER_GID").delete()
setting.objects.using(db_alias).filter(id=23, key="INSTANCE_CPU_DEFAULT_MODE").delete()
setting.objects.using(db_alias).filter(id=24, key="INSTANCE_MACHINE_DEFAULT_TYPE").delete()
setting.objects.using(db_alias).filter(id=25, key="INSTANCE_FIRMWARE_DEFAULT_TYPE").delete()
setting.objects.using(db_alias).filter(id=26, key="INSTANCE_ARCH_DEFAULT_TYPE").delete()
setting.objects.using(db_alias).filter(id=27, key="BOOTSTRAP_THEME").delete()
setting.objects.using(db_alias).filter(id=28, key="SASS_DIR").delete()
class Migration(migrations.Migration):
dependencies = [
('appsettings', '0001_initial'),
]
operations = [
migrations.RunPython(add_default_settings, del_default_settings),
]

View file

8
appsettings/models.py Normal file
View file

@ -0,0 +1,8 @@
from django.db import models
class AppSettings(models.Model):
name = models.CharField(max_length=25, null=False)
key = models.CharField(max_length=50, unique=True)
value = models.CharField(max_length=25)
description=models.CharField(max_length=100, null=True)

View file

@ -0,0 +1,72 @@
{% extends "base.html" %}
{% load i18n %}
{% block title %}{% trans "Edit Settings" %}{% endblock %}
{% block content %}
<!-- Page Heading -->
<div class="row">
<div class="col-lg-12">
<h2 class="page-header">{% trans "Edit Settings" %}</h2>
</div>
</div>
<!-- /.row -->
{% include 'errors_block.html' %}
<div class="">
<div class="col-lg-12">
<h3 class="page-header">{% trans "App Settings" %}</h3>
<form action="{% url 'set_language' %}" method="post" style="display:inline">{% csrf_token %}
<div class="form-group row">
<input name="next" type="hidden" value="{{ redirect_to }}">
<label class="col-sm-3 col-form-label">{% trans "Language" %}</label>
<div class="col-sm-6">
<select name="language" class="form-control" onchange="this.form.submit()">
{% get_current_language as LANGUAGE_CODE %}
{% get_available_languages as LANGUAGES %}
{% get_language_info_list for LANGUAGES as languages %}
{% for language in languages %}
<option value="{{ language.code }}"{% if language.code == LANGUAGE_CODE %} selected{% endif %}>
{{ language.name_local }} ({{ language.code }})
</option>
{% endfor %}
</select>
</div>
</div>
</form>
{% if request.user.is_superuser %}
<form method="post" action="" role="form">{% csrf_token %}
<div class="form-group row">
<label class="col-sm-3 col-form-label">{{ sass_dir.name }}</label>
<div class="col-sm-6">
<input class="form-control" name="{{ sass_dir.key }}" value="{{ sass_dir.value }}" onchange="this.form.submit()"/>
</div>
</div>
</form>
<form method="post" action="" role="form">{% csrf_token %}
<div class="form-group row">
<label class="col-sm-3 col-form-label">{% trans bootstrap_theme.name %}</label>
<div class="col-sm-6">
<select class="form-control" name="{{ bootstrap_theme.key }}" onchange="this.form.submit()">
{% for theme in themes_list %}
<option {% if bootstrap_theme.value == theme %}selected{% endif %} value="{{ theme }}">{{ theme }}</option>
{% endfor %}
</select>
<span class="text-muted">{% trans "After change please full refresh page with Ctrl + F5 "%}</span>
</div>
</div>
</form>
{% endif %}
<form method="post" action="" role="form">{% csrf_token %}
<div class="form-group row">
<label class="col-sm-3 col-form-label">{{ show_inst_bottom_bar.name }}</label>
<div class="col-sm-6">
<select class="form-control" name="{{ show_inst_bottom_bar.key }}" onchange="this.form.submit()">
<option {% if show_inst_bottom_bar.value == 'True' %} selected {% endif %} value=True>{% trans "True" %}</option>
<option {% if show_inst_bottom_bar.value == 'False' %} selected {% endif %} value=False>{% trans "False" %}</option>
</select>
</div>
</div>
</form>
</div>
</div>
{% endblock %}

3
appsettings/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

57
appsettings/views.py Normal file
View file

@ -0,0 +1,57 @@
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.decorators import login_required
from django.conf import settings
from appsettings.models import AppSettings
import sass
import os
@login_required
def appsettings(request):
"""
:param request:
:return:
"""
error_messages = []
show_inst_bottom_bar = AppSettings.objects.get(key="VIEW_INSTANCE_DETAIL_BOTTOM_BAR")
bootstrap_theme = AppSettings.objects.get(key="BOOTSTRAP_THEME")
sass_dir = AppSettings.objects.get(key="SASS_DIR")
themes_list = os.listdir(sass_dir.value + "/wvc-theme")
if request.method == 'POST':
if 'BOOTSTRAP_THEME' in request.POST:
theme = request.POST.get("BOOTSTRAP_THEME", "")
scss_var = f"@import '{sass_dir.value}/wvc-theme/{theme}/variables';"
scss_bootswatch = f"@import '{sass_dir.value}/wvc-theme/{theme}/bootswatch';"
scss_boot = f"@import '{sass_dir.value}/bootstrap-overrides.scss';"
with open(sass_dir.value + "/wvc-main.scss", "w") as main:
main.write(scss_var + "\n" + scss_boot + "\n" + scss_bootswatch)
css_compressed = sass.compile(string=scss_var + "\n"+ scss_boot + "\n" + scss_bootswatch, output_style='compressed')
with open("static/" + "css/wvc-main.min.css", "w") as css:
css.write(css_compressed)
bootstrap_theme.value = theme
bootstrap_theme.save()
return HttpResponseRedirect(request.get_full_path())
if 'SASS_DIR' in request.POST:
sass_dir.value = request.POST.get("SASS_DIR", "")
sass_dir.save()
return HttpResponseRedirect(request.get_full_path())
if 'VIEW_INSTANCE_DETAIL_BOTTOM_BAR' in request.POST:
show_inst_bottom_bar.value = request.POST.get("VIEW_INSTANCE_DETAIL_BOTTOM_BAR", "")
show_inst_bottom_bar.save()
return HttpResponseRedirect(request.get_full_path())
return render(request, 'appsettings.html', locals())

View file

@ -15,6 +15,7 @@ from computes.models import Compute
from instances.models import Instance
from django.contrib.auth.models import User
from accounts.models import UserInstance, UserSSHKey
from appsettings.models import AppSettings
from vrtManager.hostdetails import wvmHostDetails
from vrtManager.instance import wvmInstance, wvmInstances
from vrtManager.connection import connection_manager
@ -113,7 +114,7 @@ def instance(request, compute_id, vname):
keymaps = settings.QEMU_KEYMAPS
console_types = settings.QEMU_CONSOLE_TYPES
console_listen_addresses = settings.QEMU_CONSOLE_LISTEN_ADDRESSES
bottom_bar = settings.VIEW_INSTANCE_DETAIL_BOTTOM_BAR
bottom_bar = AppSettings.objects.get(key="VIEW_INSTANCE_DETAIL_BOTTOM_BAR").value
try:
userinstance = UserInstance.objects.get(instance__compute_id=compute_id,
instance__name=vname,

View file

@ -82,7 +82,6 @@
</dd>
</dl>
{% if state %}
<div class="row">
<h5 class="page-header mr-auto">{% trans "Volumes" %}</h5>

View file

@ -14,22 +14,25 @@
<a class="nav-link" href="{% url 'allinstances' %}"><i class="fa fa-fw fa-desktop"></i> {% trans "Instances" %}</a>
</li>
{% if request.user.is_superuser %}
<li class="nav-item {% class_active request '^/computes' %}">
<a class="nav-link" href="{% url 'computes' %}"><i class="fa fa-fw fa-server"></i> {% trans "Computes" %}</a>
</li>
<li class="nav-item dropdown {% app_active request 'admin' %}">
<a class="nav-link dropdown-toggle" id="administration" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{% trans "Administration" %}
</a>
<ul class="dropdown-menu" aria-labelledby="administration">
<a class="dropdown-item {% view_active request 'admin:user_list' %}" href="{% url 'admin:user_list' %}">{% icon 'user-plus' %} {% trans "Users" %}</a>
<a class="dropdown-item {% view_active request 'admin:group_list' %}" href="{% url 'admin:group_list' %}">{% icon 'users' %} {% trans "Groups" %}</a>
<a class="dropdown-item {% view_active request 'admin:logs' %}" href="{% url 'admin:logs' %}">{% icon 'list-alt' %} {% trans "Logs" %}</a>
</ul>
</li>
<li class="nav-item {% class_active request '^/computes' %}">
<a class="nav-link" href="{% url 'computes' %}"><i class="fa fa-fw fa-server"></i> {% trans "Computes" %}</a>
</li>
{% endif %}
</ul>
<ul class="nav navbar-nav navbar-right">
<ul class="navbar-nav navbar-right mt-2 mt-md-0">
{% if request.user.is_superuser %}
<li class="nav-item dropdown {% app_active request 'admin' %}">
<a class="nav-link" id="administration" href="#" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{% icon 'wrench' %}
</a>
<div class="dropdown-menu" aria-labelledby="administration">
<a class="dropdown-item {% view_active request 'admin:user_list' %}" href="{% url 'admin:user_list' %}">{% icon 'user-plus' %} {% trans "Users" %}</a>
<a class="dropdown-item {% view_active request 'admin:group_list' %}" href="{% url 'admin:group_list' %}">{% icon 'users' %} {% trans "Groups" %}</a>
<a class="dropdown-item {% view_active request 'admin:logs' %}" href="{% url 'admin:logs' %}">{% icon 'list-alt' %} {% trans "Logs" %}</a>
<a class="dropdown-item {% view_active request 'appsettings' %}" href="{% url 'appsettings' %}">{% icon 'cogs' %} {% trans "Settings" %}</i></a>
</div>
</li>
{% endif %}
<li class="nav-item dropdown {% class_active request '^/accounts' %}">
<a class="nav-link dropdown-toggle" href="#" id="navbarUserDropdown" data-toggle="dropdown" role="button" aria-expanded="false">
<i class="fa fa-fw fa-user"></i> {{ request.user.username }}

View file

@ -35,6 +35,7 @@ INSTALLED_APPS = [
'nwfilters',
'storages',
'secrets',
'appsettings',
'logs',
]
@ -186,9 +187,6 @@ SHOW_ACCESS_SSH_KEYS = False
# available list style: default (grouped), nongrouped
VIEW_INSTANCES_LIST_STYLE = 'grouped'
# available options: True, False
VIEW_INSTANCE_DETAIL_BOTTOM_BAR = True
# available volume format: raw, qcow2, qcow
INSTANCE_VOLUME_DEFAULT_FORMAT = 'qcow2'

View file

@ -2,14 +2,17 @@ from django.urls import include, path
from instances.views import index
from console.views import console
from appsettings.views import appsettings
urlpatterns = [
path('', index, name='index'),
path('admin/', include(('admin.urls', 'admin'), namespace='admin')),
path('instances/', include('instances.urls')),
path('accounts/', include('accounts.urls')),
path('appsettings/', appsettings, name='appsettings'),
path('computes/', include('computes.urls')),
path('logs/', include('logs.urls')),
path('datasource/', include('datasource.urls')),
path('console/', console, name='console'),
path('datasource/', include('datasource.urls')),
path('instances/', include('instances.urls')),
path('i18n/', include('django.conf.urls.i18n')),
path('logs/', include('logs.urls')),
]