Skip to content
Commits on Source (77)
......@@ -6,6 +6,26 @@ All notable changes to this project will be documented in this file.
The format is based on `Keep a Changelog`_,
and this project adheres to `Semantic Versioning`_.
`3.1`_ - 2022-05-30
-------------------
Changed
~~~~~~~
* The frontend is now able to display headings in the main toolbar.
Fixed
~~~~~
* Default translations from Vuetify were not loaded.
* Browser locale was not the default locale in the entire frontend.
* In some cases, items in the sidenav menu were not shown.
* The search bar in the sidenav menu was shown even though the user had no permission to see it.
* Accept invitation menu item was shown when the invitation feature was disabled.
* Metrics endpoint for Prometheus was at the wrong URL.
* Polling behavior of the whoAmI and permission queries was improved.
* Confirmation e-mail contained a wrong link.
`3.0`_ - 2022-05-11
-------------------
......@@ -16,6 +36,7 @@ Added
* Provide API endpoint for system status.
* [Dev] UpdateIndicator Vue Component to display the status of interactive pages
* [Dev] DeleteDialog Vue Component to unify item deletion in the new frontend
* Use build-in mechanism in Apollo for GraphQL batch querying.
Changed
......@@ -1094,50 +1115,51 @@ Fixed
.. _Keep a Changelog: https://keepachangelog.com/en/1.0.0/
.. _Semantic Versioning: https://semver.org/spec/v2.0.0.html
.. _1.0a1: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/1.0a1
.. _1.0a2: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/1.0a2
.. _1.0a4: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/1.0a4
.. _2.0a1: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.0a1
.. _2.0a2: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.0a2
.. _2.0b0: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.0b0
.. _2.0b1: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.0b1
.. _2.0b2: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.0b2
.. _2.0rc1: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.0rc1
.. _2.0rc2: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.0rc2
.. _2.0rc3: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.0rc3
.. _2.0rc4: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.0rc4
.. _2.0rc5: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.0rc5
.. _2.0rc6: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.0rc6
.. _2.0rc7: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.0rc7
.. _2.0: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.0
.. _2.1: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.1
.. _2.1.1: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.1.1
.. _2.2: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.2
.. _2.2.1: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.2.1
.. _2.3: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.3
.. _2.3.1: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.3.1
.. _2.4: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.4
.. _2.5: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.5
.. _2.6: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.6
.. _2.7: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.7
.. _2.7.1: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.7.1
.. _2.7.2: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.7.2
.. _2.7.3: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.7.3
.. _2.7.4: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.7.4
.. _2.8: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.8
.. _2.8.1: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.8.1
.. _2.9: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.9
.. _2.10: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.10
.. _2.10.1: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.10.1
.. _2.10.2: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.10.2
.. _2.11: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.11
.. _2.11.1: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.11.1
.. _2.12: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.12
.. _2.12.1: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.12.1
.. _2.12.2: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.12.2
.. _2.12.3: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/2.12.3
.. _3.0b0: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/3.0b0
.. _3.0b1: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/3.0b1
.. _3.0b2: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/3.0b2
.. _3.0b3: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/3.0b3
.. _3.0: https://edugit.org/AlekSIS/Official/AlekSIS/-/tags/3.0
.. _1.0a1: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/1.0a1
.. _1.0a2: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/1.0a2
.. _1.0a4: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/1.0a4
.. _2.0a1: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.0a1
.. _2.0a2: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.0a2
.. _2.0b0: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.0b0
.. _2.0b1: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.0b1
.. _2.0b2: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.0b2
.. _2.0rc1: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.0rc1
.. _2.0rc2: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.0rc2
.. _2.0rc3: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.0rc3
.. _2.0rc4: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.0rc4
.. _2.0rc5: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.0rc5
.. _2.0rc6: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.0rc6
.. _2.0rc7: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.0rc7
.. _2.0: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.0
.. _2.1: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.1
.. _2.1.1: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.1.1
.. _2.2: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.2
.. _2.2.1: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.2.1
.. _2.3: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.3
.. _2.3.1: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.3.1
.. _2.4: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.4
.. _2.5: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.5
.. _2.6: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.6
.. _2.7: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.7
.. _2.7.1: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.7.1
.. _2.7.2: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.7.2
.. _2.7.3: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.7.3
.. _2.7.4: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.7.4
.. _2.8: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.8
.. _2.8.1: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.8.1
.. _2.9: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.9
.. _2.10: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.10
.. _2.10.1: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.10.1
.. _2.10.2: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.10.2
.. _2.11: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.11
.. _2.11.1: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.11.1
.. _2.12: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.12
.. _2.12.1: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.12.1
.. _2.12.2: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.12.2
.. _2.12.3: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/2.12.3
.. _3.0b0: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/3.0b0
.. _3.0b1: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/3.0b1
.. _3.0b2: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/3.0b2
.. _3.0b3: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/3.0b3
.. _3.0: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/3.0
.. _3.1: https://edugit.org/AlekSIS/official/AlekSIS-Core/-/tags/3.1
......@@ -160,7 +160,18 @@ class EditGroupForm(SchoolTermRelatedExtensibleForm):
class Meta:
model = Group
exclude = []
fields = [
"school_term",
"name",
"short_name",
"group_type",
"members",
"owners",
"parent_groups",
"additional_fields",
"photo",
"avatar",
]
widgets = {
"members": ModelSelect2MultipleWidget(
search_fields=[
......@@ -316,7 +327,16 @@ class AnnouncementForm(ExtensibleForm):
class Meta:
model = Announcement
exclude = []
fields = [
"valid_from_date",
"valid_from_time",
"valid_until_date",
"valid_until_time",
"groups",
"persons",
"title",
"description",
]
class ChildGroupsForm(forms.Form):
......@@ -348,7 +368,7 @@ class EditAdditionalFieldForm(forms.ModelForm):
class Meta:
model = AdditionalField
exclude = []
fields = ["title", "field_type", "required", "help_text"]
class EditGroupTypeForm(forms.ModelForm):
......@@ -366,7 +386,7 @@ class SchoolTermForm(ExtensibleForm):
class Meta:
model = SchoolTerm
exclude = []
fields = ["name", "date_start", "date_end"]
class DashboardWidgetOrderForm(ExtensibleForm):
......
......@@ -2,11 +2,12 @@
* Configuration for Apollo provider, client, and caches.
*/
import { ApolloClient, HttpLink, from } from "@/apollo-boost";
import { ApolloClient, from } from "@/apollo-boost";
import { RetryLink } from "@/apollo-link-retry";
import { persistCache, LocalStorageWrapper } from "@/apollo3-cache-persist";
import { InMemoryCache } from "@/apollo-cache-inmemory";
import { BatchHttpLink } from "@/apollo-link-batch-http";
// Cache for GraphQL query results in memory and persistent across sessions
const cache = new InMemoryCache();
......@@ -33,14 +34,17 @@ const links = [
// Automatically retry failed queries
new RetryLink(),
// Finally, the HTTP link to the real backend (Django)
new HttpLink({
new BatchHttpLink({
uri: getGraphqlURL(),
batchInterval: 200,
batchDebounce: true,
}),
];
/** Upstream Apollo GraphQL client */
const apolloClient = new ApolloClient({
cache,
shouldBatch: true,
link: from(links),
});
......
......@@ -25,7 +25,7 @@
class="white--text text-decoration-none"
@click="$router.push({ name: 'dashboard' })"
>
{{ systemProperties.sitePreferences.generalTitle }}
{{ $root.toolbarTitle }}
</v-toolbar-title>
<v-progress-linear
......@@ -246,12 +246,17 @@ export default {
systemProperties: gqlSystemProperties,
whoAmI: {
query: gqlWhoAmI,
pollInterval: 30000,
result({ data }) {
if (data && data.whoAmI) {
this.$root.permissions = data.whoAmI.permissions;
}
},
variables() {
return {
permissions: this.permissionNames,
permissions: this.$root.permissionNames,
};
},
pollInterval: 10000,
},
messages: {
query: gqlMessages,
......
......@@ -33,17 +33,33 @@ export default {
type: Array,
required: true,
},
defaultLanguage: {
type: Object,
required: true,
},
},
methods: {
setLanguage: function (languageOption) {
document.cookie = languageOption.cookie;
this.$i18n.locale = languageOption.code;
this.$vuetify.lang.current = languageOption.code;
this.language = languageOption;
},
nameForMenu: function (item) {
return `${item.nameLocal} (${item.code})`;
},
},
mounted() {
if (
this.availableLanguages.filter((lang) => lang.code === this.$i18n.locale)
.length === 0
) {
console.warn(
`Unsupported language ${this.$i18n.locale} selected, defaulting to ${this.defaultLanguage.code}`
);
this.setLanguage(this.defaultLanguage);
}
},
name: "LanguageForm",
};
</script>
<template>
<v-navigation-drawer app :value="value" @input="$emit('input', $event)">
<v-navigation-drawer
app
:value="value"
height="100dvh"
@input="$emit('input', $event)"
>
<v-list nav dense shaped>
<v-list-item class="logo">
<a
......@@ -10,7 +15,7 @@
<brand-logo :site-preferences="systemProperties.sitePreferences" />
</a>
</v-list-item>
<v-list-item class="search">
<v-list-item v-if="checkPermission('core.search_rule')" class="search">
<sidenav-search />
</v-list-item>
<v-list-item-group :value="$route.name" v-if="sideNavMenu">
......@@ -82,6 +87,7 @@
<v-spacer />
<language-form
:available-languages="systemProperties.availableLanguages"
:default-language="systemProperties.defaultLanguage"
/>
<v-spacer />
</div>
......@@ -94,6 +100,8 @@ import BrandLogo from "./BrandLogo.vue";
import LanguageForm from "./LanguageForm.vue";
import SidenavSearch from "./SidenavSearch.vue";
import permissionsMixin from "../../mixins/permissions.js";
export default {
name: "SideNav",
components: {
......@@ -106,6 +114,10 @@ export default {
systemProperties: { type: Object, required: true },
value: { type: Boolean, required: true },
},
mixins: [permissionsMixin],
mounted() {
this.addPermissions(["core.search_rule"]);
},
};
</script>
......
......@@ -6,6 +6,12 @@
nameLocal
cookie
}
defaultLanguage {
code
nameTranslated
nameLocal
cookie
}
sitePreferences {
themePrimary
themeSecondary
......
query ($permissions: [String]!) {
query whoAmI($permissions: [String]!) {
whoAmI {
username
isAuthenticated
......
<template>
<div>
<h1 class="mb-4">{{ $t("oauth.authorized_application.title") }}</h1>
<div v-if="$apollo.queries.accessTokens.loading">
<v-skeleton-loader type="card"></v-skeleton-loader>
</div>
......
......@@ -47,7 +47,7 @@ export default {
apollo: {
celeryProgressByUser: {
query: gqlCeleryProgressButton,
pollInterval: 1000,
pollInterval: 30000,
},
},
};
......
......@@ -83,7 +83,7 @@ export default {
apollo: {
myNotifications: {
query: gqlMyNotifications,
pollInterval: 1000,
pollInterval: 30000,
},
},
};
......
<template>
<div>
<h1 class="mb-4">{{ $t("accounts.two_factor.title") }}</h1>
<div v-if="$apollo.queries.twoFactor.loading">
<v-skeleton-loader type="card,card"></v-skeleton-loader>
</div>
......
......@@ -36,9 +36,7 @@ import routerOpts from "./app/router.js";
import apolloOpts from "./app/apollo.js";
const i18n = new VueI18n({
locale: Vue.$cookies.get("django_language")
? Vue.$cookies.get("django_language")
: "en",
locale: Vue.$cookies.get("django_language") || navigator.language || "en",
...i18nOpts,
});
const vuetify = new Vuetify({
......@@ -46,6 +44,7 @@ const vuetify = new Vuetify({
current: Vue.$cookies.get("django_language")
? Vue.$cookies.get("django_language")
: "en",
t: (key, ...params) => i18n.t(key, params),
},
...vuetifyOpts,
});
......@@ -70,6 +69,9 @@ const app = new Vue({
backgroundActive: true,
invalidation: false,
snackbarItems: [],
toolbarTitle: "AlekSIS®",
permissions: [],
permissionNames: [],
}),
computed: {
matchedComponents() {
......@@ -89,5 +91,6 @@ const app = new Vue({
});
// Late setup for some plugins handed off to out ALeksisVue plugin
app.$loadVuetifyMessages();
app.$loadAppMessages();
app.$setupNavigationGuards();
......@@ -45,28 +45,28 @@
"menu_title": "Drittanbieter-Konten"
},
"two_factor": {
"add_authentication_method": "Authentifizierungsmethode hinzufügen",
"backup_codes_count": "Sie haben keine Backup-Codes übrig.|Sie haben nur noch einen Backup-Code übrig.|Sie haben {counter} Backup-Codes übrig.",
"add_authentication_method": "Authentifizierungs-Methode hinzufügen",
"backup_codes_count": "Sie haben keine Backup-Codes mehr übrig.|Sie haben nur noch einen Backup-Code übrig.|Sie haben {counter} Backup-Codes übrig.",
"backup_codes_description": "Wenn Sie keines Ihrer Geräte benutzen können, können Sie mithilfe von Backup-Codes auf Ihr Konto zugreifen.",
"backup_codes_title": "Backup-Codes",
"disable_button": "Zwei-Faktor-Authentifizierung deaktivieren",
"disable_description": "Auch wenn Ihr stark davon abraten, können Sie die Zwei-Faktor-Authentifizierung für Ihr Konto deaktivieren.",
"disable_title": "Zwei-Faktor-Authentifizierung deaktivieren",
"disable_button": "Zwei-Faktor-Authentifizierung deaktiveren",
"disable_description": "Auch wenn wir Ihnen stark davon abraten, können Sie die Zwei-Faktor-Authentifizierung für Ihr Konto auch deaktivieren.",
"disable_title": "Zwei-Faktor-Authentifizierung deaktiveren",
"enable_button": "Zwei-Faktor-Authentifizierung aktivieren",
"enable_description": "Die Zwei-Faktor-Authentifizierung ist für Ihr Konto nicht aktiviert. Aktivieren Sie die Zwei-Faktor-Authentifizierung für eine bessere Kontosicherheit.",
"enable_description": "Die Zwei-Faktor-Authentifizierung ist für Ihr Konto aktuell nicht aktiviert. Aktivieren Sie die Zwei-Faktor-Authentifizierung für zusätzliche Kontosicherheit.",
"enable_title": "Zwei-Faktor-Authentifizierung aktuell deaktiviert",
"menu_title": "2FA",
"methods": {
"call": "Wir rufen Sie auf Ihrem Handy an und nennen Ihnen einen Einmalcode.",
"call": "Wir rufen Sie auf Ihrer Mobiltelefonnummer an und teilen Ihnen einen Einmal-Code mit.",
"email": "Wir senden Ihnen Einmal-Codes an Ihre E-Mail-Adresse.",
"generator": "Sie generieren Einmal-Codes mit einem Code-Generator.",
"sms": "Wir senden Ihnen Einmal-Codes an Ihre Handy-Nummer.",
"webauthn": "Sie nutzen einen Sicherheitsschlüssel (entweder als externes Gerät oder in Ihr persönliches Gerät integriert).",
"yubikey": "Sie nutzen eine Yubikey, um Einmal-Codes zu generieren."
"generator": "Sie generieren Einmal-Codes mithilfe eines Code-Generators.",
"sms": "Wir senden Ihnen Einmal-Codes an Ihre Mobiltelefonnummer.",
"webauthn": "Sie nutzen einen Sicherheitsschlüssel (entweder als externes Gerät oder integriert in Ihr persönliches Gerät).",
"yubikey": "Sie nutzen einen Yubikey, um Einmal-Codes zu generieren."
},
"other_devices_description": "Wenn Ihr primäres Authentifizierungs-Gerät während der Anmeldung nicht verfügbar ist, können Sie eines der folgende Geräte nutzen:",
"other_devices_description": "Wenn Ihr primäres Authentifizierungs-Gerät während der Anmeldung nicht verfügbar ist, können Sie eins der folgenden Geräte nutzen:",
"other_devices_title": "Andere Authentifizierungs-Geräte",
"primary_device_description": "Während der Anmeldung wird AlekSIS Sie bitten, die Anmeldung mit folgendem Gerät zu bestätigen. Wenn das Gerät nicht verfügbar ist, können Sie ein Backup-Gerät benutzen.",
"primary_device_description": "Während der Anmeldung wird AlekSIS Sie fragen, die Anmeldung mit dem folgenden Gerät zu bestätigen. Wenn dieses Gerät nicht verfügbar ist, können Sie ein Backup-Gerät nutzen.",
"primary_device_title": "Primäres Authentifizierungs-Gerät",
"title": "Zwei-Faktor-Authentifizierung"
}
......
{
"about": {
"about_aleksis": "Про AlekSIS®",
"about_aleksis_1": "Эта платформа основана на AlekSIS®, веб-инструменте информационной системы для обучения (SIS), с помощью которой можно управлять и/или освещать организационные моменты учебных заведений. AlekSIS — бесплатное ПО, которое может использовать кто-угодно.",
"about_aleksis_2": "AlekSIS® — зарегистрированная торговая марка проекта с открытым исходным кодом AlekSIS, представленная Teckids e.V.",
"free_open_source_licence": "Лицензия бесплатного или свободного исходного кода",
"free_software": "Свободное ПО",
"full_licence_text": "Полный текст лицензии",
"licence_consists_of": "Лицензия состоит из",
"licence_information": "Лицензионная информация",
"licence_information_1": "Ядро и официальные приложения AlekSIS лицензированы EUPL, версии 1.2 или поздней. Для получения лицензионной информации о приложениях третьих сторон, если такие установлены, обратитесь к соответствующим компонентам ниже. Лицензии обозначены таким образом:",
"licence_type": "Тип Лицензии",
"licenced_under": "Лицензировано под",
"more_information_eupl": "Больше информации о EUPL",
"open_source": "Открытое ПО",
"other_licence": "Другая лицензия",
"page_title": "Про AlekSIS®",
"proprietary": "Проприетарное",
"show_copyright": "Показать авторские права",
"source_code": "Исходный код",
"website_of_aleksis": "Веб-сайт AlekSIS"
},
"accounts": {
"change_password": {
"menu_title": "Изменить пароль"
},
"invitation": {
"accept_invitation": {
"menu_title": "Принять приглашение"
},
"invite_person": {
"menu_title": "Пригласить кого-то"
}
},
"login": {
"menu_title": "Войти"
},
"logout": {
"menu_title": "Выйти"
},
"signup": {
"menu_title": "Зарегистрироваться"
},
"social_connections": {
"menu_title": "Учётные записи третьих сторон"
},
"two_factor": {
"menu_title": "2FA"
}
},
"actions": {
"back": "Назад",
"close": "Закрыть",
"edit": "Редактировать",
"search": "Поиск"
},
"administration": {
"backend_admin": {
"menu_title": "Администрирование бекенда"
},
"menu_title": "Администрация",
"system_status": {
"menu_title": "Состояние системы"
}
},
"alerts": {
"page_cached": "Эта страница может содержать устаревшую информацию, т.к. отсутствует интернет-соединение."
},
"announcement": {
"menu_title": "Объявления",
"title": "Объявление",
"title_plural": "Объявления"
},
"base": {
"about_aleksis": "Об AlekSIS® — The Free School Information System",
"imprint": "Реквизиты",
"logo": "Логотип",
"no_permission": "У Вас нет разрешения на просмотр этой страницы. Войдите, пожалуйста, с другой учётной записью.",
"no_permission_message_long": "У Вас нет разрешения на просмотр этой страницы. Войдите, пожалуйста, с другой учётной записью.",
"no_permission_message_short": "Нет разрешения",
"no_permission_redirect_text": "На страницу входа",
"person_is_dummy": "Ваша административная учётная запись не связана ни с кем. В виду этого, к Вашему аккаунту подвязана фейковая учётная запись.",
"privacy_policy": "Политика персональных данных",
"user_not_linked_to_person": "Ваша учётная запись не связана ни с кем. Это может означать, что у Вас не будет доступа к учебной информации. Обратитесь, пожалуйста, к администраторам AlekSIS в своём учебном заведении."
},
"celery_progress": {
"error_message": "Завершить операцию успешно не получилось.",
"progress_title": "Загрузка ...",
"running_tasks": "Выполняется 1 задание | Выполняется {number} заданий",
"success_message": "Операция успешно завершена."
},
"dashboard": {
"dashboard_widget": {
"menu_title": "Виджеты информпанели",
"title": "Виджет информпанели",
"title_plural": "Виджеты информпанели"
},
"menu_title": "Информпанель"
},
"data_check": {
"menu_title": "Проверки данных"
},
"download_pdf": {
"download": "Скачать",
"notice": "Если скачивание автоматически не началось, нажмите, пожалуйста, кнопку, которая находится ниже.",
"title": "Скачивается файл PDF ..."
},
"graphql": {
"snackbar_error_message": "Во время получения данных страницы возникла ошибка. Попробуйте, пожалуйста, ещё раз."
},
"group": {
"additional_field": {
"menu_title": "Дополнительные поля",
"title": "Дополнительное поле",
"title_plural": "Дополнительные поля"
},
"group_type": {
"menu_title": "Типы групп",
"title": "Тип группы",
"title_plural": "Типы групп"
},
"groups_and_child_groups": "Группы и дочерние группы",
"menu_title": "Группы",
"ownership": "Владельцы группы",
"title": "Группа",
"title_plural": "Группы"
},
"ical_feed": {
"menu_title": "Каналы календарей"
},
"legacy": {
"unworthy": "Форма жизни, создавшая эту страницу, не склонилась перед силами всемогущего Алексолотля."
},
"network_errors": {
"back_to_start": "Вернуться домой",
"error_404": "404",
"offline_notification": "Вы в автономном режиме. Некоторые функции могут не работать и некоторые данные могут не обновляться.",
"page_not_found": "Страница, которую Вы искали, или ресурс с ней не найдены.",
"snackbar_error_message": "Возникла ошибка сети. Попробуйте ещё раз."
},
"notifications": {
"mark_as_read": "Отметить прочитанным",
"more_information": "Подробнее",
"no_notifications": "У Вас нет новых уведомлений.",
"notifications": "Уведомления"
},
"oauth": {
"application": {
"menu_title": "Приложения OAuth2",
"title": "Приложение OAuth2",
"title_plural": "Приложения OAuth2"
},
"authorized_token": {
"menu_title": "Авторизованные приложения"
}
},
"people": "Люди",
"permissions": {
"manage": {
"menu_title": "Управление доступом"
}
},
"person": {
"account_menu_title": "Учётная запись",
"additional_image": "Дополнительное изображение",
"avatar": "Аватар",
"children": "Дети",
"delete": "Удалить",
"details": "Контактные данные",
"guardians": "Родители / опекуны",
"home": "домашний телефон",
"impersonation": {
"impersonate": "Теневой сеанс",
"impersonating": "Маскируясь как",
"stop": "Остановить теневой сеанс"
},
"invite": "Приглашение",
"logged_in_as": "Вход выполнен как",
"menu_title": "Люди",
"mobile": "мобильный телефон",
"no_additional_image": "Этот пользователь не загрузил дополнительное изображение",
"no_persons": "Никого нет",
"page_title": "Физлицо",
"title": "Физлицо",
"title_plural": "Люди"
},
"preferences": {
"person": {
"change_preferences": "Свойства",
"menu_title": "Свойства"
},
"site": {
"menu_title": "Конфигурация"
}
},
"school_term": {
"menu_title": "Учебные годы",
"title": "Учебный год",
"title_plural": "Учебные годы"
},
"service_worker": {
"dismiss": "Отказаться",
"new_version_available": "Доступна новая версия программы",
"update": "Обновить"
}
}
{
"about": {
"about_aleksis": "Щодо AlekSIS®",
"about_aleksis_1": "Ця платформа базується на AlekSIS®, веб-інструменті інформаційної системи для навчання (SIS), за допомогою якої можна керувати та/або висвітлювати організаційні елементи навчальних закладів. AlekSIS - безплатне ПЗ і ним може користуватися будь-хто.",
"about_aleksis_2": "AlekSIS® зареєстрована торгова марка проекту з відкритим програмним кодом AlekSIS, що представлена Teckids e.V.",
"free_open_source_licence": "Безкоштовна або Ліцензія Відкритого Коду (Open Source)",
"about_aleksis_1": "Ця платформа базується на AlekSIS®, веб-інструменті інформаційної системи для навчання (SIS), за допомогою якої можна керувати та/або висвітлювати організаційні моменти навчальних закладів. AlekSIS безплатне ПЗ і ним може користуватися будь-хто.",
"about_aleksis_2": "AlekSIS® зареєстрована торгова марка проекту з відкритим сирцевим кодом AlekSIS, що представлена Teckids e.V.",
"free_open_source_licence": "Ліцензія Безкоштовного або Відкритого Коду (Open Source)",
"free_software": "Вільне ПЗ",
"full_licence_text": "Повний текст ліцензії",
"licence_consists_of": "Ліцензія складається з",
......@@ -17,7 +17,7 @@
"page_title": "Щодо AlekSIS®",
"proprietary": "Пропрієтарне",
"show_copyright": "Показати авторські права",
"source_code": "Програмний код",
"source_code": "Сирцевий код",
"website_of_aleksis": "Веб-сайт AlekSIS"
},
"accounts": {
......@@ -42,10 +42,33 @@
"menu_title": "Зареєструватися"
},
"social_connections": {
"menu_title": "Обліковки третіх сторін"
"menu_title": "Облікові записи третіх сторін"
},
"two_factor": {
"menu_title": "2FA"
"add_authentication_method": "Додати метод аутентифікації",
"backup_codes_count": "У Вас не залишилося резервних кодів.|У Вас залишився лише один резервний код.|У Вас залишилося {counter} резервних кодів.",
"backup_codes_description": "Якщо Ви не зможете скористатися ніяким зі своїх пристроїв, Ви зможете отримати доступ до облікового запису за допомогою резервних кодів.",
"backup_codes_title": "Резервні коди",
"disable_button": "Вимкнути двофакторну аутентифікацію",
"disable_description": "Хоча ми рішуче застерігаємо Вас від цього, Ви можете вимкнути двофакторну аутентифікацію для свого облікового запису.",
"disable_title": "Вимкнути двофакторну аутентифікацію",
"enable_button": "Увімкнути двофакторну аутентифікацію",
"enable_description": "Двофакторна аутентифікація у Вашому обліковому записі не працює. Для кращої безпеки увімкніть двофакторну аутентифікацію.",
"enable_title": "Двофакторна аутентифікація зараз вимкнена",
"menu_title": "2FA",
"methods": {
"call": "Ми зателефонуємо на Ваш мобільний та скажемо одноразовий код.",
"email": "Ми надішлемо Вам одноразові коди на Вашу ел.пошту.",
"generator": "Ви генеруєте одноразові коди за допомогою генератора кодів.",
"sms": "Ми надішлемо Вам одноразові коди на Ваш мобільний.",
"webauthn": "Ви використовуєте ключ безпеки (зовнішній або вбудований у Ваш персональний пристрій).",
"yubikey": "Ви використовуєте YubiKey для створення одноразових кодів."
},
"other_devices_description": "Якщо Ваш основний пристрій аутентифікації буде недоступний під час входу, Ви можете скористатися одним з цих пристроїв:",
"other_devices_title": "Інші пристрої аутентифікації",
"primary_device_description": "Під час входу в систему AlekSIS попросить Вас підтвердити вхід за допомогою наступного пристрою. Якщо цей пристрій буде недоступний, Ви зможете скористатися резервним пристроєм.",
"primary_device_title": "Основний пристрій для аутентифікації",
"title": "Двофакторна аутентифікація"
}
},
"actions": {
......@@ -73,12 +96,15 @@
},
"base": {
"about_aleksis": "Щодо AlekSIS® — The Free School Information System",
"imprint": "Відбиток",
"imprint": "Реквізити",
"logo": "Логотип",
"no_permission": "Ви не маєте дозволу на перегляд цієї сторінки. Увійдіть, будь ласка, з іншим обліковим записом.",
"no_permission_message_long": "Ви не маєте дозволу на перегляд цієї сторінки. Увійдіть, будь ласка, з іншим обліковим записом.",
"no_permission_message_short": "Немає дозволу",
"no_permission_redirect_text": "На сторінку входу",
"person_is_dummy": "Ваш адміністративний обліковий запис не поєднаний з жодною особою. Через це до Вашого облікового запису приєднана фейкова особа.",
"privacy_policy": "Політика приватності",
"user_not_linked_to_person": "Ваш обліковий запис не пов'язаний з фізособою. Це означає, що Ви не маєте жодного доступу до навчальної інформації. Зверніться, будь ласка, до адміністраторів AlekSIS у Вашому навчальному закладі."
"user_not_linked_to_person": "Ваш обліковий запис не пов'язаний з фізособою. Це означає, що Ви не маєте жодного доступу до навчальної інформації. Зверніться, будь ласка, до адміністраторів AlekSIS у своєму навчальному закладі."
},
"celery_progress": {
"error_message": "Завершити цю операцію успішно не вдалося.",
......@@ -108,7 +134,7 @@
"group": {
"additional_field": {
"menu_title": "Додаткові поля",
"title": "Додаткові поля",
"title": "Додаткове поле",
"title_plural": "Додаткові поля"
},
"group_type": {
......@@ -137,7 +163,7 @@
},
"notifications": {
"mark_as_read": "Позначити прочитаним",
"more_information": "Більше інформаціЇ",
"more_information": "Докладніше",
"no_notifications": "У Вас немає нових сповіщень.",
"notifications": "Сповіщення"
},
......@@ -164,8 +190,8 @@
"children": "Діти",
"delete": "Видалити",
"details": "Контактні дані",
"guardians": "Опікуни / батьки",
"home": "Домашній телефон",
"guardians": "Батьки / опікуни",
"home": "домашній телефон",
"impersonation": {
"impersonate": "Маскування",
"impersonating": "Маскуючись як",
......@@ -174,9 +200,9 @@
"invite": "Запрошення",
"logged_in_as": "Вхід виконано як",
"menu_title": "Особи",
"mobile": "Мобільний телефон",
"mobile": "мобільний телефон",
"no_additional_image": "Ця особа не завантажила додаткове зображення",
"no_persons": "Без осіб",
"no_persons": "Нікого немає",
"page_title": "Особа",
"title": "Особа",
"title_plural": "Особи"
......@@ -191,9 +217,9 @@
}
},
"school_term": {
"menu_title": "Навчальний рік",
"menu_title": "Навчальні роки",
"title": "Навчальний рік",
"title_plural": "Навчальний рік"
"title_plural": "Навчальні роки"
},
"service_worker": {
"dismiss": "Відмовитися",
......
import gqlCustomMenu from "../components/app/customMenu.graphql";
import permissionsMixin from "./permissions.js";
/**
* Vue mixin containing menu generation code.
*
* Only used by main App component, but factored out for readability.
*/
const menusMixin = {
mixins: [permissionsMixin],
data() {
return {
footerMenu: null,
permissionNames: [],
sideNavMenu: null,
accountMenu: null,
};
......@@ -35,8 +37,7 @@ const menusMixin = {
}
}
this.permissionNames = permArray;
this.$apollo.queries.whoAmI.refetch();
this.addPermissions(permArray);
},
buildMenu(routes, menuKey) {
let menu = {};
......@@ -99,14 +100,6 @@ const menusMixin = {
return Object.values(menu);
},
checkPermission(permissionName) {
return (
this.whoAmI &&
this.whoAmI.permissions &&
this.whoAmI.permissions.find((p) => p.name === permissionName) &&
this.whoAmI.permissions.find((p) => p.name === permissionName).result
);
},
checkValidators(validators) {
for (const validator of validators) {
if (!validator(this.whoAmI)) {
......@@ -118,14 +111,9 @@ const menusMixin = {
buildMenus() {
this.accountMenu = this.buildMenu(
this.$router.getRoutes(),
"inAccountMenu",
this.whoAmI ? this.whoAmI.permissions : []
);
this.sideNavMenu = this.buildMenu(
this.$router.getRoutes(),
"inMenu",
this.whoAmI ? this.whoAmI.permissions : []
"inAccountMenu"
);
this.sideNavMenu = this.buildMenu(this.$router.getRoutes(), "inMenu");
},
},
apollo: {
......
/**
* Vue mixin containing permission checking code.
*/
const permissionsMixin = {
methods: {
checkPermission(permissionName) {
return (
this.$root.permissions &&
this.$root.permissions.find((p) => p.name === permissionName) &&
this.$root.permissions.find((p) => p.name === permissionName).result
);
},
addPermissions(newPermissionNames) {
const keepPermissionNames = this.$root.permissionNames.filter(
(oldPermName) =>
!newPermissionNames.find((newPermName) => newPermName === oldPermName)
);
this.$root.permissionNames = [
...keepPermissionNames,
...newPermissionNames,
];
},
},
};
export default permissionsMixin;
......@@ -14,7 +14,7 @@ const routesMixin = {
apollo: {
dynamicRoutes: {
query: gqlDynamicRoutes,
pollInterval: 10000,
pollInterval: 30000,
},
},
watch: {
......
......@@ -5,6 +5,7 @@
// aleksisAppImporter is a virtual module defined in Vite config
import { appMessages } from "aleksisAppImporter";
import aleksisMixin from "../mixins/aleksis.js";
import * as langs from "@/vuetify/src/locale";
console.debug("Defining AleksisVue plugin");
const AleksisVue = {};
......@@ -104,6 +105,33 @@ AleksisVue.install = function (Vue) {
document.title = newTitle;
};
/**
* Set the toolbar title visible on the page.
*
* This will automatically add the base title discovered at app loading time.
*
* @param {string} title Specific title to set, or null.
* @param {Object} route Route to discover title from, or null.
*/
Vue.prototype.$setToolBarTitle = function (title, route) {
let newTitle;
if (title) {
newTitle = title;
} else {
if (!route) {
route = this.$route;
}
if (route.meta.toolbarTitle) {
newTitle = this.$t(route.meta.toolbarTitle);
}
}
newTitle = newTitle || Vue.$pageBaseTitle;
console.debug(`Setting toolbar title: ${newTitle}`);
this.$root.toolbarTitle = newTitle;
};
/**
* Load i18n messages from all known AlekSIS apps.
*/
......@@ -115,6 +143,15 @@ AleksisVue.install = function (Vue) {
}
};
/**
* Load vuetifys built-in translations
*/
Vue.prototype.$loadVuetifyMessages = function () {
for (const [locale, messages] of Object.entries(langs)) {
this.$i18n.mergeLocaleMessage(locale, { $vuetify: messages });
}
};
/**
* Invalidate state and force reload from server.
*
......@@ -150,6 +187,7 @@ AleksisVue.install = function (Vue) {
this.$router.afterEach((to, from, next) => {
console.debug("Setting new page title due to route change");
vm.$setPageTitle(null, to);
vm.$setToolBarTitle(null, to);
});
// eslint-disable-next-line no-unused-vars
......