diff --git a/api/config/settings/common.py b/api/config/settings/common.py index 8f2e00ade7972bf4d2f6e836e48299d6d48b3767..455a5822e6722629c9226d011f5c1adacfe73d1a 100644 --- a/api/config/settings/common.py +++ b/api/config/settings/common.py @@ -46,7 +46,7 @@ logging.config.dictConfig( }, "loggers": { "funkwhale_api": { - "level": LOGLEVEL, + "level": logging.getLevelName("LOGLEVEL"), "handlers": ["console"], # required to avoid double logging with root logger "propagate": False, diff --git a/api/funkwhale_api/manage/filters.py b/api/funkwhale_api/manage/filters.py index 984b83133734e81a87194de73016a4f0ca5ee11f..9e20a6af4ffb3b3d2f1874b64db61072dc7aeeda 100644 --- a/api/funkwhale_api/manage/filters.py +++ b/api/funkwhale_api/manage/filters.py @@ -237,10 +237,20 @@ class ManageUploadFilterSet(filters.FilterSet): class ManageDomainFilterSet(filters.FilterSet): q = fields.SearchFilter(search_fields=["name"]) + allowed = filters.BooleanFilter() class Meta: model = federation_models.Domain - fields = ["name"] + fields = ["name", "allowed"] + + def filter_allowed(self, qs, value): + """ + If value=false, we want to include object with value=null as well + """ + if value: + return qs.filter(allowed=True) + else: + return qs.filter(allowed__in=[False, None]) class ManageActorFilterSet(filters.FilterSet): diff --git a/api/funkwhale_api/moderation/dynamic_preferences_registry.py b/api/funkwhale_api/moderation/dynamic_preferences_registry.py index 8d8237cbb4231728021316eacf4eacad00436b14..04a732f4d4863a97d52e1401d0d921cc042c9eeb 100644 --- a/api/funkwhale_api/moderation/dynamic_preferences_registry.py +++ b/api/funkwhale_api/moderation/dynamic_preferences_registry.py @@ -19,8 +19,8 @@ class AllowListPublic(types.BooleanPreference): name = "allow_list_public" verbose_name = "Publish your allowed-domains list" help_text = ( - "If enabled, everyone will be able to retrieve the list of domains you allowed. ", + "If enabled, everyone will be able to retrieve the list of domains you allowed. " "This is useful on open setups, to help people decide if they want to join your pod, or to " - "make your moderation policy public.", + "make your moderation policy public." ) default = False diff --git a/front/src/components/manage/moderation/DomainsTable.vue b/front/src/components/manage/moderation/DomainsTable.vue index 544d91156f3101236b8395bec133b9248d2d112a..08fff4cc5c208f51fed0894fb423e0e14f136f55 100644 --- a/front/src/components/manage/moderation/DomainsTable.vue +++ b/front/src/components/manage/moderation/DomainsTable.vue @@ -6,6 +6,14 @@ <label><translate translate-context="Content/Search/Input.Label/Noun">Search</translate></label> <input name="search" type="text" v-model="search" :placeholder="labels.searchPlaceholder" /> </div> + <div class="field"> + <label><translate translate-context="Content/Moderation/*/Adjective">Is present on allow-list</translate></label> + <select class="ui dropdown" v-model="allowed"> + <option :value="null"><translate translate-context="*/*/*">All</translate></option> + <option :value="true"><translate translate-context="*/*/*">Yes</translate></option> + <option :value="false"><translate translate-context="*/*/*">No</translate></option> + </select> + </div> <div class="field"> <label><translate translate-context="Content/Search/Dropdown.Label/Noun">Ordering</translate></label> <select class="ui dropdown" v-model="ordering"> @@ -93,7 +101,8 @@ import TranslationsMixin from '@/components/mixins/Translations' export default { mixins: [OrderingMixin, TranslationsMixin], props: { - filters: {type: Object, required: false} + filters: {type: Object, required: false}, + allowListEnabled: {type: Boolean, default: false}, }, components: { Pagination, @@ -108,6 +117,7 @@ export default { page: 1, paginateBy: 50, search: '', + allowed: null, orderingDirection: defaultOrdering.direction || '+', ordering: defaultOrdering.field, orderingOptions: [ @@ -124,12 +134,16 @@ export default { }, methods: { fetchData () { - let params = _.merge({ + let baseFilters = { 'page': this.page, 'page_size': this.paginateBy, 'q': this.search, - 'ordering': this.getOrderingAsString() - }, this.filters) + 'ordering': this.getOrderingAsString(), + } + if (this.allowed !== null) { + baseFilters.allowed = this.allowed + } + let params = _.merge(baseFilters, this.filters) let self = this self.isLoading = true self.checked = [] @@ -179,6 +193,9 @@ export default { page () { this.fetchData() }, + allowed () { + this.fetchData() + }, ordering () { this.fetchData() }, diff --git a/front/src/views/admin/Settings.vue b/front/src/views/admin/Settings.vue index 7102fb311b1b64fa9c63f160d026659ebe06a087..a1d03aab77740435d4636315196ad979482ea295 100644 --- a/front/src/views/admin/Settings.vue +++ b/front/src/views/admin/Settings.vue @@ -82,6 +82,7 @@ export default { let musicLabel = this.$pgettext('*/*/*/Noun', 'Music') let playlistsLabel = this.$pgettext('*/*/*', 'Playlists') let federationLabel = this.$pgettext('Content/Admin/Menu', 'Federation') + let moderationLabel = this.$pgettext('Content/Admin/Menu', 'Moderation') let subsonicLabel = this.$pgettext('Content/Admin/Menu', 'Subsonic') let statisticsLabel = this.$pgettext('Content/Admin/Menu', 'Statistics') let errorLabel = this.$pgettext('Content/Admin/Menu', 'Error reporting') @@ -118,6 +119,14 @@ export default { id: "playlists", settings: ["playlists__max_tracks"] }, + { + label: moderationLabel, + id: "moderation", + settings: [ + "moderation__allow_list_enabled", + "moderation__allow_list_public", + ] + }, { label: federationLabel, id: "federation", diff --git a/front/src/views/admin/moderation/Base.vue b/front/src/views/admin/moderation/Base.vue index 564debf79ae8f7dcc42abe94e073511de237f1ac..d1c3ae29fb8e7fb1a074f2dc65e0b30679e1c5f5 100644 --- a/front/src/views/admin/moderation/Base.vue +++ b/front/src/views/admin/moderation/Base.vue @@ -9,12 +9,31 @@ :to="{name: 'manage.moderation.accounts.list'}"><translate translate-context="*/Moderation/Title">Accounts</translate></router-link> </nav> - <router-view :key="$route.fullPath"></router-view> + <router-view :allow-list-enabled="allowListEnabled" :key="$route.fullPath"></router-view> </div> </template> <script> +import _ from '@/lodash' +import axios from 'axios' + export default { + data () { + return { + allowListEnabled: false + } + }, + created () { + this.fetchNodeInfo() + }, + methods: { + fetchNodeInfo () { + let self = this + axios.get('instance/nodeinfo/2.0/').then(response => { + self.allowListEnabled = _.get(response.data, 'metadata.allowList.enabled', false) + }) + }, + }, computed: { labels() { return { @@ -22,6 +41,6 @@ export default { secondaryMenu: this.$pgettext('Menu/*/Hidden text', "Secondary menu") } } - } + }, } </script> diff --git a/front/src/views/admin/moderation/DomainsDetail.vue b/front/src/views/admin/moderation/DomainsDetail.vue index 575b15f93ca2bbf696b9672cbb38e7535c8b09ab..c30f426bbbca56045d292cffcc92d4784b457328 100644 --- a/front/src/views/admin/moderation/DomainsDetail.vue +++ b/front/src/views/admin/moderation/DomainsDetail.vue @@ -20,6 +20,34 @@ </div> </div> </h2> + <div class="header-buttons"> + <div class="ui icon buttons"> + <a + v-if="$store.state.auth.profile.is_superuser" + class="ui labeled icon button" + :href="$store.getters['instance/absoluteUrl'](`/api/admin/federation/domain/${object.name}`)" + target="_blank" rel="noopener noreferrer"> + <i class="wrench icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">View in Django's admin</translate> + </a> + </div> + <div v-if="allowListEnabled" class="ui icon buttons"> + <button + v-if="object.allowed" + @click.prevent="setAllowList(false)" + :class="['ui', 'labeled', {loading: isLoadingAllowList}, 'icon', 'button']"> + <i class="x icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">Remove from allow-list</translate> + </button> + <button + v-else + @click.prevent="setAllowList(true)" + :class="['ui', 'labeled', {loading: isLoadingAllowList}, 'icon', 'button']"> + <i class="check icon"></i> + <translate translate-context="Content/Moderation/Link/Verb">Add to allow-list</translate> + </button> + </div> + </div> </div> </div> <div class="ui column"> @@ -74,6 +102,15 @@ </h3> <table class="ui very basic table"> <tbody> + <tr v-if="allowListEnabled"> + <td> + <translate translate-context="Content/Moderation/*/Adjective">Is present on allow-list</translate> + </td> + <td> + <translate v-if="object.allowed" translate-context="*/*/*">Yes</translate> + <translate v-else translate-context="*/*/*">No</translate> + </td> + </tr> <tr> <td> <translate translate-context="Content/*/Table.Label">Last checked</translate> @@ -300,7 +337,7 @@ import InstancePolicyForm from "@/components/manage/moderation/InstancePolicyFor import InstancePolicyCard from "@/components/manage/moderation/InstancePolicyCard" export default { - props: ["id"], + props: ["id", "allowListEnabled"], components: { InstancePolicyForm, InstancePolicyCard, @@ -311,6 +348,7 @@ export default { isLoading: true, isLoadingStats: false, isLoadingPolicy: false, + isLoadingAllowList: false, policy: null, object: null, stats: null, @@ -353,6 +391,15 @@ export default { self.isLoadingPolicy = false }) }, + setAllowList(value) { + var self = this + this.isLoadingAllowList = true + let url = `manage/federation/domains/${this.id}/` + axios.patch(url, {allowed: value}).then(response => { + self.object = response.data + self.isLoadingAllowList = false + }) + }, refreshNodeInfo (data) { this.object.nodeinfo = data this.object.nodeinfo_fetch_date = new Date() diff --git a/front/src/views/admin/moderation/DomainsList.vue b/front/src/views/admin/moderation/DomainsList.vue index 3ce8e6afc44a2e653bc5ecb88a6837c3d80181d9..b4ffad7b8ca8b7e4b3d121e83474c806d6d4d0aa 100644 --- a/front/src/views/admin/moderation/DomainsList.vue +++ b/front/src/views/admin/moderation/DomainsList.vue @@ -14,6 +14,10 @@ <label for="domain"><translate translate-context="Content/Moderation/Form.Label/Verb">Add a domain</translate></label> <input type="text" name="domain" id="domain" v-model="domainName"> </div> + <div class="field" v-if="allowListEnabled"> + <input type="checkbox" name="allowed" id="allowed" v-model="domainAllowed"> + <label for="allowed"><translate translate-context="Content/Moderation/Form.Label/Verb">Add to allow-list</translate></label> + </div> <div class="field"> <button :class="['ui', {'loading': isCreating}, 'green', 'button']" type="submit" :disabled="isCreating"> <label for="domain"><translate translate-context="Content/Moderation/Button/Verb">Add</translate></label> @@ -22,7 +26,7 @@ </div> </form> <div class="ui clearing hidden divider"></div> - <domains-table></domains-table> + <domains-table :allow-list-enabled="allowListEnabled"></domains-table> </section> </main> </template> @@ -32,12 +36,14 @@ import axios from 'axios' import DomainsTable from "@/components/manage/moderation/DomainsTable" export default { + props: ['allowListEnabled'], components: { DomainsTable }, data () { return { domainName: '', + domainAllowed: this.allowListEnabled ? true : null, isCreating: false, errors: [] } @@ -54,7 +60,7 @@ export default { let self = this this.isCreating = true this.errors = [] - axios.post('manage/federation/domains/', {name: this.domainName}).then((response) => { + axios.post('manage/federation/domains/', {name: this.domainName, allowed: this.domainAllowed}).then((response) => { this.isCreating = false this.$router.push({ name: "manage.moderation.domains.detail",