mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
Merge pull request #3615 from modoboa/feature/theme_simple_customization
Simple and stupid way to customize theme colors.
This commit is contained in:
+41
-1
@@ -4,4 +4,44 @@
|
||||
</Suspense>
|
||||
</template>
|
||||
|
||||
<script setup></script>
|
||||
<script setup>
|
||||
import { useTheme } from 'vuetify'
|
||||
import themeApi from '@/api/theme'
|
||||
|
||||
const theme = useTheme()
|
||||
|
||||
themeApi.getTheme().then((response) => {
|
||||
if (
|
||||
response.data.theme_primary_color !==
|
||||
theme.themes.value.light.colors.primary
|
||||
) {
|
||||
theme.themes.value.light.colors.primary = response.data.theme_primary_color
|
||||
}
|
||||
if (
|
||||
response.data.theme_primary_color_light !==
|
||||
theme.themes.value.light.colors['primary-lighten-1']
|
||||
) {
|
||||
theme.themes.value.light.colors['primary-lighten-1'] =
|
||||
response.data.theme_primary_color_light
|
||||
}
|
||||
if (
|
||||
response.data.theme_primary_color_dark !==
|
||||
theme.themes.value.light.colors['primary-darken-1']
|
||||
) {
|
||||
theme.themes.value.light.colors['primary-darken-1'] =
|
||||
response.data.theme_primary_color_dark
|
||||
}
|
||||
if (
|
||||
response.data.theme_secondary_color !==
|
||||
theme.themes.value.light.colors.secondary
|
||||
) {
|
||||
theme.themes.value.light.colors.secondary =
|
||||
response.data.theme_secondary_color
|
||||
}
|
||||
if (
|
||||
response.data.theme_label_color !== theme.themes.value.light.colors.label
|
||||
) {
|
||||
theme.themes.value.light.colors.label = response.data.theme_label_color
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import repository from './repository'
|
||||
|
||||
export default {
|
||||
getTheme() {
|
||||
return repository.get(`/theme/`)
|
||||
},
|
||||
}
|
||||
@@ -68,6 +68,16 @@
|
||||
v-model="parameters[param.name]"
|
||||
>
|
||||
</HtmlEditor>
|
||||
<v-color-picker
|
||||
v-else-if="param.widget === 'ColorPicker'"
|
||||
v-model="parameters[param.name]"
|
||||
:hint="param.help_text"
|
||||
persistent-hint
|
||||
:error="formErrors[param.name] !== undefined"
|
||||
:error-messages="formErrors[param.name]"
|
||||
density="compact"
|
||||
variant="outlined"
|
||||
/>
|
||||
<v-text-field
|
||||
v-else
|
||||
v-model="parameters[param.name]"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<v-toolbar flat>
|
||||
<v-toolbar-title>{{ $gettext('Welcome to Modoboa') }}</v-toolbar-title>
|
||||
<v-toolbar-title>{{ welcomeMsg }}</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
|
||||
<v-row>
|
||||
@@ -11,7 +11,21 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { useGettext } from 'vue3-gettext'
|
||||
import NewsFeedWidget from '@/components/admin/dashboard/NewsFeedWidget'
|
||||
import parametersApi from '@/api/parameters'
|
||||
|
||||
const { $gettext } = useGettext()
|
||||
|
||||
const welcomeMsg = ref('')
|
||||
|
||||
const response = await parametersApi.getGlobalApplication('core')
|
||||
if (response.data.params.custom_welcome_message) {
|
||||
welcomeMsg.value = response.data.params.custom_welcome_message
|
||||
} else {
|
||||
welcomeMsg.value = $gettext('Welcome to Modoboa')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -141,11 +141,19 @@ class CoreGlobalParametersSerializer(serializers.Serializer):
|
||||
)
|
||||
|
||||
# Dashboard settings
|
||||
custom_welcome_message = serializers.CharField(required=False)
|
||||
rss_feed_url = serializers.URLField(
|
||||
allow_blank=True, required=False, allow_null=True
|
||||
)
|
||||
hide_features_widget = serializers.BooleanField(default=False)
|
||||
|
||||
# Theme settings
|
||||
theme_primary_color = serializers.CharField(default="#046BF8")
|
||||
theme_primary_color_dark = serializers.CharField(default="#0350BA")
|
||||
theme_primary_color_light = serializers.CharField(default="#3688F9")
|
||||
theme_secondary_color = serializers.CharField(default="#F18429")
|
||||
theme_label_color = serializers.CharField(default="#616161")
|
||||
|
||||
# Notification settings
|
||||
sender_address = lib_fields.DRFEmailFieldUTF8(default="noreply@yourdomain.test")
|
||||
|
||||
@@ -654,3 +662,12 @@ class NewsFeedEntrySerializer(serializers.Serializer):
|
||||
title = serializers.CharField()
|
||||
link = serializers.CharField()
|
||||
published = serializers.DateTimeField()
|
||||
|
||||
|
||||
class ThemeSerializer(serializers.Serializer):
|
||||
|
||||
theme_primary_color = serializers.CharField(default="#046BF8")
|
||||
theme_primary_color_dark = serializers.CharField(default="#0350BA")
|
||||
theme_primary_color_light = serializers.CharField(default="#3688F9")
|
||||
theme_secondary_color = serializers.CharField(default="#F18429")
|
||||
theme_label_color = serializers.CharField(default="#616161")
|
||||
|
||||
@@ -545,6 +545,22 @@ class CapabilitiesAPITestCase(ModoAPITestCase):
|
||||
self.assertIn("rspamd", response.json()["capabilities"])
|
||||
|
||||
|
||||
class ThemeAPITestCase(ModoAPITestCase):
|
||||
|
||||
def test_get(self):
|
||||
url = reverse("v2:theme")
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
theme = response.json()
|
||||
self.assertEqual(theme["theme_primary_color"], "#046BF8")
|
||||
|
||||
self.set_global_parameter("theme_primary_color", "#FF0000")
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
theme = response.json()
|
||||
self.assertEqual(theme["theme_primary_color"], "#FF0000")
|
||||
|
||||
|
||||
class NewsFeedAPIViewTestCase(ModoAPITestCase):
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -43,4 +43,5 @@ urlpatterns += [
|
||||
name="news-feed",
|
||||
),
|
||||
path("capabilities/", views.CapabilitiesAPIView.as_view(), name="capabilities"),
|
||||
path("theme/", views.ThemeAPIView.as_view(), name="theme"),
|
||||
]
|
||||
|
||||
@@ -179,6 +179,27 @@ class CapabilitiesAPIView(APIView):
|
||||
return response.Response({"capabilities": get_capabilities()})
|
||||
|
||||
|
||||
class ThemeAPIView(APIView):
|
||||
"""Return the theme values of this Modoboa instance."""
|
||||
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
throttle_classes = [UserLesserDdosUser]
|
||||
|
||||
@extend_schema(responses=serializers.ThemeSerializer)
|
||||
def get(self, request, *args, **kwargs):
|
||||
values = {}
|
||||
params = [
|
||||
"theme_primary_color",
|
||||
"theme_primary_color_dark",
|
||||
"theme_primary_color_light",
|
||||
"theme_secondary_color",
|
||||
"theme_label_color",
|
||||
]
|
||||
for param in params:
|
||||
values[param] = request.localconfig.parameters.get_value(param)
|
||||
return response.Response(values)
|
||||
|
||||
|
||||
class NewsFeedAPIView(APIView):
|
||||
"""Return list of latest news from configured RSS feed."""
|
||||
|
||||
|
||||
@@ -99,12 +99,14 @@ GLOBAL_PARAMETERS_STRUCT = collections.OrderedDict(
|
||||
(
|
||||
"allow_special_characters",
|
||||
{
|
||||
"label": gettext_lazy("Allow special characters for random password"),
|
||||
"label": gettext_lazy(
|
||||
"Allow special characters for random password"
|
||||
),
|
||||
"help_text": gettext_lazy(
|
||||
"Enable special characters in randomly generated "
|
||||
"passwords."
|
||||
),
|
||||
}
|
||||
},
|
||||
),
|
||||
(
|
||||
"update_password_url",
|
||||
@@ -478,6 +480,15 @@ GLOBAL_PARAMETERS_STRUCT = collections.OrderedDict(
|
||||
"label": gettext_lazy("Dashboard"),
|
||||
"params": collections.OrderedDict(
|
||||
[
|
||||
(
|
||||
"custom_welcome_message",
|
||||
{
|
||||
"label": gettext_lazy("Custom welcome message"),
|
||||
"help_text": gettext_lazy(
|
||||
"A welcome message that will be displayed instead the default one"
|
||||
),
|
||||
},
|
||||
),
|
||||
(
|
||||
"rss_feed_url",
|
||||
{
|
||||
@@ -502,6 +513,70 @@ GLOBAL_PARAMETERS_STRUCT = collections.OrderedDict(
|
||||
),
|
||||
},
|
||||
),
|
||||
(
|
||||
"theme",
|
||||
{
|
||||
"label": gettext_lazy("Theme"),
|
||||
"params": collections.OrderedDict(
|
||||
[
|
||||
(
|
||||
"theme_primary_color",
|
||||
{
|
||||
"label": gettext_lazy("Primary theme color"),
|
||||
"help": gettext_lazy(
|
||||
"Primary color of the theme (hexadecimal code), used as the background color of the left menu for example"
|
||||
),
|
||||
"widget": "ColorPicker",
|
||||
},
|
||||
),
|
||||
(
|
||||
"theme_primary_color_light",
|
||||
{
|
||||
"label": gettext_lazy(
|
||||
"Primary theme color (lighter version)"
|
||||
),
|
||||
"help": gettext_lazy(
|
||||
"Primary color of the theme (hexadecimal code)"
|
||||
),
|
||||
"widget": "ColorPicker",
|
||||
},
|
||||
),
|
||||
(
|
||||
"theme_primary_color_dark",
|
||||
{
|
||||
"label": gettext_lazy(
|
||||
"Primary theme color (darker version)"
|
||||
),
|
||||
"help": gettext_lazy(
|
||||
"Primary color of the theme (hexadecimal code)"
|
||||
),
|
||||
"widget": "ColorPicker",
|
||||
},
|
||||
),
|
||||
(
|
||||
"theme_secondary_color",
|
||||
{
|
||||
"label": gettext_lazy("Secondary theme color"),
|
||||
"help": gettext_lazy(
|
||||
"Secondary color of the theme (hexadecimal code), used as the background color of some buttons for example"
|
||||
),
|
||||
"widget": "ColorPicker",
|
||||
},
|
||||
),
|
||||
(
|
||||
"theme_label_color",
|
||||
{
|
||||
"label": gettext_lazy("Labels theme color"),
|
||||
"help": gettext_lazy(
|
||||
"Theme color for labels (hexadecimal code)"
|
||||
),
|
||||
"widget": "ColorPicker",
|
||||
},
|
||||
),
|
||||
]
|
||||
),
|
||||
},
|
||||
),
|
||||
(
|
||||
"notifications",
|
||||
{
|
||||
|
||||
@@ -134,7 +134,7 @@ def send_mail(request, attributes: dict, attachments: list) -> tuple[bool, str |
|
||||
{
|
||||
"backend": "modoboa.lib.smtp_backend.OAuthBearerEmailBackend",
|
||||
"username": request.user.email,
|
||||
"password": request.auth,
|
||||
"password": str(request.auth),
|
||||
}
|
||||
)
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user