Merge branch '192-nodeinfo' into 'develop'
Resolve "Use nodeinfo schema for instance statistics" Closes #192 See merge request funkwhale/funkwhale!187
This commit is contained in:
commit
2ef8723485
|
@ -85,13 +85,31 @@ class InstanceActorViewSet(FederationMixin, viewsets.GenericViewSet):
|
||||||
return response.Response({}, status=200)
|
return response.Response({}, status=200)
|
||||||
|
|
||||||
|
|
||||||
class WellKnownViewSet(FederationMixin, viewsets.GenericViewSet):
|
class WellKnownViewSet(viewsets.GenericViewSet):
|
||||||
authentication_classes = []
|
authentication_classes = []
|
||||||
permission_classes = []
|
permission_classes = []
|
||||||
renderer_classes = [renderers.WebfingerRenderer]
|
renderer_classes = [renderers.WebfingerRenderer]
|
||||||
|
|
||||||
|
@list_route(methods=['get'])
|
||||||
|
def nodeinfo(self, request, *args, **kwargs):
|
||||||
|
if not preferences.get('instance__nodeinfo_enabled'):
|
||||||
|
return HttpResponse(status=404)
|
||||||
|
data = {
|
||||||
|
'links': [
|
||||||
|
{
|
||||||
|
'rel': 'http://nodeinfo.diaspora.software/ns/schema/2.0',
|
||||||
|
'href': utils.full_url(
|
||||||
|
reverse('api:v1:instance:nodeinfo-2.0')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return response.Response(data)
|
||||||
|
|
||||||
@list_route(methods=['get'])
|
@list_route(methods=['get'])
|
||||||
def webfinger(self, request, *args, **kwargs):
|
def webfinger(self, request, *args, **kwargs):
|
||||||
|
if not preferences.get('federation__enabled'):
|
||||||
|
return HttpResponse(status=405)
|
||||||
try:
|
try:
|
||||||
resource_type, resource = webfinger.clean_resource(
|
resource_type, resource = webfinger.clean_resource(
|
||||||
request.GET['resource'])
|
request.GET['resource'])
|
||||||
|
|
|
@ -68,3 +68,31 @@ class RavenEnabled(types.BooleanPreference):
|
||||||
'Wether error reporting to a Sentry instance using raven is enabled'
|
'Wether error reporting to a Sentry instance using raven is enabled'
|
||||||
' for front-end errors'
|
' for front-end errors'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@global_preferences_registry.register
|
||||||
|
class InstanceNodeinfoEnabled(types.BooleanPreference):
|
||||||
|
show_in_api = False
|
||||||
|
section = instance
|
||||||
|
name = 'nodeinfo_enabled'
|
||||||
|
default = True
|
||||||
|
verbose_name = 'Enable nodeinfo endpoint'
|
||||||
|
help_text = (
|
||||||
|
'This endpoint is needed for your about page to work.'
|
||||||
|
'It\'s also helpful for the various monitoring '
|
||||||
|
'tools that map and analyzize the fediverse, '
|
||||||
|
'but you can disable it completely if needed.'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@global_preferences_registry.register
|
||||||
|
class InstanceNodeinfoStatsEnabled(types.BooleanPreference):
|
||||||
|
show_in_api = False
|
||||||
|
section = instance
|
||||||
|
name = 'nodeinfo_stats_enabled'
|
||||||
|
default = True
|
||||||
|
verbose_name = 'Enable usage and library stats in nodeinfo endpoint'
|
||||||
|
help_text = (
|
||||||
|
'Disable this f you don\'t want to share usage and library statistics'
|
||||||
|
'in the nodeinfo endpoint but don\'t want to disable it completely.'
|
||||||
|
)
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
import memoize.djangocache
|
||||||
|
|
||||||
|
import funkwhale_api
|
||||||
|
from funkwhale_api.common import preferences
|
||||||
|
|
||||||
|
from . import stats
|
||||||
|
|
||||||
|
|
||||||
|
store = memoize.djangocache.Cache('default')
|
||||||
|
memo = memoize.Memoizer(store, namespace='instance:stats')
|
||||||
|
|
||||||
|
|
||||||
|
def get():
|
||||||
|
share_stats = preferences.get('instance__nodeinfo_stats_enabled')
|
||||||
|
data = {
|
||||||
|
'version': '2.0',
|
||||||
|
'software': {
|
||||||
|
'name': 'funkwhale',
|
||||||
|
'version': funkwhale_api.__version__
|
||||||
|
},
|
||||||
|
'protocols': ['activitypub'],
|
||||||
|
'services': {
|
||||||
|
'inbound': [],
|
||||||
|
'outbound': []
|
||||||
|
},
|
||||||
|
'openRegistrations': preferences.get('users__registration_enabled'),
|
||||||
|
'usage': {
|
||||||
|
'users': {
|
||||||
|
'total': 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'metadata': {
|
||||||
|
'shortDescription': preferences.get('instance__short_description'),
|
||||||
|
'longDescription': preferences.get('instance__long_description'),
|
||||||
|
'nodeName': preferences.get('instance__name'),
|
||||||
|
'library': {
|
||||||
|
'federationEnabled': preferences.get('federation__enabled'),
|
||||||
|
'federationNeedsApproval': preferences.get('federation__music_needs_approval'),
|
||||||
|
'anonymousCanListen': preferences.get('common__api_authentication_required'),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if share_stats:
|
||||||
|
getter = memo(
|
||||||
|
lambda: stats.get(),
|
||||||
|
max_age=600
|
||||||
|
)
|
||||||
|
statistics = getter()
|
||||||
|
data['usage']['users']['total'] = statistics['users']
|
||||||
|
data['metadata']['library']['tracks'] = {
|
||||||
|
'total': statistics['tracks'],
|
||||||
|
}
|
||||||
|
data['metadata']['library']['artists'] = {
|
||||||
|
'total': statistics['artists'],
|
||||||
|
}
|
||||||
|
data['metadata']['library']['albums'] = {
|
||||||
|
'total': statistics['albums'],
|
||||||
|
}
|
||||||
|
data['metadata']['library']['music'] = {
|
||||||
|
'hours': statistics['music_duration']
|
||||||
|
}
|
||||||
|
|
||||||
|
data['metadata']['usage'] = {
|
||||||
|
'favorites': {
|
||||||
|
'tracks': {
|
||||||
|
'total': statistics['track_favorites'],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'listenings': {
|
||||||
|
'total': statistics['listenings']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data
|
|
@ -1,11 +1,9 @@
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
from django.views.decorators.cache import cache_page
|
|
||||||
|
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
url(r'^nodeinfo/2.0/$', views.NodeInfo.as_view(), name='nodeinfo-2.0'),
|
||||||
url(r'^settings/$', views.InstanceSettings.as_view(), name='settings'),
|
url(r'^settings/$', views.InstanceSettings.as_view(), name='settings'),
|
||||||
url(r'^stats/$',
|
|
||||||
cache_page(60 * 5)(views.InstanceStats.as_view()), name='stats'),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -4,9 +4,17 @@ from rest_framework.response import Response
|
||||||
from dynamic_preferences.api import serializers
|
from dynamic_preferences.api import serializers
|
||||||
from dynamic_preferences.registries import global_preferences_registry
|
from dynamic_preferences.registries import global_preferences_registry
|
||||||
|
|
||||||
|
from funkwhale_api.common import preferences
|
||||||
|
|
||||||
|
from . import nodeinfo
|
||||||
from . import stats
|
from . import stats
|
||||||
|
|
||||||
|
|
||||||
|
NODEINFO_2_CONTENT_TYPE = (
|
||||||
|
'application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8' # noqa
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class InstanceSettings(views.APIView):
|
class InstanceSettings(views.APIView):
|
||||||
permission_classes = []
|
permission_classes = []
|
||||||
authentication_classes = []
|
authentication_classes = []
|
||||||
|
@ -27,10 +35,13 @@ class InstanceSettings(views.APIView):
|
||||||
return Response(data, status=200)
|
return Response(data, status=200)
|
||||||
|
|
||||||
|
|
||||||
class InstanceStats(views.APIView):
|
class NodeInfo(views.APIView):
|
||||||
permission_classes = []
|
permission_classes = []
|
||||||
authentication_classes = []
|
authentication_classes = []
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
data = stats.get()
|
if not preferences.get('instance__nodeinfo_enabled'):
|
||||||
return Response(data, status=200)
|
return Response(status=404)
|
||||||
|
data = nodeinfo.get()
|
||||||
|
return Response(
|
||||||
|
data, status=200, content_type=NODEINFO_2_CONTENT_TYPE)
|
||||||
|
|
|
@ -70,6 +70,32 @@ def test_wellknown_webfinger_system(
|
||||||
assert response.data == serializer.data
|
assert response.data == serializer.data
|
||||||
|
|
||||||
|
|
||||||
|
def test_wellknown_nodeinfo(db, preferences, api_client, settings):
|
||||||
|
expected = {
|
||||||
|
'links': [
|
||||||
|
{
|
||||||
|
'rel': 'http://nodeinfo.diaspora.software/ns/schema/2.0',
|
||||||
|
'href': '{}{}'.format(
|
||||||
|
settings.FUNKWHALE_URL,
|
||||||
|
reverse('api:v1:instance:nodeinfo-2.0')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
url = reverse('federation:well-known-nodeinfo')
|
||||||
|
response = api_client.get(url)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response['Content-Type'] == 'application/jrd+json'
|
||||||
|
assert response.data == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_wellknown_nodeinfo_disabled(db, preferences, api_client):
|
||||||
|
preferences['instance__nodeinfo_enabled'] = False
|
||||||
|
url = reverse('federation:well-known-nodeinfo')
|
||||||
|
response = api_client.get(url)
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
def test_audio_file_list_requires_authenticated_actor(
|
def test_audio_file_list_requires_authenticated_actor(
|
||||||
db, preferences, api_client):
|
db, preferences, api_client):
|
||||||
preferences['federation__music_needs_approval'] = True
|
preferences['federation__music_needs_approval'] = True
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
import funkwhale_api
|
||||||
|
|
||||||
|
from funkwhale_api.instance import nodeinfo
|
||||||
|
|
||||||
|
|
||||||
|
def test_nodeinfo_dump(preferences, mocker):
|
||||||
|
preferences['instance__nodeinfo_stats_enabled'] = True
|
||||||
|
stats = {
|
||||||
|
'users': 1,
|
||||||
|
'tracks': 2,
|
||||||
|
'albums': 3,
|
||||||
|
'artists': 4,
|
||||||
|
'track_favorites': 5,
|
||||||
|
'music_duration': 6,
|
||||||
|
'listenings': 7,
|
||||||
|
}
|
||||||
|
mocker.patch('funkwhale_api.instance.stats.get', return_value=stats)
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
'version': '2.0',
|
||||||
|
'software': {
|
||||||
|
'name': 'funkwhale',
|
||||||
|
'version': funkwhale_api.__version__
|
||||||
|
},
|
||||||
|
'protocols': ['activitypub'],
|
||||||
|
'services': {
|
||||||
|
'inbound': [],
|
||||||
|
'outbound': []
|
||||||
|
},
|
||||||
|
'openRegistrations': preferences['users__registration_enabled'],
|
||||||
|
'usage': {
|
||||||
|
'users': {
|
||||||
|
'total': stats['users'],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'metadata': {
|
||||||
|
'shortDescription': preferences['instance__short_description'],
|
||||||
|
'longDescription': preferences['instance__long_description'],
|
||||||
|
'nodeName': preferences['instance__name'],
|
||||||
|
'library': {
|
||||||
|
'federationEnabled': preferences['federation__enabled'],
|
||||||
|
'federationNeedsApproval': preferences['federation__music_needs_approval'],
|
||||||
|
'anonymousCanListen': preferences['common__api_authentication_required'],
|
||||||
|
'tracks': {
|
||||||
|
'total': stats['tracks'],
|
||||||
|
},
|
||||||
|
'artists': {
|
||||||
|
'total': stats['artists'],
|
||||||
|
},
|
||||||
|
'albums': {
|
||||||
|
'total': stats['albums'],
|
||||||
|
},
|
||||||
|
'music': {
|
||||||
|
'hours': stats['music_duration']
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'usage': {
|
||||||
|
'favorites': {
|
||||||
|
'tracks': {
|
||||||
|
'total': stats['track_favorites'],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'listenings': {
|
||||||
|
'total': stats['listenings']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert nodeinfo.get() == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_nodeinfo_dump_stats_disabled(preferences, mocker):
|
||||||
|
preferences['instance__nodeinfo_stats_enabled'] = False
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
'version': '2.0',
|
||||||
|
'software': {
|
||||||
|
'name': 'funkwhale',
|
||||||
|
'version': funkwhale_api.__version__
|
||||||
|
},
|
||||||
|
'protocols': ['activitypub'],
|
||||||
|
'services': {
|
||||||
|
'inbound': [],
|
||||||
|
'outbound': []
|
||||||
|
},
|
||||||
|
'openRegistrations': preferences['users__registration_enabled'],
|
||||||
|
'usage': {
|
||||||
|
'users': {
|
||||||
|
'total': 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'metadata': {
|
||||||
|
'shortDescription': preferences['instance__short_description'],
|
||||||
|
'longDescription': preferences['instance__long_description'],
|
||||||
|
'nodeName': preferences['instance__name'],
|
||||||
|
'library': {
|
||||||
|
'federationEnabled': preferences['federation__enabled'],
|
||||||
|
'federationNeedsApproval': preferences['federation__music_needs_approval'],
|
||||||
|
'anonymousCanListen': preferences['common__api_authentication_required'],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert nodeinfo.get() == expected
|
|
@ -3,16 +3,6 @@ from django.urls import reverse
|
||||||
from funkwhale_api.instance import stats
|
from funkwhale_api.instance import stats
|
||||||
|
|
||||||
|
|
||||||
def test_can_get_stats_via_api(db, api_client, mocker):
|
|
||||||
stats = {
|
|
||||||
'foo': 'bar'
|
|
||||||
}
|
|
||||||
mocker.patch('funkwhale_api.instance.stats.get', return_value=stats)
|
|
||||||
url = reverse('api:v1:instance:stats')
|
|
||||||
response = api_client.get(url)
|
|
||||||
assert response.data == stats
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_users(mocker):
|
def test_get_users(mocker):
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
'funkwhale_api.users.models.User.objects.count', return_value=42)
|
'funkwhale_api.users.models.User.objects.count', return_value=42)
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
|
||||||
|
def test_nodeinfo_endpoint(db, api_client, mocker):
|
||||||
|
payload = {
|
||||||
|
'test': 'test'
|
||||||
|
}
|
||||||
|
mocked_nodeinfo = mocker.patch(
|
||||||
|
'funkwhale_api.instance.nodeinfo.get', return_value=payload)
|
||||||
|
url = reverse('api:v1:instance:nodeinfo-2.0')
|
||||||
|
response = api_client.get(url)
|
||||||
|
ct = 'application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8' # noqa
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert response['Content-Type'] == ct
|
||||||
|
assert response.data == payload
|
||||||
|
|
||||||
|
|
||||||
|
def test_nodeinfo_endpoint_disabled(db, api_client, preferences):
|
||||||
|
preferences['instance__nodeinfo_enabled'] = False
|
||||||
|
url = reverse('api:v1:instance:nodeinfo-2.0')
|
||||||
|
response = api_client.get(url)
|
||||||
|
|
||||||
|
assert response.status_code == 404
|
|
@ -0,0 +1,76 @@
|
||||||
|
Use nodeinfo standard for publishing instance information (#192)
|
||||||
|
|
||||||
|
Nodeinfo standard for instance information and stats
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
The ``/api/v1/instance/stats/`` endpoint which was used to display
|
||||||
|
instance data in the about page is removed in favor of the new
|
||||||
|
``/api/v1/instance/nodeinfo/2.0/`` endpoint.
|
||||||
|
|
||||||
|
In earlier version, we where using a custom endpoint and format for
|
||||||
|
our instance information and statistics. While this was working,
|
||||||
|
this was not compatible with anything else on the fediverse.
|
||||||
|
|
||||||
|
We now offer a nodeinfo 2.0 endpoint which provides, in a single place,
|
||||||
|
all the instance information such as library and user activity statistics,
|
||||||
|
public instance settings (description, registration and federation status, etc.).
|
||||||
|
|
||||||
|
We offer two settings to manage nodeinfo in your Funkwhale instance:
|
||||||
|
|
||||||
|
1. One setting to completely disable nodeinfo, but this is not recommended
|
||||||
|
as the exposed data may be needed to make some parts of the front-end
|
||||||
|
work (especially the about page).
|
||||||
|
2. One setting to disable only usage and library statistics in the nodeinfo
|
||||||
|
endpoint. This is useful if you want the nodeinfo endpoint to work,
|
||||||
|
but don't feel comfortable sharing aggregated statistics about your library
|
||||||
|
and user activity.
|
||||||
|
|
||||||
|
To make your instance fully compatible with the nodeinfo protocol, you need to
|
||||||
|
to edit your nginx configuration file:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
# before
|
||||||
|
...
|
||||||
|
location /.well-known/webfinger {
|
||||||
|
include /etc/nginx/funkwhale_proxy.conf;
|
||||||
|
proxy_pass http://funkwhale-api/.well-known/webfinger;
|
||||||
|
}
|
||||||
|
...
|
||||||
|
|
||||||
|
# after
|
||||||
|
...
|
||||||
|
location /.well-known/ {
|
||||||
|
include /etc/nginx/funkwhale_proxy.conf;
|
||||||
|
proxy_pass http://funkwhale-api/.well-known/;
|
||||||
|
}
|
||||||
|
...
|
||||||
|
|
||||||
|
You can do the same if you use apache:
|
||||||
|
|
||||||
|
.. code-block::
|
||||||
|
|
||||||
|
# before
|
||||||
|
...
|
||||||
|
<Location "/.well-known/webfinger">
|
||||||
|
ProxyPass ${funkwhale-api}/.well-known/webfinger
|
||||||
|
ProxyPassReverse ${funkwhale-api}/.well-known/webfinger
|
||||||
|
</Location>
|
||||||
|
...
|
||||||
|
|
||||||
|
# after
|
||||||
|
...
|
||||||
|
<Location "/.well-known/">
|
||||||
|
ProxyPass ${funkwhale-api}/.well-known/
|
||||||
|
ProxyPassReverse ${funkwhale-api}/.well-known/
|
||||||
|
</Location>
|
||||||
|
...
|
||||||
|
|
||||||
|
This will ensure all well-known endpoints are proxied to funkwhale, and
|
||||||
|
not just webfinger one.
|
||||||
|
|
||||||
|
Links:
|
||||||
|
|
||||||
|
- About nodeinfo: https://github.com/jhass/nodeinfo
|
|
@ -84,9 +84,9 @@ Define MUSIC_DIRECTORY_PATH /srv/funkwhale/data/music
|
||||||
ProxyPassReverse ${funkwhale-api}/federation
|
ProxyPassReverse ${funkwhale-api}/federation
|
||||||
</Location>
|
</Location>
|
||||||
|
|
||||||
<Location "/.well-known/webfinger">
|
<Location "/.well-known/">
|
||||||
ProxyPass ${funkwhale-api}/.well-known/webfinger
|
ProxyPass ${funkwhale-api}/.well-known/
|
||||||
ProxyPassReverse ${funkwhale-api}/.well-known/webfinger
|
ProxyPassReverse ${funkwhale-api}/.well-known/
|
||||||
</Location>
|
</Location>
|
||||||
|
|
||||||
Alias /media /srv/funkwhale/data/media
|
Alias /media /srv/funkwhale/data/media
|
||||||
|
|
|
@ -67,9 +67,9 @@ server {
|
||||||
proxy_pass http://funkwhale-api/federation/;
|
proxy_pass http://funkwhale-api/federation/;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /.well-known/webfinger {
|
location /.well-known/ {
|
||||||
include /etc/nginx/funkwhale_proxy.conf;
|
include /etc/nginx/funkwhale_proxy.conf;
|
||||||
proxy_pass http://funkwhale-api/.well-known/webfinger;
|
proxy_pass http://funkwhale-api/.well-known/;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /media/ {
|
location /media/ {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<div v-if="stats" class="ui stackable two column grid">
|
<div v-if="stats" class="ui stackable two column grid">
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<h3 class="ui left aligned header"><i18next path="User activity"/></h3>
|
<h3 class="ui left aligned header"><i18next path="User activity"/></h3>
|
||||||
<div class="ui mini horizontal statistics">
|
<div v-if="stats" class="ui mini horizontal statistics">
|
||||||
<div class="statistic">
|
<div class="statistic">
|
||||||
<div class="value">
|
<div class="value">
|
||||||
<i class="green user icon"></i>
|
<i class="green user icon"></i>
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="statistic">
|
<div class="statistic">
|
||||||
<div class="value">
|
<div class="value">
|
||||||
<i class="pink heart icon"></i> {{ stats.track_favorites }}
|
<i class="pink heart icon"></i> {{ stats.trackFavorites }}
|
||||||
</div>
|
</div>
|
||||||
<i18next tag="div" class="label" path="Tracks favorited"/>
|
<i18next tag="div" class="label" path="Tracks favorited"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -30,7 +30,7 @@
|
||||||
<div class="ui mini horizontal statistics">
|
<div class="ui mini horizontal statistics">
|
||||||
<div class="statistic">
|
<div class="statistic">
|
||||||
<div class="value">
|
<div class="value">
|
||||||
{{ parseInt(stats.music_duration) }}
|
{{ parseInt(stats.musicDuration) }}
|
||||||
</div>
|
</div>
|
||||||
<i18next tag="div" class="label" path="hours of music"/>
|
<i18next tag="div" class="label" path="hours of music"/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -59,6 +59,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import _ from 'lodash'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import logger from '@/logging'
|
import logger from '@/logging'
|
||||||
|
|
||||||
|
@ -76,8 +77,16 @@ export default {
|
||||||
var self = this
|
var self = this
|
||||||
this.isLoading = true
|
this.isLoading = true
|
||||||
logger.default.debug('Fetching instance stats...')
|
logger.default.debug('Fetching instance stats...')
|
||||||
axios.get('instance/stats/').then((response) => {
|
axios.get('instance/nodeinfo/2.0/').then((response) => {
|
||||||
self.stats = response.data
|
let d = response.data
|
||||||
|
self.stats = {}
|
||||||
|
self.stats.users = _.get(d, 'usage.users.total')
|
||||||
|
self.stats.listenings = _.get(d, 'metadata.usage.listenings.total')
|
||||||
|
self.stats.trackFavorites = _.get(d, 'metadata.usage.favorites.tracks.total')
|
||||||
|
self.stats.musicDuration = _.get(d, 'metadata.library.music.hours')
|
||||||
|
self.stats.artists = _.get(d, 'metadata.library.artists.total')
|
||||||
|
self.stats.albums = _.get(d, 'metadata.library.albums.total')
|
||||||
|
self.stats.tracks = _.get(d, 'metadata.library.tracks.total')
|
||||||
self.isLoading = false
|
self.isLoading = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue