diff --git a/aleksis/core/assets/components/about/About.vue b/aleksis/core/assets/components/about/About.vue new file mode 100644 index 0000000000000000000000000000000000000000..4a1a30fdf621998358774ce8cafe8fcb25069352 --- /dev/null +++ b/aleksis/core/assets/components/about/About.vue @@ -0,0 +1,62 @@ +<template> + <div class="mt-4 mb-4"> + <v-row align="stretch"> + <v-col class="d-flex align-stretch"> + <v-card class="d-flex flex-column"> + <v-card-title>About AlekSIS</v-card-title> + <v-card-text> + <p> + This platform is powered by AlekSIS®, a web-based school information system (SIS) which can be used + to manage and/or publish organisational artifacts of educational institutions. AlekSIS is free software + and can be used by anyone. + </p> + <p> + AlekSIS® is a registered trademark of the AlekSIS open source project, represented by Teckids e.V. + </p> + </v-card-text> + <v-spacer></v-spacer> + <v-card-actions> + <v-btn text color="primary" href="https://aleksis.org/">Website of AlekSIS</v-btn> + <v-btn text color="primary" href="https://edugit.org/AlekSIS/">Source code</v-btn> + </v-card-actions> + + </v-card> + </v-col> + <v-col class="d-flex align-stretch"> + <v-card class="d-flex flex-column"> + <v-card-title>Licence information</v-card-title> + <v-card-text> + <p> + The core and the official apps of AlekSIS are licenced under the EUPL, version 1.2 or later. For licence + information from third-party apps, if installed, refer to the respective components below. The + licences are marked like this: + </p> + <p> + <v-chip color="green" text-color="white">Free/Open Source Licence</v-chip> + <v-chip color="green" text-color="white">Other Licence</v-chip> + </p> + </v-card-text> + <v-spacer></v-spacer> + <v-card-actions> + <v-btn text color="primary" href="https://eupl.eu">Full licence text</v-btn> + <v-btn text color="primary" + href="https://joinup.ec.europa.eu/collection/eupl/guidelines-users-and-developers"> + More information about the EUPL + </v-btn> + </v-card-actions> + </v-card> + </v-col> + </v-row> + <installed-apps-list/> + </div> +</template> + +<script> +import InstalledAppsList from "./InstalledAppsList.vue"; + +export default { + name: "About", + components: {VCheckbox, InstalledAppsList} +} +</script> + diff --git a/aleksis/core/assets/components/about/InstalledAppCard.vue b/aleksis/core/assets/components/about/InstalledAppCard.vue new file mode 100644 index 0000000000000000000000000000000000000000..ac97ac293df28b22ed6fa358777030dbd36ce76b --- /dev/null +++ b/aleksis/core/assets/components/about/InstalledAppCard.vue @@ -0,0 +1,63 @@ +<template> + <v-col cols="6" class="d-flex align-stretch"> + <v-card :id="app.name" class="d-flex flex-column flex-grow-1"> + <v-card-title> + {{ app.verboseName }} + <v-chip v-if="app.licence.flags.isFsfLibre" color="green" text-color="white" class="ml-4">Free Software</v-chip> + <v-chip v-else-if="app.licence.flags.isOsiApproved" color="green" text-color="white" class="ml-4">Open Source + </v-chip> + </v-card-title> + + <v-card-subtitle> + {{ app.version }} + </v-card-subtitle> + + <v-card-text> + <p v-if="app.copyrights.length !== 0"> + <span v-for="(copyright, index) in app.copyrights" :key="index"> + Copyright © {{ copyright.years }} + <a :href="'mailto:' + copyright.email">{{ copyright.name }}</a> + <br/> + </span> + </p> + + <p v-if="app.licence"> + This app is licenced under {{ app.licence.verboseName }}. + </p> + <div v-for="licence in app.licence.licences" class="mb-2" :key="licence.name"> + <v-chip v-if="licence.isOsiApproved || licence.isFsfLibre" color="green" text-color="green" outlined + :href="licence.url"> + {{ licence.name }} + </v-chip> + <v-chip v-else color="orange" text-color="orange" outlined :href="licence.url"> + {{ licence.name }} + </v-chip> + </div> + </v-card-text> + + <v-spacer></v-spacer> + + <v-card-actions v-if="app.urls.length !== 0"> + <v-btn + v-for="url in app.urls" + color="primary" + text + :href="url.url" + > + {{ url.name }} + </v-btn> + </v-card-actions> + </v-card> + </v-col> +</template> +<script> +export default { + name: "InstalledAppCard", + props: { + app: { + type: Object, + required: true, + }, + }, +} +</script> diff --git a/aleksis/core/assets/components/about/InstalledAppsList.vue b/aleksis/core/assets/components/about/InstalledAppsList.vue new file mode 100644 index 0000000000000000000000000000000000000000..b49f2b3198ea2b40183528df94dac6c47bcf8f85 --- /dev/null +++ b/aleksis/core/assets/components/about/InstalledAppsList.vue @@ -0,0 +1,25 @@ +<template> + <ApolloQuery + :query="require('./installedApps.graphql')" + > + <template v-slot="{ result: { error, data }, isLoading }"> + <v-row> + <InstalledAppCard + v-for="app in data.installedApps" + :key="app.name" + :app="app" + /> + </v-row> + </template> + </ApolloQuery> +</template> + +<script> +import InstalledAppCard from "./InstalledAppCard.vue"; + +export default { + name: "InstalledAppsList", + components: {InstalledAppCard} +} +</script> + diff --git a/aleksis/core/assets/components/about/installedApps.graphql b/aleksis/core/assets/components/about/installedApps.graphql new file mode 100644 index 0000000000000000000000000000000000000000..01ceaf99eb8d79d44ee7014d9f582d7dfeb54ace --- /dev/null +++ b/aleksis/core/assets/components/about/installedApps.graphql @@ -0,0 +1,29 @@ +{ + installedApps { + name + verboseName + version + copyrights { + years + name + email + } + licence { + verboseName + flags { + isFsfLibre + isOsiApproved + } + licences { + isFsfLibre + isOsiApproved + name + url + } + } + urls { + name + url + } + } +} diff --git a/aleksis/core/assets/index.js b/aleksis/core/assets/index.js index 94f4131baf7181ea2a299c518f5fe942b850926d..03c33d7313dc3a51f237ac0cea45d0e220fae1d6 100644 --- a/aleksis/core/assets/index.js +++ b/aleksis/core/assets/index.js @@ -2,3 +2,7 @@ import '@mdi/font/css/materialdesignicons.css' import "./util" import "./app" + +import About from "./components/about/About.vue"; + +window.router.addRoute({ path: "/about", component: About }); diff --git a/aleksis/core/schema.py b/aleksis/core/schema.py index 7e637aa20612a5d486732d85201c05febcfeed3b..b7efdb3e5115262c986e97e42046d5a187293b63 100644 --- a/aleksis/core/schema.py +++ b/aleksis/core/schema.py @@ -1,9 +1,12 @@ import graphene +from django.apps import apps +from graphene import ObjectType from graphene_django import DjangoObjectType from graphene_django.forms.mutation import DjangoModelFormMutation from .forms import PersonForm from .models import Group, Notification, Person +from .util.apps import AppConfig from .util.core_helpers import get_app_module, get_app_packages, has_person @@ -27,6 +30,41 @@ class GroupType(DjangoObjectType): model = Group +class AppURLType(ObjectType): + name = graphene.String(required=True) + url = graphene.String(required=True) + +class CopyrightType(ObjectType): + years = graphene.String(required=True) + name = graphene.String(required=True) + email = graphene.String(required=True) + +class LicenceFlagsType(ObjectType): + isFsfLibre = graphene.Boolean(required=True) + isOsiApproved = graphene.Boolean(required=True) + +class SubLicenceType(ObjectType): + isDeprecatedLicenseId = graphene.Boolean(default_value=False) + isFsfLibre = graphene.Boolean(default_value=False) + isOsiApproved = graphene.Boolean(default_value=False) + licenseId = graphene.String(required=True) + name = graphene.String(required=True) + referenceNumber = graphene.Int(default_value=-1) + url = graphene.String() + +class LicenceType(ObjectType): + verbose_name = graphene.String(required=True) + flags = graphene.Field(LicenceFlagsType, required=True) + licences = graphene.List(SubLicenceType) + +class AppType(ObjectType): + copyrights = graphene.List(CopyrightType) + licence = graphene.Field(LicenceType) + name = graphene.String(required=True) + verbose_name = graphene.String(required=True) + version = graphene.String() + urls = graphene.List(AppURLType) + class PersonMutation(DjangoModelFormMutation): person = graphene.Field(PersonType) @@ -59,6 +97,8 @@ class Query(graphene.ObjectType): person_by_id = graphene.Field(PersonType, id=graphene.ID()) who_am_i = graphene.Field(PersonType) + installed_apps = graphene.List(AppType) + def resolve_notifications(root, info, **kwargs): # FIXME do permission stuff return Notification.objects.all() @@ -76,6 +116,8 @@ class Query(graphene.ObjectType): else: return None + def resolve_installed_apps(root, info, **kwargs): + return [app.get_dict() for app in apps.get_app_configs() if isinstance(a, AppConfig)] class Mutation(graphene.ObjectType): update_person = PersonMutation.Field() diff --git a/aleksis/core/settings.py b/aleksis/core/settings.py index 3313076e8d2649bbbc0fa885e59ea9c5d84dcf62..bd65cc85d48fc653a315326cf6f097e3cb4c4dab 100644 --- a/aleksis/core/settings.py +++ b/aleksis/core/settings.py @@ -178,7 +178,7 @@ MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", - "debug_toolbar.middleware.DebugToolbarMiddleware", + # "debug_toolbar.middleware.DebugToolbarMiddleware", "django.middleware.locale.LocaleMiddleware", "django.middleware.http.ConditionalGetMiddleware", "django.contrib.sites.middleware.CurrentSiteMiddleware", diff --git a/aleksis/core/templates/core/pages/about.html b/aleksis/core/templates/core/pages/about.html index 6c44d90cbfe51df457f2bd09f600dfb676b7b97d..3489a50e2395309b078a0c1b2fa053b6a728fac6 100644 --- a/aleksis/core/templates/core/pages/about.html +++ b/aleksis/core/templates/core/pages/about.html @@ -1,5 +1,5 @@ {# -*- engine:django -*- #} -{% extends "core/base.html" %} +{% extends "core/vue_base.html" %} {% load i18n %} @@ -7,121 +7,5 @@ {% block page_title %}{% blocktrans %}AlekSIS® – The Free School Information System{% endblocktrans %}{% endblock %} {% block content %} - - <div class="row"> - <div class="col s12"> - <div class="card"> - <div class="card-content"> - <span class="card-title">{% blocktrans %}About AlekSIS{% endblocktrans %}</span> - <p> - {% blocktrans %} - This platform is powered by AlekSIS®, a web-based school information system (SIS) which can be used - to manage and/or publish organisational artifacts of educational institutions. AlekSIS is free software and - can be used by anyone. - {% endblocktrans %} - </p> - <p> - {% blocktrans %} - AlekSIS® is a registered trademark of the AlekSIS open source project, represented by Teckids e.V. - {% endblocktrans %} - </p> - </div> - <div class="card-action"> - <a class="" href="https://aleksis.org/">{% trans "Website of AlekSIS" %}</a> - <a class="" href="https://edugit.org/AlekSIS/">{% trans "Source code" %}</a> - </div> - </div> - </div> - </div> - <div class="row"> - <div class="col s12"> - <div class="card"> - <div class="card-content"> - <span class="card-title">{% trans "Licence information" %}</span> - <p> - {% blocktrans %} - The core and the official apps of AlekSIS are licenced under the EUPL, version 1.2 or later. For licence - information from third-party apps, if installed, refer to the respective components below. The - licences are marked like this: - {% endblocktrans %} - </p> - <br/> - <p> - <span class="chip green white-text">{% trans "Free/Open Source Licence" %}</span> - <span class="chip orange white-text">{% trans "Other Licence" %}</span> - </p> - </div> - <div class="card-action"> - <a href="https://eupl.eu">{% trans "Full licence text" %}</a> - <a href="https://joinup.ec.europa.eu/collection/eupl/guidelines-users-and-developers">{% trans "More information about the EUPL" %}</a> - </div> - </div> - </div> - </div> - - <div class="row"> - {% for app_config in app_configs %} - <div class="col s12 m12 l6"> - <div class="card " id="{{ app_config.name }}"> - <div class="card-content"> - {% if app_config.get_licence.1.isFsfLibre %} - <span class="chip green white-text right">Free Software</span> - {% elif app_config.get_licence.1.isOsiApproved %} - <span class="chip green white-text right">Open Source</span> - {% endif %} - - <span class="card-title">{{ app_config.get_name }} <small>{{ app_config.get_version }}</small></span> - - {% if app_config.get_copyright %} - <p> - {% for holder in app_config.get_copyright %} - Copyright © {{ holder.0 }} - - {% if holder.2 %} - <a href="mailto:{{ holder.2 }}">{{ holder.1 }}</a> - {% else %} - {{ holder.1 }} - {% endif %} - - <br/> - {% endfor %} - </p> - <br/> - {% endif %} - - {% if app_config.get_licence %} - {% with licence=app_config.get_licence %} - <p> - {% blocktrans with licence=licence.0 %} - This app is licenced under {{ licence }}. - {% endblocktrans %} - </p> - <br/> - <p> - {% for l in licence.2 %} - <a class="chip white-text {% if l.isOsiApproved or l.isFsfLibre %}green{% else %}orange{% endif %}" - href="{{ l.url }}"> - {{ l.name }} - </a> - {% endfor %} - </p> - {% endwith %} - {% endif %} - </div> - {% if app_config.get_urls %} - <div class="card-action"> - {% for url_name, url in app_config.get_urls.items %} - <a href="{{ url }}">{{ url_name }}</a> - {% endfor %} - </div> - {% endif %} - </div> - </div> - {% if forloop.counter|divisibleby:2 %} - </div> - <div class="row"> - {% endif %} - {% endfor %} - </div> - + <router-view></router-view> {% endblock %} diff --git a/aleksis/core/util/apps.py b/aleksis/core/util/apps.py index 5b3898dd835506ade932ebccf30e4cf0de15486e..4fdd4740015a22df3fdce4bbe6b3a835d2b5a2e0 100644 --- a/aleksis/core/util/apps.py +++ b/aleksis/core/util/apps.py @@ -35,6 +35,10 @@ class AppConfig(django.apps.AppConfig): # Getting an app ready means it should look at its config once self.preference_updated(self) + def get_dict(self): + return {"name": self.name, "verbose_name": self.get_name(), "version": self.get_version(), "copyrights": self.get_copyright_dicts(), "licence": self.get_licence_dict(), "urls": self.get_urls_dict()} + + def get_distribution_name(self): """Get distribution name of application package.""" if hasattr(self, "dist_name"): @@ -129,11 +133,26 @@ class AppConfig(django.apps.AppConfig): return ("Unknown", [default_dict]) @classmethod + def get_licence_dict(cls): + """Get licence information of application package.""" + licence = cls.get_licence() + return { + "verbose_name": licence[0], + "flags": licence[1], + "licences": licence[2], + } + @classmethod def get_urls(cls): """Get list of URLs for this application package.""" return getattr(cls, "urls", {}) # TODO Try getting from distribution if not set + @classmethod + def get_urls_dict(cls): + """Get list of URLs for this application package.""" + urls = cls.get_urls() + return [{"name": key, "url": value} for key, value in urls.items()] + @classmethod def get_copyright(cls) -> Sequence[tuple[str, str, str]]: """Get copyright information tuples for application package.""" @@ -155,6 +174,12 @@ class AppConfig(django.apps.AppConfig): return copyrights_processed # TODO Try getting from distribution if not set + @classmethod + def get_copyright_dicts(cls): + """Get copyright information dictionaries for application package.""" + infos = cls.get_copyright() + return [{"years": info[0], "name": info[1], "email": info[2] } for info in infos] + def preference_updated( self, sender: Any,