Compare commits
11 Commits
5b5bb2fe22
...
cc4a9b9b91
Author | SHA1 | Date |
---|---|---|
jorgepastorr | cc4a9b9b91 | |
jorgepastorr | 3ddd25b456 | |
jorgepastorr | a8808366e1 | |
jorgepastorr | d5980fd0c8 | |
jorgepastorr | 076f5dbb96 | |
jorgepastorr | f5e20c8858 | |
jorgepastorr | f3e0a62bf2 | |
jorgepastorr | 638c1ceb44 | |
jorgepastorr | 631c27cd14 | |
jorgepastorr | ca9dcceb88 | |
jorgepastorr | adc59e2e3c |
|
@ -26,12 +26,13 @@ from .actions import (list_contacts, service_report, delete_related_services, di
|
||||||
enable_selected)
|
enable_selected)
|
||||||
from .forms import AccountCreationForm
|
from .forms import AccountCreationForm
|
||||||
from .models import Account
|
from .models import Account
|
||||||
|
from .filters import HasTipeServerFilter
|
||||||
|
|
||||||
|
|
||||||
class AccountAdmin(ChangePasswordAdminMixin, auth.UserAdmin, ExtendedModelAdmin):
|
class AccountAdmin(ChangePasswordAdminMixin, auth.UserAdmin, ExtendedModelAdmin):
|
||||||
list_display = ('username', 'full_name', 'type', 'is_active')
|
list_display = ('username', 'full_name', 'type', 'is_active')
|
||||||
list_filter = (
|
list_filter = (
|
||||||
'type', 'is_active',
|
'type', 'is_active', HasTipeServerFilter
|
||||||
)
|
)
|
||||||
add_fieldsets = (
|
add_fieldsets = (
|
||||||
(_("User"), {
|
(_("User"), {
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
from django.contrib.admin import SimpleListFilter
|
from django.contrib.admin import SimpleListFilter
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from orchestra.contrib.orchestration.models import Server
|
||||||
|
from orchestra.contrib.websites.models import Website
|
||||||
|
from orchestra.settings import WEB_SERVERS
|
||||||
|
|
||||||
class IsActiveListFilter(SimpleListFilter):
|
class IsActiveListFilter(SimpleListFilter):
|
||||||
title = _("is active")
|
title = _("is active")
|
||||||
|
@ -25,3 +27,16 @@ class IsActiveListFilter(SimpleListFilter):
|
||||||
elif self.value() == 'object':
|
elif self.value() == 'object':
|
||||||
return queryset.filter(is_active=False)
|
return queryset.filter(is_active=False)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
class HasTipeServerFilter(SimpleListFilter):
|
||||||
|
title = _("has type server")
|
||||||
|
parameter_name = 'has_servers'
|
||||||
|
|
||||||
|
def lookups(self, request, model_admin):
|
||||||
|
return [ (x.id, x.name) for x in Server.objects.filter(name__in=WEB_SERVERS) ]
|
||||||
|
|
||||||
|
def queryset(self, request, queryset):
|
||||||
|
if self.value() is not None:
|
||||||
|
serverWebsites = Website.objects.filter(target_server=self.value())
|
||||||
|
return queryset.filter(id__in=[ x.account.id for x in serverWebsites ] )
|
||||||
|
return queryset
|
|
@ -16,7 +16,7 @@ from .filters import HasCustomAddressListFilter
|
||||||
from .models import List
|
from .models import List
|
||||||
|
|
||||||
|
|
||||||
class ListAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedModelAdmin):
|
class ListAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
||||||
list_display = (
|
list_display = (
|
||||||
'name', 'address_name', 'address_domain_link', 'account_link', 'display_active'
|
'name', 'address_name', 'address_domain_link', 'account_link', 'display_active'
|
||||||
)
|
)
|
||||||
|
@ -31,7 +31,7 @@ class ListAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedModel
|
||||||
}),
|
}),
|
||||||
(_("Admin"), {
|
(_("Admin"), {
|
||||||
'classes': ('wide',),
|
'classes': ('wide',),
|
||||||
'fields': ('admin_email', 'password1', 'password2'),
|
'fields': ('admin_email',),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
|
@ -45,35 +45,15 @@ class ListAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedModel
|
||||||
) % settings.LISTS_DEFAULT_DOMAIN,
|
) % settings.LISTS_DEFAULT_DOMAIN,
|
||||||
'fields': (('address_name', 'address_domain'),)
|
'fields': (('address_name', 'address_domain'),)
|
||||||
}),
|
}),
|
||||||
(_("Admin"), {
|
|
||||||
'classes': ('wide',),
|
|
||||||
'fields': ('password',),
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
search_fields = ('name', 'address_name', 'address_domain__name', 'account__username')
|
search_fields = ('name', 'address_name', 'address_domain__name', 'account__username')
|
||||||
list_filter = (IsActiveListFilter, HasCustomAddressListFilter)
|
list_filter = (IsActiveListFilter, HasCustomAddressListFilter)
|
||||||
readonly_fields = ('account_link',)
|
readonly_fields = ('account_link',)
|
||||||
change_readonly_fields = ('name',)
|
change_readonly_fields = ('name',)
|
||||||
form = NonStoredUserChangeForm
|
|
||||||
add_form = UserCreationForm
|
|
||||||
list_select_related = ('account', 'address_domain',)
|
list_select_related = ('account', 'address_domain',)
|
||||||
filter_by_account_fields = ['address_domain']
|
filter_by_account_fields = ['address_domain']
|
||||||
actions = (disable, enable, list_accounts)
|
actions = (disable, enable, list_accounts)
|
||||||
|
|
||||||
address_domain_link = admin_link('address_domain', order='address_domain__name')
|
address_domain_link = admin_link('address_domain', order='address_domain__name')
|
||||||
|
|
||||||
def get_urls(self):
|
|
||||||
useradmin = UserAdmin(List, self.admin_site)
|
|
||||||
return [
|
|
||||||
url(r'^(\d+)/password/$',
|
|
||||||
self.admin_site.admin_view(useradmin.user_change_password))
|
|
||||||
] + super(ListAdmin, self).get_urls()
|
|
||||||
|
|
||||||
def save_model(self, request, obj, form, change):
|
|
||||||
""" set password """
|
|
||||||
if not change:
|
|
||||||
obj.set_password(form.cleaned_data["password1"])
|
|
||||||
super(ListAdmin, self).save_model(request, obj, form, change)
|
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(List, ListAdmin)
|
admin.site.register(List, ListAdmin)
|
||||||
|
|
|
@ -182,7 +182,6 @@ class MailmanController(MailmanVirtualDomainController):
|
||||||
context.update({
|
context.update({
|
||||||
'banner': self.get_banner(mail_list),
|
'banner': self.get_banner(mail_list),
|
||||||
'name': mail_list.name,
|
'name': mail_list.name,
|
||||||
'password': mail_list.password,
|
|
||||||
'domain': mail_list.address_domain or settings.LISTS_DEFAULT_DOMAIN,
|
'domain': mail_list.address_domain or settings.LISTS_DEFAULT_DOMAIN,
|
||||||
'address_name': mail_list.get_address_name(),
|
'address_name': mail_list.get_address_name(),
|
||||||
'address_domain': mail_list.address_domain,
|
'address_domain': mail_list.address_domain,
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
# Generated by Django 2.2.28 on 2023-09-01 14:59
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import orchestra.core.validators
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('domains', '__first__'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='List',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(help_text='Default list address <name>@grups.pangea.org', max_length=64, unique=True, validators=[orchestra.core.validators.validate_name], verbose_name='name')),
|
||||||
|
('address_name', models.CharField(blank=True, max_length=64, validators=[orchestra.core.validators.validate_name], verbose_name='address name')),
|
||||||
|
('admin_email', models.EmailField(help_text='Administration email address', max_length=254, verbose_name='admin email')),
|
||||||
|
('is_active', models.BooleanField(default=True, help_text='Designates whether this account should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||||
|
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='lists', to=settings.AUTH_USER_MODEL, verbose_name='Account')),
|
||||||
|
('address_domain', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='domains.Domain', verbose_name='address domain')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('address_name', 'address_domain')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -8,17 +8,6 @@ from orchestra.core.validators import validate_name
|
||||||
from . import settings
|
from . import settings
|
||||||
|
|
||||||
|
|
||||||
class ListQuerySet(models.QuerySet):
|
|
||||||
def create(self, **kwargs):
|
|
||||||
""" Sets password if provided, all within a single DB operation """
|
|
||||||
password = kwargs.pop('password')
|
|
||||||
instance = self.model(**kwargs)
|
|
||||||
if password:
|
|
||||||
instance.set_password(password)
|
|
||||||
instance.save()
|
|
||||||
return instance
|
|
||||||
|
|
||||||
|
|
||||||
# TODO address and domain, perhaps allow only domain?
|
# TODO address and domain, perhaps allow only domain?
|
||||||
class List(models.Model):
|
class List(models.Model):
|
||||||
name = models.CharField(_("name"), max_length=64, unique=True, validators=[validate_name],
|
name = models.CharField(_("name"), max_length=64, unique=True, validators=[validate_name],
|
||||||
|
@ -35,9 +24,6 @@ class List(models.Model):
|
||||||
is_active = models.BooleanField(_("active"), default=True,
|
is_active = models.BooleanField(_("active"), default=True,
|
||||||
help_text=_("Designates whether this account should be treated as active. "
|
help_text=_("Designates whether this account should be treated as active. "
|
||||||
"Unselect this instead of deleting accounts."))
|
"Unselect this instead of deleting accounts."))
|
||||||
password = None
|
|
||||||
|
|
||||||
objects = ListQuerySet.as_manager()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('address_name', 'address_domain')
|
unique_together = ('address_name', 'address_domain')
|
||||||
|
@ -75,9 +61,6 @@ class List(models.Model):
|
||||||
def get_username(self):
|
def get_username(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def set_password(self, password):
|
|
||||||
self.password = password
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
context = {
|
context = {
|
||||||
'name': self.name
|
'name': self.name
|
||||||
|
|
|
@ -9,13 +9,14 @@ LISTS_DOMAIN_MODEL = Setting('LISTS_DOMAIN_MODEL',
|
||||||
|
|
||||||
|
|
||||||
LISTS_DEFAULT_DOMAIN = Setting('LISTS_DEFAULT_DOMAIN',
|
LISTS_DEFAULT_DOMAIN = Setting('LISTS_DEFAULT_DOMAIN',
|
||||||
'lists.{}'.format(ORCHESTRA_BASE_DOMAIN),
|
'grups.{}'.format(ORCHESTRA_BASE_DOMAIN),
|
||||||
help_text="Uses <tt>ORCHESTRA_BASE_DOMAIN</tt> by default."
|
help_text="Uses <tt>ORCHESTRA_BASE_DOMAIN</tt> by default."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
LISTS_LIST_URL = Setting('LISTS_LIST_URL',
|
LISTS_LIST_URL = Setting('LISTS_LIST_URL',
|
||||||
'https://lists.{}/mailman/listinfo/%(name)s'.format(ORCHESTRA_BASE_DOMAIN),
|
# 'https://lists.{}/mailman/listinfo/%(name)s'.format(ORCHESTRA_BASE_DOMAIN),
|
||||||
|
'https://www.{0}/mailman3/lists/%(name)s.{0}'.format(LISTS_DEFAULT_DOMAIN),
|
||||||
help_text="Uses <tt>ORCHESTRA_BASE_DOMAIN</tt> by default."
|
help_text="Uses <tt>ORCHESTRA_BASE_DOMAIN</tt> by default."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ from orchestra.core import caches
|
||||||
|
|
||||||
from . import settings
|
from . import settings
|
||||||
from .actions import SendMailboxEmail, SendAddressEmail
|
from .actions import SendMailboxEmail, SendAddressEmail
|
||||||
from .filters import HasMailboxListFilter, HasForwardListFilter, HasAddressListFilter
|
from .filters import HasMailboxListFilter, HasForwardListFilter, HasAddressListFilter, HasTipeServerFilter
|
||||||
from .forms import MailboxCreationForm, MailboxChangeForm, AddressForm
|
from .forms import MailboxCreationForm, MailboxChangeForm, AddressForm
|
||||||
from .models import Mailbox, Address, Autoresponse
|
from .models import Mailbox, Address, Autoresponse
|
||||||
from .widgets import OpenCustomFilteringOnSelect
|
from .widgets import OpenCustomFilteringOnSelect
|
||||||
|
@ -40,7 +40,7 @@ class MailboxAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedMo
|
||||||
list_display = (
|
list_display = (
|
||||||
'name', 'account_link', 'display_filtering', 'display_addresses', 'display_active',
|
'name', 'account_link', 'display_filtering', 'display_addresses', 'display_active',
|
||||||
)
|
)
|
||||||
list_filter = (IsActiveListFilter, HasAddressListFilter, 'filtering')
|
list_filter = (IsActiveListFilter, HasAddressListFilter, 'filtering', HasTipeServerFilter)
|
||||||
search_fields = (
|
search_fields = (
|
||||||
'account__username', 'account__short_name', 'account__full_name', 'name',
|
'account__username', 'account__short_name', 'account__full_name', 'name',
|
||||||
'addresses__name', 'addresses__domain__name',
|
'addresses__name', 'addresses__domain__name',
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
from django.contrib.admin import SimpleListFilter
|
from django.contrib.admin import SimpleListFilter
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from orchestra.contrib.orchestration.models import Server
|
||||||
|
from orchestra.contrib.websites.models import Website
|
||||||
|
from orchestra.settings import WEB_SERVERS
|
||||||
|
|
||||||
class HasMailboxListFilter(SimpleListFilter):
|
class HasMailboxListFilter(SimpleListFilter):
|
||||||
""" Filter addresses whether they have any mailbox or not """
|
""" Filter addresses whether they have any mailbox or not """
|
||||||
|
@ -45,3 +47,17 @@ class HasAddressListFilter(HasMailboxListFilter):
|
||||||
elif self.value() == 'False':
|
elif self.value() == 'False':
|
||||||
return queryset.filter(addresses__isnull=True)
|
return queryset.filter(addresses__isnull=True)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
class HasTipeServerFilter(SimpleListFilter):
|
||||||
|
title = _("has type server")
|
||||||
|
parameter_name = 'has_servers'
|
||||||
|
|
||||||
|
def lookups(self, request, model_admin):
|
||||||
|
return [ (x.id, x.name) for x in Server.objects.filter(name__in=WEB_SERVERS) ]
|
||||||
|
|
||||||
|
def queryset(self, request, queryset):
|
||||||
|
if self.value() is not None:
|
||||||
|
serverWebsites = Website.objects.filter(target_server=self.value())
|
||||||
|
return queryset.filter(account__in=[ x.account.id for x in serverWebsites ] )
|
||||||
|
return queryset
|
|
@ -700,7 +700,6 @@ class UNIXUserControllerNewServers(ServiceController):
|
||||||
def get_groups(self, user):
|
def get_groups(self, user):
|
||||||
groups = []
|
groups = []
|
||||||
if user.is_main:
|
if user.is_main:
|
||||||
groups = list(user.account.systemusers.exclude(username=user.username).values_list('username', flat=True))
|
|
||||||
groups.append("main-systemusers")
|
groups.append("main-systemusers")
|
||||||
return groups
|
return groups
|
||||||
|
|
||||||
|
@ -812,7 +811,8 @@ class WebappUserController(ServiceController):
|
||||||
|
|
||||||
def get_groups(self, user):
|
def get_groups(self, user):
|
||||||
groups = []
|
groups = []
|
||||||
groups = list(user.account.systemusers.exclude(username=user.username).values_list('username', flat=True))
|
# groups = list(user.account.systemusers.exclude(username=user.username).values_list('username', flat=True))
|
||||||
|
groups.append(user.account.main_systemuser.username)
|
||||||
groups.append("webapp-systemusers")
|
groups.append("webapp-systemusers")
|
||||||
return groups
|
return groups
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ class WebAppAdmin(SelectPluginAdminMixin, AccountAdminMixin, ExtendedModelAdmin)
|
||||||
list_display = (
|
list_display = (
|
||||||
'name', 'display_type', 'display_detail', 'display_websites', 'account_link', 'target_server',
|
'name', 'display_type', 'display_detail', 'display_websites', 'account_link', 'target_server',
|
||||||
)
|
)
|
||||||
list_filter = ('type', HasWebsiteListFilter, DetailListFilter)
|
list_filter = ('type', HasWebsiteListFilter, DetailListFilter, 'target_server')
|
||||||
inlines = [WebAppOptionInline]
|
inlines = [WebAppOptionInline]
|
||||||
readonly_fields = ('account_link',)
|
readonly_fields = ('account_link',)
|
||||||
change_readonly_fields = ('name', 'type', 'display_websites', 'display_sftpuser', 'target_server',)
|
change_readonly_fields = ('name', 'type', 'display_websites', 'display_sftpuser', 'target_server',)
|
||||||
|
|
|
@ -114,7 +114,6 @@ WEBAPPS_PHP_VERSIONS_SERVERS = Setting('WEBAPPS_PHP_VERSIONS_SERVERS', {
|
||||||
'web-ng' : ('5.6-fpm', '7.0-fpm', '7.3-fpm',),
|
'web-ng' : ('5.6-fpm', '7.0-fpm', '7.3-fpm',),
|
||||||
'web-11.pangea.lan': ('7.4-fpm',),
|
'web-11.pangea.lan': ('7.4-fpm',),
|
||||||
'web-12.pangea.lan' : ('8.1-fpm', '8.2-fpm'),
|
'web-12.pangea.lan' : ('8.1-fpm', '8.2-fpm'),
|
||||||
'bookworm' : ('8.1-fpm', '8.2-fpm'),
|
|
||||||
},
|
},
|
||||||
help_text="PHP available for each server",
|
help_text="PHP available for each server",
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,7 +3,7 @@ import os
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from orchestra.plugins.forms import ExtendedPluginDataForm
|
from orchestra.plugins.forms import ExtendedPluginDataForm, PluginDataForm
|
||||||
|
|
||||||
from ..options import AppOption
|
from ..options import AppOption
|
||||||
from . import AppType
|
from . import AppType
|
||||||
|
@ -23,6 +23,11 @@ class StaticApp(AppType):
|
||||||
return ('static', self.instance.get_path())
|
return ('static', self.instance.get_path())
|
||||||
|
|
||||||
|
|
||||||
|
class WebalizerAppform(PluginDataForm):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(WebalizerAppform, self).__init__(*args, **kwargs)
|
||||||
|
self.fields['sftpuser'].widget = forms.HiddenInput()
|
||||||
|
|
||||||
class WebalizerApp(AppType):
|
class WebalizerApp(AppType):
|
||||||
name = 'webalizer'
|
name = 'webalizer'
|
||||||
verbose_name = "Webalizer"
|
verbose_name = "Webalizer"
|
||||||
|
@ -32,6 +37,8 @@ class WebalizerApp(AppType):
|
||||||
"Statistics will be collected once this app is mounted into one or more Websites.")
|
"Statistics will be collected once this app is mounted into one or more Websites.")
|
||||||
icon = 'orchestra/icons/apps/Stats.png'
|
icon = 'orchestra/icons/apps/Stats.png'
|
||||||
option_groups = ()
|
option_groups = ()
|
||||||
|
# form = PluginDataForm
|
||||||
|
form = WebalizerAppform
|
||||||
|
|
||||||
def get_directive(self):
|
def get_directive(self):
|
||||||
webalizer_path = os.path.join(self.instance.get_path(), '%(site_name)s')
|
webalizer_path = os.path.join(self.instance.get_path(), '%(site_name)s')
|
||||||
|
|
|
@ -63,7 +63,7 @@ class WebsiteAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
||||||
'name', 'display_domains', 'display_webapps', 'account_link', 'target_server', 'display_active'
|
'name', 'display_domains', 'display_webapps', 'account_link', 'target_server', 'display_active'
|
||||||
)
|
)
|
||||||
list_filter = (
|
list_filter = (
|
||||||
'protocol', IsActiveListFilter, HasWebAppsListFilter, HasDomainsFilter
|
'protocol', IsActiveListFilter, HasWebAppsListFilter, HasDomainsFilter, 'target_server'
|
||||||
)
|
)
|
||||||
change_readonly_fields = ('name',)
|
change_readonly_fields = ('name',)
|
||||||
inlines = (ContentInline, WebsiteDirectiveInline)
|
inlines = (ContentInline, WebsiteDirectiveInline)
|
||||||
|
|
|
@ -93,7 +93,6 @@ ORCHESTRA_SSH_CONTROL_PATH = Setting('ORCHESTRA_SSH_CONTROL_PATH',
|
||||||
|
|
||||||
NEW_SERVERS = Setting('NEW_SERVERS',
|
NEW_SERVERS = Setting('NEW_SERVERS',
|
||||||
(
|
(
|
||||||
'bookworm',
|
|
||||||
'web-11.pangea.lan',
|
'web-11.pangea.lan',
|
||||||
'web-12.pangea.lan',
|
'web-12.pangea.lan',
|
||||||
)
|
)
|
||||||
|
@ -104,6 +103,5 @@ WEB_SERVERS = Setting('WEBAPPS_SERVERS', (
|
||||||
'web-ng',
|
'web-ng',
|
||||||
'web-11.pangea.lan',
|
'web-11.pangea.lan',
|
||||||
'web-12.pangea.lan',
|
'web-12.pangea.lan',
|
||||||
'bookworm',
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
|
@ -0,0 +1,25 @@
|
||||||
|
<?xml version="1.0" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||||
|
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||||
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="225.000000pt" height="225.000000pt" viewBox="0 0 225.000000 225.000000"
|
||||||
|
preserveAspectRatio="xMidYMid meet">
|
||||||
|
|
||||||
|
<g transform="translate(0.000000,225.000000) scale(0.100000,-0.100000)"
|
||||||
|
fill="#000000" stroke="none">
|
||||||
|
<path d="M1020 2240 c-441 -44 -804 -329 -953 -747 -80 -222 -80 -515 0 -736
|
||||||
|
117 -328 364 -574 690 -690 221 -80 515 -80 736 0 382 136 658 457 733 850 22
|
||||||
|
114 22 302 0 416 -75 393 -352 714 -733 850 -134 48 -328 72 -473 57z m237
|
||||||
|
-832 c59 -28 122 -90 147 -145 l18 -41 32 35 c99 114 273 87 337 -52 76 -163
|
||||||
|
-102 -341 -266 -266 -22 10 -54 34 -71 54 l-32 35 -18 -41 c-25 -56 -87 -117
|
||||||
|
-149 -147 -73 -35 -180 -34 -256 1 -64 30 -129 93 -154 149 l-17 38 -32 -35
|
||||||
|
c-99 -114 -273 -87 -337 52 -76 163 102 341 266 266 22 -10 54 -34 71 -54 l32
|
||||||
|
-35 17 38 c67 149 262 219 412 148z"/>
|
||||||
|
<path d="M1045 1294 c-126 -66 -142 -234 -30 -320 65 -49 170 -45 232 9 44 38
|
||||||
|
63 82 63 141 0 111 -75 186 -187 186 -26 0 -61 -7 -78 -16z"/>
|
||||||
|
<path d="M590 1180 c-12 -12 -20 -33 -20 -55 0 -45 29 -75 73 -75 51 0 77 24
|
||||||
|
77 71 0 50 -27 79 -75 79 -22 0 -43 -8 -55 -20z"/>
|
||||||
|
<path d="M1550 1182 c-16 -13 -22 -29 -22 -57 0 -48 28 -75 78 -75 45 0 74 30
|
||||||
|
74 75 0 45 -29 75 -74 75 -19 0 -44 -8 -56 -18z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
Loading…
Reference in New Issue