Compare commits
10 Commits
ee7cf07294
...
142c9f97ae
Author | SHA1 | Date |
---|---|---|
Jorge Pastor | 142c9f97ae | |
Jorge Pastor | b911e0e89f | |
Jorge Pastor | b896dd2262 | |
Jorge Pastor | 6ff6a75eda | |
Jorge Pastor | 4688605bc6 | |
Jorge Pastor | fa1a130370 | |
Jorge Pastor | 3c3e15d1c5 | |
Jorge Pastor | bb4fc5e267 | |
Jorge Pastor | 60771b7808 | |
Jorge Pastor | 5c0c82d50b |
|
@ -2,12 +2,21 @@ from django import forms
|
|||
from django.contrib.auth.forms import AuthenticationForm
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.utils.encoding import force_str
|
||||
from orchestra.forms.widgets import DynamicHelpTextSelect
|
||||
|
||||
from django.contrib.auth.hashers import make_password
|
||||
|
||||
from orchestra.contrib.domains.models import Domain, Record
|
||||
from orchestra.contrib.mailboxes.models import Address, Mailbox
|
||||
from orchestra.contrib.systemusers.models import WebappUsers, SystemUser
|
||||
from orchestra.contrib.musician.validators import ValidateZoneMixin
|
||||
from orchestra.contrib.webapps.models import WebApp, WebAppOption
|
||||
from orchestra.contrib.webapps.options import AppOption
|
||||
from orchestra.contrib.webapps.types import AppType
|
||||
|
||||
from . import api
|
||||
from .settings import MUSICIAN_EDIT_ENABLE_PHP_OPTIONS
|
||||
|
||||
|
||||
class LoginForm(AuthenticationForm):
|
||||
|
@ -27,6 +36,42 @@ class LoginForm(AuthenticationForm):
|
|||
|
||||
return self.cleaned_data
|
||||
|
||||
class ChangePasswordForm(forms.ModelForm):
|
||||
error_messages = {
|
||||
'password_mismatch': _('The two password fields didn’t match.'),
|
||||
}
|
||||
password = forms.CharField(
|
||||
label=_("Password"),
|
||||
strip=False,
|
||||
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
|
||||
)
|
||||
password2 = forms.CharField(
|
||||
label=_("Password confirmation"),
|
||||
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
|
||||
strip=False,
|
||||
help_text=_("Enter the same password as before, for verification."),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
fields = ("password",)
|
||||
model = WebappUsers
|
||||
|
||||
def clean_password2(self):
|
||||
password = self.cleaned_data.get("password")
|
||||
password2 = self.cleaned_data.get("password2")
|
||||
if password and password2 and password != password2:
|
||||
raise ValidationError(
|
||||
self.error_messages['password_mismatch'],
|
||||
code='password_mismatch',
|
||||
)
|
||||
return password2
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
password = cleaned_data.get("password")
|
||||
cleaned_data['password'] = make_password(password)
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class MailForm(forms.ModelForm):
|
||||
class Meta:
|
||||
|
@ -53,36 +98,12 @@ class MailForm(forms.ModelForm):
|
|||
return instance
|
||||
|
||||
|
||||
class MailboxChangePasswordForm(forms.ModelForm):
|
||||
error_messages = {
|
||||
'password_mismatch': _('The two password fields didn’t match.'),
|
||||
}
|
||||
password = forms.CharField(
|
||||
label=_("Password"),
|
||||
strip=False,
|
||||
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
|
||||
)
|
||||
password2 = forms.CharField(
|
||||
label=_("Password confirmation"),
|
||||
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
|
||||
strip=False,
|
||||
help_text=_("Enter the same password as before, for verification."),
|
||||
)
|
||||
class MailboxChangePasswordForm(ChangePasswordForm):
|
||||
|
||||
class Meta:
|
||||
fields = ("password",)
|
||||
model = Mailbox
|
||||
|
||||
def clean_password2(self):
|
||||
password = self.cleaned_data.get("password")
|
||||
password2 = self.cleaned_data.get("password2")
|
||||
if password and password2 and password != password2:
|
||||
raise ValidationError(
|
||||
self.error_messages['password_mismatch'],
|
||||
code='password_mismatch',
|
||||
)
|
||||
return password2
|
||||
|
||||
|
||||
class MailboxCreateForm(forms.ModelForm):
|
||||
error_messages = {
|
||||
|
@ -120,7 +141,13 @@ class MailboxCreateForm(forms.ModelForm):
|
|||
self.error_messages['password_mismatch'],
|
||||
code='password_mismatch',
|
||||
)
|
||||
return password2
|
||||
return password
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
password = cleaned_data.get("password")
|
||||
cleaned_data['password'] = make_password(password)
|
||||
return cleaned_data
|
||||
|
||||
def save(self, commit=True):
|
||||
instance = super().save(commit=False)
|
||||
|
@ -169,3 +196,75 @@ class RecordUpdateForm(ValidateZoneMixin, forms.ModelForm):
|
|||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.domain = self.instance.domain
|
||||
|
||||
|
||||
class WebappUsersChangePasswordForm(ChangePasswordForm):
|
||||
class Meta:
|
||||
fields = ("password",)
|
||||
model = WebappUsers
|
||||
|
||||
class SystemUsersChangePasswordForm(ChangePasswordForm):
|
||||
class Meta:
|
||||
fields = ("password",)
|
||||
model = SystemUser
|
||||
|
||||
|
||||
class WebappOptionForm(forms.ModelForm):
|
||||
|
||||
OPTIONS_HELP_TEXT = {
|
||||
op.name: force_str(op.help_text) for op in AppOption.get_plugins()
|
||||
}
|
||||
|
||||
class Meta:
|
||||
model = WebAppOption
|
||||
fields = ("name", "value")
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
try:
|
||||
self.webapp = kwargs.pop('webapp')
|
||||
super().__init__(*args, **kwargs)
|
||||
except:
|
||||
super().__init__(*args, **kwargs)
|
||||
self.webapp = self.instance.webapp
|
||||
|
||||
target = 'this.id.replace("name", "value")'
|
||||
self.fields['name'].widget.attrs = DynamicHelpTextSelect(target, self.OPTIONS_HELP_TEXT).attrs
|
||||
|
||||
def save(self, commit=True):
|
||||
instance = super().save(commit=False)
|
||||
instance.webapp = self.webapp
|
||||
if commit:
|
||||
super().save(commit=True)
|
||||
self.webapp.save()
|
||||
return instance
|
||||
|
||||
|
||||
class WebappOptionCreateForm(WebappOptionForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
plugin = AppType.get(self.webapp.type)
|
||||
choices = list(plugin.get_group_options_choices())
|
||||
for grupo, opciones in enumerate(choices):
|
||||
if isinstance(opciones[1], list):
|
||||
nueva_lista = [opc for opc in opciones[1] if opc[0] in MUSICIAN_EDIT_ENABLE_PHP_OPTIONS]
|
||||
choices[grupo] = (opciones[0], nueva_lista)
|
||||
self.fields['name'].widget.choices = choices
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
name = self.cleaned_data.get("name")
|
||||
if WebAppOption.objects.filter(webapp=self.webapp, name=name).exists():
|
||||
raise ValidationError(_("This option already exist."))
|
||||
return cleaned_data
|
||||
|
||||
class WebappOptionUpdateForm(WebappOptionForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['name'].widget.choices = [(self.initial['name'], self.initial['name'])]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -14,11 +14,13 @@ class CustomContextMixin(ContextMixin):
|
|||
context = super().get_context_data(**kwargs)
|
||||
# generate services menu items
|
||||
services_menu = [
|
||||
{'icon': 'globe-europe', 'pattern_name': 'musician:dashboard', 'title': _('Domains & websites')},
|
||||
{'icon': 'globe-europe', 'pattern_name': 'musician:dashboard', 'title': _('Domains')},
|
||||
{'icon': 'envelope', 'pattern_name': 'musician:address-list', 'title': _('Mails')},
|
||||
{'icon': 'mail-bulk', 'pattern_name': 'musician:mailing-lists', 'title': _('Mailing lists')},
|
||||
{'icon': 'database', 'pattern_name': 'musician:database-list', 'title': _('Databases')},
|
||||
{'icon': 'fire', 'pattern_name': 'musician:saas-list', 'title': _('SaaS')},
|
||||
{'icon': 'globe', 'pattern_name': 'musician:website-list', 'title': _('Websites')},
|
||||
{'icon': 'folder', 'pattern_name': 'musician:webapp-list', 'title': _('Webapps'), 'indent': True},
|
||||
]
|
||||
context.update({
|
||||
'services_menu': services_menu,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from orchestra.contrib.settings import Setting
|
||||
from collections import defaultdict
|
||||
from django.conf import settings
|
||||
|
||||
|
@ -46,3 +47,14 @@ URL_SAAS_GITLAB = getsetting("URL_SAAS_GITLAB")
|
|||
URL_SAAS_OWNCLOUD = getsetting("URL_SAAS_OWNCLOUD")
|
||||
|
||||
URL_SAAS_WORDPRESS = getsetting("URL_SAAS_WORDPRESS")
|
||||
|
||||
|
||||
MUSICIAN_EDIT_ENABLE_PHP_OPTIONS = Setting('MUSICIAN_EDIT_ENABLE_PHP_OPTIONS', (
|
||||
'public-root',
|
||||
'timeout',
|
||||
'max_input_time',
|
||||
'max_input_vars',
|
||||
'memory_limit',
|
||||
'post_max_size',
|
||||
'upload_max_filesize',
|
||||
))
|
||||
|
|
|
@ -46,7 +46,11 @@
|
|||
{# <!-- services menu --> #}
|
||||
<ul id="sidebar-services" class="nav flex-column">
|
||||
{% for item in services_menu %}
|
||||
{% if item.indent %}
|
||||
<li class="nav-item ml-3">
|
||||
{% else %}
|
||||
<li class="nav-item">
|
||||
{% endif %}
|
||||
<a class="nav-link text-light active" href="{% url item.pattern_name %}">
|
||||
<i class="fas fa-{{ item.icon }}"></i>
|
||||
{{ item.title }}
|
||||
|
|
|
@ -50,11 +50,12 @@
|
|||
<div class="col-md-8">
|
||||
{% with domain.websites.0 as website %}
|
||||
{% with website.contents.0 as content %}
|
||||
<button type="button" class="btn text-secondary" data-toggle="modal" data-target="#configDetailsModal"
|
||||
<a href="{% url 'musician:domain-detail' domain.id %}" class="btn btn-primary">{% trans "View DNS records" %}</a>
|
||||
<!-- <button type="button" class="btn text-secondary" data-toggle="modal" data-target="#configDetailsModal"
|
||||
data-domain="{{ domain.name }}" data-website="{{ website|yesno:'true,false' }}" data-webapp-type="{{ content.webapp.type }}" data-root-path="{{ content.path }}"
|
||||
data-url="{% url 'musician:domain-detail' domain.id %}">
|
||||
{% trans "view configuration" %} <strong class="fas fa-tools"></strong>
|
||||
</button>
|
||||
</button> -->
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load bootstrap4 i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="service-name">{% trans "Change password" %}: <span class="font-weight-light">{{ object.name }}</span></h1>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
{% buttons %}
|
||||
<a class="btn btn-light mr-2" href="{% url 'musician:systemuser-list' %}">{% trans "Cancel" %}</a>
|
||||
<button type="submit" class="btn btn-secondary">{% trans "Save" %}</button>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,41 @@
|
|||
{% extends "musician/users_base.html" %}
|
||||
{% load bootstrap4 i18n %}
|
||||
|
||||
{% block tabcontent %}
|
||||
<p></p>
|
||||
<p>{% trans "The main user is your system's main user on each server. You'll be able to view the logs of your websites at (/home/account/logs) and all web content, but you'll never be able to edit content on a website." %}</p>
|
||||
<p>{% trans "This user only has write permissions in their own directory." %}</p>
|
||||
|
||||
|
||||
<table class="table service-list">
|
||||
<colgroup>
|
||||
<col span="1" style="width: 15%;">
|
||||
<col span="1" style="width: 25%;">
|
||||
<col span="1" style="width: 60%;">
|
||||
</colgroup>
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">{% trans "Username" %}</th>
|
||||
<th scope="col">{% trans "Path" %}</th>
|
||||
<th scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for systemuser in object_list %}
|
||||
{% if systemuser.is_main %}
|
||||
<tr>
|
||||
<td>{{ systemuser.username }}</td>
|
||||
<td>{{ systemuser.home }}/{{ systemuser.username }}</td>
|
||||
<td>
|
||||
<div class="d-flex justify-content-end">
|
||||
<a class="btn btn-outline-warning" href="{% url 'musician:systemuser-password' systemuser.id %}">
|
||||
<i class="fas fa-key"></i> {% trans "Update password" %}</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,31 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
{% if active_domain %}
|
||||
<a class="btn-arrow-left" href="{% url 'musician:systemuser-list' %}">{% trans "Go to global" %}</a>
|
||||
{% endif %}
|
||||
|
||||
<h1 class="service-name">{{ service.verbose_name }}
|
||||
{% if active_domain %}<span class="font-weight-light">{% trans "for" %} {{ active_domain.name }}</span>{% endif %}
|
||||
</h1>
|
||||
<p class="service-description">{{ service.description }}</p>
|
||||
|
||||
{% with request.resolver_match.url_name as url_name %}
|
||||
<ul class="nav nav-tabs" id="myTab" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if url_name == 'systemuser-list' %}active{% endif %}" href="{% url 'musician:systemuser-list' %}" role="tab"
|
||||
aria-selected="{% if url_name == 'systemuser-list' %}true{% else %}false{% endif %}">{% trans "Main User" %}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if url_name == 'webappuser-list' %}active{% endif %}" href="{% url 'musician:webappuser-list' %}" role="tab"
|
||||
aria-selected="{% if url_name == 'webappuser-list' %}true{% else %}false{% endif %}">{% trans "SFTP Users" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
{% endwith %}
|
||||
|
||||
<div class="tab-content" id="myTabContent">
|
||||
{% block tabcontent %}
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,46 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<a class="btn-arrow-left" href="{% url 'musician:webapp-list' %}">{% trans "Go back" %}</a>
|
||||
|
||||
<h1 class="service-name">
|
||||
{% trans "PHP settings for" %} <span class="font-weight-light">{{ object.name }}</span>
|
||||
</h1>
|
||||
|
||||
|
||||
<p class="service-description">{% trans "PHP settings page description." %}</p>
|
||||
|
||||
<table class="table service-list">
|
||||
<colgroup>
|
||||
<col span="1" style="width: 12%;">
|
||||
<col span="1" style="width: 10%;">
|
||||
<col span="1" style="width: 78%;">
|
||||
</colgroup>
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">{% trans "Type" %}</th>
|
||||
<th scope="col">{% trans "Value" %}</th>
|
||||
<th scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for option in object.options.all %}
|
||||
<tr>
|
||||
<td>{{ option.name }}</td>
|
||||
<td>{{ option.value }}</td>
|
||||
<td class="text-right">
|
||||
{% if option.name in edit_allowed_PHP_options %}
|
||||
<a href="{% url 'musician:webapp-update-option' object.pk option.pk %}">
|
||||
<i class="ml-3 fas fa-edit"></i></a>
|
||||
{% endif %}
|
||||
<a href="{% url 'musician:webapp-delete-option' object.pk option.pk %}">
|
||||
<i class="ml-3 text-danger fas fa-trash"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<a class="btn btn-primary mt-4 mb-4" href="{% url 'musician:webapp-add-option' object.pk %}">{% trans "Add new option" %}</a></td>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,20 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load bootstrap4 i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="service-name">{{ service.verbose_name }}</h1>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
{% buttons %}
|
||||
<a class="btn btn-light mr-2" href="{% url 'musician:address-list' %}">{% trans "Cancel" %}</a>
|
||||
<button type="submit" class="btn btn-secondary">{% trans "Save" %}</button>
|
||||
{% if form.instance.pk %}
|
||||
<div class="float-right">
|
||||
<a class="btn btn-danger" href="{% url 'musician:address-delete' view.kwargs.pk %}">{% trans "Delete" %}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,55 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load bootstrap4 i18n %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<p>
|
||||
{{ description }}
|
||||
</p>
|
||||
<p>
|
||||
{{ description2 }}
|
||||
</p>
|
||||
|
||||
<table class="table service-list">
|
||||
<colgroup>
|
||||
<col span="1" style="width: 19%;">
|
||||
<col span="1" style="width: 19%;">
|
||||
<col span="1" style="width: 19%;">
|
||||
<col span="1" style="width: 19%;">
|
||||
<col span="1" style="width: 19%;">
|
||||
<col span="1" style="width: 5%;">
|
||||
</colgroup>
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">{% trans "Name" %}</th>
|
||||
<th scope="col">{% trans "Type" %}</th>
|
||||
<th scope="col">{% trans "Version" %}</th>
|
||||
<th scope="col">{% trans "SFTP User" %}</th>
|
||||
<th scope="col">{% trans "Server" %}</th>
|
||||
<th scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for webapp in object_list %}
|
||||
<tr>
|
||||
<td>{{ webapp.name }}</td>
|
||||
<td>{{ webapp.type }}</td>
|
||||
<td>{{ webapp.type_instance.get_detail }}</td>
|
||||
<td>
|
||||
{% if webapp.sftpuser %}
|
||||
<a href="{% url 'musician:webappuser-list'%}">{{ webapp.sftpuser }}</a>
|
||||
{% else %}
|
||||
<a href="{% url 'musician:systemuser-list'%}">{{ webapp.account.main_systemuser }}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ webapp.target_server }}</td>
|
||||
<td>
|
||||
<a class="btn btn-outline-warning" href="{% url 'musician:webapp-detail' webapp.id %}">
|
||||
<i class="fas fa-tools"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,22 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load bootstrap4 i18n %}
|
||||
|
||||
|
||||
{% block content %}
|
||||
|
||||
<a class="btn-arrow-left" href="{% url 'musician:webapp-detail' view.kwargs.pk %}">{% trans "Go back" %}</a>
|
||||
|
||||
<h1 class="service-name">
|
||||
{% if form.instance.pk %}{% trans "Update Option of" %}{% else %}{% trans "Add Option to" %}{% endif %}
|
||||
<span class="font-weight-light">{{ form.webapp.name }}</span>
|
||||
</h1>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
{% buttons %}
|
||||
<a class="btn btn-light mr-2" href="{% url 'musician:webapp-detail' view.kwargs.pk %}">{% trans "Cancel" %}</a>
|
||||
<button type="submit" class="btn btn-secondary">{% trans "Save" %}</button>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,13 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<p>{% blocktrans %}Are you sure that you want remove the following option"?{% endblocktrans %}</p>
|
||||
<pre>{{ object.name}} {{ object.value}}</pre>
|
||||
<p class="alert alert-warning"><strong>{% trans 'WARNING: This action cannot be undone.' %}</strong></p>
|
||||
<input class="btn btn-danger" type="submit" value="{% trans 'Delete' %}">
|
||||
<a class="btn btn-secondary" href="{% url 'musician:webapp-detail' view.kwargs.pk %}">{% trans 'Cancel' %}</a>
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,15 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load bootstrap4 i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="service-name">{% trans "Change password" %}: <span class="font-weight-light">{{ object.name }}</span></h1>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form %}
|
||||
{% buttons %}
|
||||
<a class="btn btn-light mr-2" href="{% url 'musician:webappuser-list' %}">{% trans "Cancel" %}</a>
|
||||
<button type="submit" class="btn btn-secondary">{% trans "Save" %}</button>
|
||||
{% endbuttons %}
|
||||
</form>
|
||||
{% endblock %}
|
|
@ -0,0 +1,37 @@
|
|||
{% extends "musician/users_base.html" %}
|
||||
{% load bootstrap4 i18n %}
|
||||
|
||||
{% block tabcontent %}
|
||||
|
||||
<table class="table service-list">
|
||||
<colgroup>
|
||||
<col span="1" style="width: 15%;">
|
||||
<col span="1" style="width: 25%;">
|
||||
<col span="1" style="width: 40%;">
|
||||
<col span="1" style="width: 20%;">
|
||||
</colgroup>
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">{% trans "Username" %}</th>
|
||||
<th scope="col">{% trans "Path" %}</th>
|
||||
<th scope="col">{% trans "Server" %}</th>
|
||||
<th scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for webappuser in object_list %}
|
||||
<tr>
|
||||
<td>{{ webappuser.username }}</td>
|
||||
<td>/home/{{ webappuser.account }}/webapps/{{ webappuser.home }}</td>
|
||||
<td>{{ webappuser.target_server }}</td>
|
||||
<td>
|
||||
<a class="btn btn-outline-warning" href="{% url 'musician:webappuser-password' webappuser.id %}">
|
||||
<i class="fas fa-key"></i> {% trans "Update password" %}</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% include "musician/components/table_paginator.html" %}
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
|
@ -0,0 +1,128 @@
|
|||
{% extends "musician/base.html" %}
|
||||
{% load bootstrap4 i18n %}
|
||||
|
||||
{% block content %}
|
||||
<p>
|
||||
{{ description }}
|
||||
</p>
|
||||
|
||||
<table class="table service-list table-hover">
|
||||
<colgroup>
|
||||
<col span="1" style="width: 15%;">
|
||||
<col span="1" style="width: 25%;">
|
||||
<col span="1" style="width: 40%;">
|
||||
<col span="1" style="width: 10%;">
|
||||
<col span="1" style="width: 10%;">
|
||||
</colgroup>
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">{% trans "Name" %}</th>
|
||||
<th scope="col">{% trans "Url" %}</th>
|
||||
<th scope="col">{% trans "Server" %}</th>
|
||||
<th scope="col">{% trans "Is active?" %}</th>
|
||||
<th scope="col"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for website in object_list %}
|
||||
<tr class="fila-principal" data-toggle="collapse" data-target=".detalles{{ website.id }}">
|
||||
<td>{{ website.name }}</td>
|
||||
<td>
|
||||
{% for domain in website.domains.all %}
|
||||
<a href="{{ website.get_protocol }}://{{ domain }}">{{ website.get_protocol }}://{{ domain }}</a><br>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td>{{ website.target_server }}</td>
|
||||
<td class="text-{{website.is_active|yesno:'success,danger'}}">
|
||||
<i class="fa fa-{{ website.is_active|yesno:'check,times' }}"></i>
|
||||
<span class="sr-only">{{ website.is_active|yesno }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<!-- <a class="btn btn-outline-warning" href="#">
|
||||
<i class="fas fa-tools"></i></a> -->
|
||||
<button type="button" class="btn btn-outline-warning" data-toggle="modal" data-target="#exampleModal">
|
||||
<i class="fas fa-tools"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Fila oculta de webapp -->
|
||||
<tr class="collapse detalles{{ website.id }}">
|
||||
<td colspan="12"class="p-5">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
{% for content in website.content_set.all %}
|
||||
<tr class="table-active">
|
||||
<td>Webapp Dir</td>
|
||||
<td>/home/{{ content.webapp.account }}/webapps/{{ content.webapp }}</td>
|
||||
<td class="text-right">
|
||||
<a class="btn btn-outline-secondary" href="{% url 'musician:webapp-list'%}">
|
||||
<i class="fas fa-tools"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Url</td>
|
||||
{% if website.domains.first %}
|
||||
<td><a href="{{ website.get_protocol }}://{{ website.domains.first }}{{ content.path }}">
|
||||
{{ website.get_protocol }}://{{ website.domains.first }}{{ content.path }}</a>
|
||||
</td>
|
||||
{% else %}
|
||||
<td></td>
|
||||
{% endif %}
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Type</td>
|
||||
{% if content.webapp.type == "php" %}
|
||||
<td>PHP {{ content.webapp.type_instance.get_detail }}</td>
|
||||
{% else %}
|
||||
<td>{{ content.webapp.type }}</td>
|
||||
{% endif %}
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
{% if content.webapp.sftpuser %}
|
||||
<td>SFTP user</td>
|
||||
<td>{{ content.webapp.sftpuser }}</td>
|
||||
<td class="text-right">
|
||||
<a class="btn btn-outline-warning" href="{% url 'musician:webappuser-password' content.webapp.sftpuser.id %}">
|
||||
<i class="fas fa-key"></i> {% trans "Update password" %}</a>
|
||||
</td>
|
||||
{% else %}
|
||||
<td>FTP user</td>
|
||||
<td>{{ content.webapp.account.main_systemuser }}</td>
|
||||
<td class="text-right">
|
||||
<a class="btn btn-outline-warning" href="{% url 'musician:systemuser-password' content.webapp.account.main_systemuser.id %}">
|
||||
<i class="fas fa-key"></i> {% trans "Update password" %}</a>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
||||
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="exampleModalLabel">Sorry!</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{% trans "This section is under development." %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
|
@ -39,4 +39,16 @@ urlpatterns = [
|
|||
path('mailing-lists/', views.MailingListsView.as_view(), name='mailing-lists'),
|
||||
path('databases/', views.DatabaseListView.as_view(), name='database-list'),
|
||||
path('saas/', views.SaasListView.as_view(), name='saas-list'),
|
||||
|
||||
path('webappusers/', views.WebappUserListView.as_view(), name='webappuser-list'),
|
||||
path('webappuser/<int:pk>/change-password/', views.WebappUserChangePasswordView.as_view(), name='webappuser-password'),
|
||||
path('systemusers/', views.SystemUserListView.as_view(), name='systemuser-list'),
|
||||
path('systemuser/<int:pk>/change-password/', views.SystemUserChangePasswordView.as_view(), name='systemuser-password'),
|
||||
path('websites/', views.WebsiteListView.as_view(), name='website-list'),
|
||||
|
||||
path('webapps/', views.WebappListView.as_view(), name='webapp-list'),
|
||||
path('webapps/<int:pk>/', views.WebappDetailView.as_view(), name='webapp-detail'),
|
||||
path('webapps/<int:pk>/add-option/', views.WebappAddOptionView.as_view(), name='webapp-add-option'),
|
||||
path('webapps/<int:pk>/option/<int:option_pk>/delete/', views.WebappDeleteOptionView.as_view(), name='webapp-delete-option'),
|
||||
path('webapps/<int:pk>/option/<int:option_pk>/update/', views.WebappUpdateOptionView.as_view(), name='webapp-update-option'),
|
||||
]
|
||||
|
|
|
@ -32,12 +32,16 @@ from orchestra.contrib.lists.models import List
|
|||
from orchestra.contrib.mailboxes.models import Address, Mailbox
|
||||
from orchestra.contrib.resources.models import Resource, ResourceData
|
||||
from orchestra.contrib.saas.models import SaaS
|
||||
from orchestra.contrib.systemusers.models import WebappUsers, SystemUser
|
||||
from orchestra.contrib.websites.models import Website
|
||||
from orchestra.contrib.webapps.models import WebApp, WebAppOption
|
||||
from orchestra.utils.html import html_to_pdf
|
||||
|
||||
from .auth import logout as auth_logout
|
||||
from .forms import (LoginForm, MailboxChangePasswordForm, MailboxCreateForm,
|
||||
MailboxSearchForm, MailboxUpdateForm, MailForm,
|
||||
RecordCreateForm, RecordUpdateForm)
|
||||
RecordCreateForm, RecordUpdateForm, WebappUsersChangePasswordForm,
|
||||
SystemUsersChangePasswordForm, WebappOptionCreateForm, WebappOptionUpdateForm)
|
||||
from .mixins import (CustomContextMixin, ExtendedPaginationMixin,
|
||||
UserTokenRequiredMixin)
|
||||
from .models import Address as AddressService
|
||||
|
@ -45,7 +49,7 @@ from .models import Bill as BillService
|
|||
from .models import DatabaseService
|
||||
from .models import Mailbox as MailboxService
|
||||
from .models import MailinglistService, SaasService
|
||||
from .settings import ALLOWED_RESOURCES
|
||||
from .settings import ALLOWED_RESOURCES, MUSICIAN_EDIT_ENABLE_PHP_OPTIONS
|
||||
from .utils import get_bootstraped_percent
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -612,3 +616,140 @@ class LogoutView(RedirectView):
|
|||
def post(self, request, *args, **kwargs):
|
||||
"""Logout may be done via POST."""
|
||||
return self.get(request, *args, **kwargs)
|
||||
|
||||
|
||||
class WebappUserListView(ServiceListView):
|
||||
model = WebappUsers
|
||||
template_name = "musician/webappuser_list.html"
|
||||
extra_context = {
|
||||
# Translators: This message appears on the page title
|
||||
'title': _('Webapp users'),
|
||||
}
|
||||
|
||||
class WebappUserChangePasswordView(CustomContextMixin, UserTokenRequiredMixin, UpdateView):
|
||||
template_name = "musician/webappuser_change_password.html"
|
||||
model = WebappUsers
|
||||
form_class = WebappUsersChangePasswordForm
|
||||
success_url = reverse_lazy("musician:webappuser-list")
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(account=self.request.user)
|
||||
|
||||
|
||||
class SystemUserListView(ServiceListView):
|
||||
model = SystemUser
|
||||
template_name = "musician/systemuser_list.html"
|
||||
extra_context = {
|
||||
# Translators: This message appears on the page title
|
||||
'title': _('Main users'),
|
||||
}
|
||||
|
||||
class SystemUserChangePasswordView(CustomContextMixin, UserTokenRequiredMixin, UpdateView):
|
||||
template_name = "musician/systemuser_change_password.html"
|
||||
model = SystemUser
|
||||
form_class = SystemUsersChangePasswordForm
|
||||
success_url = reverse_lazy("musician:systemuser-list")
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(account=self.request.user)
|
||||
|
||||
class WebsiteListView(CustomContextMixin, UserTokenRequiredMixin, ListView):
|
||||
model = Website
|
||||
template_name = "musician/website_list.html"
|
||||
extra_context = {
|
||||
# Translators: This message appears on the page title
|
||||
'title': _('Websites'),
|
||||
}
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(account=self.request.user)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({
|
||||
'description': _("A website is the place where a domain is associated with the directory where the web files are located. (WebApp)"),
|
||||
})
|
||||
return context
|
||||
|
||||
class WebappListView(CustomContextMixin, UserTokenRequiredMixin, ListView):
|
||||
model = WebApp
|
||||
template_name = "musician/webapp_list.html"
|
||||
extra_context = {
|
||||
# Translators: This message appears on the page title
|
||||
'title': _('Webapps'),
|
||||
}
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(account=self.request.user)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({
|
||||
'description': _("A web app is the directory where your website is stored. Through SFTP, you can access this directory and upload/edit/delete files."),
|
||||
'description2': _("Each Webapp has its own SFTP user, which is created automatically when the Webapp is created.")
|
||||
})
|
||||
return context
|
||||
|
||||
|
||||
class WebappDetailView(CustomContextMixin, UserTokenRequiredMixin, DetailView):
|
||||
template_name = "musician/webapp_detail.html"
|
||||
extra_context = {
|
||||
# Translators: This message appears on the page title
|
||||
'title': _('webapp details'),
|
||||
}
|
||||
|
||||
def get_queryset(self):
|
||||
return WebApp.objects.filter(account=self.request.user)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context.update({
|
||||
'edit_allowed_PHP_options': MUSICIAN_EDIT_ENABLE_PHP_OPTIONS
|
||||
})
|
||||
return context
|
||||
|
||||
class WebappAddOptionView(CustomContextMixin, UserTokenRequiredMixin, CreateView):
|
||||
model = WebAppOption
|
||||
form_class = WebappOptionCreateForm
|
||||
template_name = "musician/webapp_option_form.html"
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super().get_form_kwargs()
|
||||
webapp = get_object_or_404(WebApp, account=self.request.user, pk=self.kwargs["pk"])
|
||||
kwargs['webapp'] = webapp
|
||||
return kwargs
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy("musician:webapp-detail", kwargs={"pk": self.kwargs["pk"]})
|
||||
|
||||
class WebappDeleteOptionView(CustomContextMixin, UserTokenRequiredMixin, DeleteView):
|
||||
model = WebAppOption
|
||||
template_name = "musician/webappoption_check_delete.html"
|
||||
pk_url_kwarg = "option_pk"
|
||||
|
||||
def get_queryset(self):
|
||||
qs = WebAppOption.objects.filter(webapp__account=self.request.user, webapp=self.kwargs["pk"])
|
||||
return qs
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy("musician:webapp-detail", kwargs={"pk": self.kwargs["pk"]})
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
object = self.get_object()
|
||||
response = super().delete(request, *args, **kwargs)
|
||||
object.webapp.save()
|
||||
return response
|
||||
|
||||
|
||||
class WebappUpdateOptionView(CustomContextMixin, UserTokenRequiredMixin, UpdateView):
|
||||
model = WebAppOption
|
||||
form_class = WebappOptionUpdateForm
|
||||
template_name = "musician/webapp_option_form.html"
|
||||
pk_url_kwarg = "option_pk"
|
||||
|
||||
def get_queryset(self):
|
||||
qs = WebAppOption.objects.filter(webapp__account=self.request.user, webapp=self.kwargs["pk"])
|
||||
return qs
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy("musician:webapp-detail", kwargs={"pk": self.kwargs["pk"]})
|
||||
|
|
|
@ -42,8 +42,8 @@ class WebAppServiceMixin(object):
|
|||
# cambios de permisos en servidores nuevos
|
||||
perms = Template(textwrap.dedent("""\
|
||||
{% if sftpuser %}
|
||||
chown -R {{ sftpuser }}:{{ sftpuser }} {{ app_path }}/* {% else %}
|
||||
chown -R {{ user }}:{{ group }} {{ app_path }}/*
|
||||
chown -R {{ sftpuser }}:{{ sftpuser }} {{ app_path }} {% else %}
|
||||
chown -R {{ user }}:{{ group }} {{ app_path }}
|
||||
{% endif %}
|
||||
"""
|
||||
))
|
||||
|
|
|
@ -77,6 +77,7 @@ class PublicRoot(AppOption):
|
|||
|
||||
def validate(self):
|
||||
super().validate()
|
||||
if self.instance.webapp_id is not None:
|
||||
base_path = self.instance.webapp.get_base_path()
|
||||
path = os.path.join(base_path, self.instance.value)
|
||||
if not os.path.abspath(path).startswith(base_path):
|
||||
|
|
Loading…
Reference in New Issue