From 9b231f6859e2a2174dfebea929ffcf9db49952b5 Mon Sep 17 00:00:00 2001 From: Jan Koppe Date: Sun, 31 May 2020 20:25:39 +0200 Subject: [PATCH] make stream and restreamconfig listviews reactive with vuejs against restapi --- .gitignore | 2 + Dockerfile | 15 +--- fetch_frontend_libs.sh | 20 ++++++ portal/templates/portal/index.html | 34 +++++++-- restapi/views.py | 3 +- .../restream/restreamconfig_list.html | 69 +++++++++++-------- restream/views.py | 2 + rtmp/templates/rtmp/stream_list.html | 68 +++++++++--------- static/js/restreamconfig-list.js | 37 ++++++++++ static/js/stream-list.js | 34 +++++++++ 10 files changed, 204 insertions(+), 80 deletions(-) create mode 100755 fetch_frontend_libs.sh create mode 100644 static/js/restreamconfig-list.js create mode 100644 static/js/stream-list.js diff --git a/.gitignore b/.gitignore index 1472ad2..010e963 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ static/css/font-awesome.min.css static/js/bootstrap.bundle.min.js static/js/jquery.min.js +static/js/vue.min.js +static/js/axios.min.js static/fonts staticfiles diff --git a/Dockerfile b/Dockerfile index 4b160dc..13d7d61 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,11 +5,6 @@ WORKDIR /app ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONUNBUFFERED 1 -# https://github.com/twbs/bootstrap/issues/30553 don't upgrade jquery to 3.5.0 yet -ENV JQUERY_VERSION=3.4.1 -ENV BOOTSTRAP_VERSION=4.4.1 -ENV INTER_VERSION=3.13 - # install required packages RUN apk add --no-cache postgresql-dev gcc python3-dev musl-dev gettext postgresql-client nginx supervisor @@ -28,14 +23,8 @@ RUN addgroup -S portier && adduser -S portier -G portier ADD --chown=portier:portier . /app # add static external libraries for frontend -RUN wget http://code.jquery.com/jquery-${JQUERY_VERSION}.min.js -O /app/static/js/jquery.min.js \ - && wget https://stackpath.bootstrapcdn.com/bootstrap/${BOOTSTRAP_VERSION}/js/bootstrap.bundle.min.js -O /app/static/js/bootstrap.bundle.min.js \ - && mkdir -p /tmp/inter /app/static/fonts \ - && cd /tmp/inter && wget https://github.com/rsms/inter/releases/download/v${INTER_VERSION}/Inter-${INTER_VERSION}.zip \ - && unzip Inter-${INTER_VERSION}.zip && mv /tmp/inter/Inter\ Web/* /app/static/fonts/ \ - && cd - \ - && rm -rf /tmp/inter \ - && chown -R portier:portier /app/static/fonts/ +RUN ./fetch_frontend_libs.sh \ + && chown -R portier:portier static/ # collect static files and compile localized strings RUN ./manage.py collectstatic --noinput --link diff --git a/fetch_frontend_libs.sh b/fetch_frontend_libs.sh new file mode 100755 index 0000000..a6ddf38 --- /dev/null +++ b/fetch_frontend_libs.sh @@ -0,0 +1,20 @@ +#!/bin/sh +set -ex + +JQUERY_VERSION=3.5.1 +BOOTSTRAP_VERSION=4.4.1 +INTER_VERSION=3.13 +VUE_VERSION=2.6.11 +AXIOS_VERSION=0.19.2 + +wget https://cdn.jsdelivr.net/npm/jquery@${JQUERY_VERSION}/dist/jquery.min.js -O static/js/jquery.min.js +wget https://cdn.jsdelivr.net/npm/vue@${VUE_VERSION}/dist/vue.min.js -O static/js/vue.min.js +wget https://cdn.jsdelivr.net/npm/axios@${AXIOS_VERSION}/dist/axios.min.js -O static/js/axios.min.js +wget https://cdn.jsdelivr.net/npm/bootstrap@${BOOTSTRAP_VERSION}/dist/js/bootstrap.bundle.min.js -O static/js/bootstrap.bundle.min.js +mkdir -p /tmp/inter static/fonts +cd /tmp/inter && wget https://github.com/rsms/inter/releases/download/v${INTER_VERSION}/Inter-${INTER_VERSION}.zip +unzip Inter-${INTER_VERSION}.zip +cd - +mv /tmp/inter/Inter\ Web/* static/fonts/ +rm -rf /tmp/inter + diff --git a/portal/templates/portal/index.html b/portal/templates/portal/index.html index b3f25f0..9fc75f7 100644 --- a/portal/templates/portal/index.html +++ b/portal/templates/portal/index.html @@ -1,11 +1,37 @@ {% extends 'base.html' %} +{% load fontawesome_5 %} {% block 'content' %}
-

Hello, world!

-

Nothing to see here yet. We're working on it, though!

+

Streaming Magic!


-

Portier will be a pretty cool streaming platform, built by and for nerds. You are welcome to contribute, be it code, ideas, documentation, design or something else entirely.

- Code on GitHub +

Portier is a pretty cool streaming platform, built by and for nerds. You are welcome to contribute, be it code, ideas, documentation, design or something else entirely.

+ Code on GitHub
+
+
+
+

{% fa5_icon 'check-double' %}

+

Redundancy

+
+

Send us multiple copies of your stream over different network connections. If one of your connections fails, we'll automatically fail over to the next available - within seconds!

+
+
+
+
+

{% fa5_icon 'code-branch' %}

+

Restreaming

+
+

Any stream that's coming into our system can be restreamed to a new target. This works with most of the popular streaming platforms - even multiple at the same time.

+
+
+
+
+

{% fa5_icon 'hands-helping' %}

+

Awesome Support

+
+

Streaming is hard. Especially if you're not a techie. We have streaming-ninjas in our team that will happily support you every step of the way.

+
+
+
{% endblock %} diff --git a/restapi/views.py b/restapi/views.py index d13dcfd..696f200 100644 --- a/restapi/views.py +++ b/restapi/views.py @@ -20,7 +20,8 @@ class ApplicationViewSet(viewsets.ReadOnlyModelViewSet): class StreamSerializer(ObjectPermissionsAssignmentMixin, serializers.ModelSerializer): class Meta: model = Stream - fields = ['id', 'stream', 'name', 'application'] + fields = '__all__' + read_only_fields = ['publish_counter'] def get_permissions_map(self, created): current_user = self.context['request'].user diff --git a/restream/templates/restream/restreamconfig_list.html b/restream/templates/restream/restreamconfig_list.html index 4008c2e..fe83a5a 100644 --- a/restream/templates/restream/restreamconfig_list.html +++ b/restream/templates/restream/restreamconfig_list.html @@ -1,8 +1,8 @@ {% extends 'base.html' %} {% load i18n %} +{% load static %} {% load bootstrap4 %} {% load fontawesome_5 %} -{% load guardian_tags %} {% block 'content' %} @@ -19,36 +19,45 @@
- - - - - - - - - - {% for object in object_list %} - {% get_obj_perms user for object as "obj_perms" %} - {% if "view_restreamconfig" in obj_perms %} - - - +
{% trans "name" %}{% trans "active" %}{% trans "actions" %}
{{ object.name }} - {% if object.active %} - {% fa5_icon 'check-circle' %} - {% else %} - {% fa5_icon 'times-circle' %} - {% endif %} +
+
+
+
+ {% trans "loading..." %} +
+
+
+ + + + + + + + + + + + - + - {% endif %} - {% endfor %} - -
{% trans "name" %}{% trans "active" %}{% trans "actions" %}
{% verbatim %}{{cfg.name}}{% endverbatim %} + - {% trans "details" %} - {% if "delete_restreamconfig" in obj_perms %} - {% fa5_icon 'trash' %} - {% endif %} + + + {% trans "details" %} + {% fa5_icon 'trash' %} +
+
+ + + + + {% endblock %} diff --git a/restream/views.py b/restream/views.py index 62d519c..ed18113 100644 --- a/restream/views.py +++ b/restream/views.py @@ -1,5 +1,6 @@ from django.urls import reverse_lazy from django.contrib.auth.decorators import login_required +from django.views.decorators.csrf import ensure_csrf_cookie from django.utils.decorators import method_decorator from django.views.generic import ListView, DetailView, CreateView, DeleteView, UpdateView from guardian.decorators import permission_required_or_403 @@ -12,6 +13,7 @@ from . import forms @method_decorator(login_required, name='dispatch') @method_decorator(permission_required_or_403('restream.add_restreamconfig'), name='dispatch') +@method_decorator(ensure_csrf_cookie, name='dispatch') class RestreamConfigList(ListView): model = models.RestreamConfig diff --git a/rtmp/templates/rtmp/stream_list.html b/rtmp/templates/rtmp/stream_list.html index 1aefaf9..7564eed 100644 --- a/rtmp/templates/rtmp/stream_list.html +++ b/rtmp/templates/rtmp/stream_list.html @@ -1,5 +1,6 @@ {% extends 'base.html' %} {% load i18n %} +{% load static %} {% load bootstrap4 %} {% load fontawesome_5 %} {% load guardian_tags %} @@ -18,36 +19,39 @@
- - - - - - - - - - {% for object in object_list %} - {% get_obj_perms user for object as "obj_perms" %} - {% if "view_stream" in obj_perms %} - - - - - - {% endif %} - {% endfor %} - -
{% trans "name" %}{% trans "receiving" %}{% trans "actions" %}
{{ object.name }} - {% if object.publish_counter > 0 %} - {% fa5_icon 'check-circle' %} - {% else %} - {% fa5_icon 'times-circle' %} - {% endif %} - - {% trans "details" %} - {% if "delete_stream" in obj_perms %} - {% fa5_icon 'trash' %} - {% endif %} -
+
+
+
+
+ {% trans "loading..." %} +
+
+
+ + + + + + + + + + + + + + + +
{% trans "name" %}{% trans "receiving" %}{% trans "actions" %}
{% verbatim %}{{ stream.name }}{% endverbatim %} + {% fa5_icon 'check-circle' %} {% verbatim %}{{ stream.publish_counter}}{% endverbatim %} + {% fa5_icon 'times-circle' %} + + {% trans "details" %} + {% fa5_icon 'trash' %} +
+
+ + + + {% endblock %} diff --git a/static/js/restreamconfig-list.js b/static/js/restreamconfig-list.js new file mode 100644 index 0000000..213e304 --- /dev/null +++ b/static/js/restreamconfig-list.js @@ -0,0 +1,37 @@ +var app = new Vue({ + el: '#app', + data: { + cfgs: [], + isLoading: true + }, + methods: { + detailLink(id) { + return `${id}/` + }, + deleteLink(id) { + return `${id}/delete` + }, + toggleActive(cfg) { + axios + .patch('/api/v1/restreamconfigs/' + cfg.id + '/', { active: !cfg.active }) + .then(response => { + i = this.cfgs.findIndex((obj => obj.id == cfg.id)) + Vue.set(this.cfgs, i, response.data) + } + ) + }, + fetchData() { + axios + .get('/api/v1/restreamconfigs/') + .then(response => { + this.cfgs = response.data + this.isLoading = false + }) + } + }, + mounted () { + axios.defaults.xsrfCookieName = 'csrftoken' + axios.defaults.xsrfHeaderName = 'X-CSRFTOKEN' + this.fetchData() + } +}) diff --git a/static/js/stream-list.js b/static/js/stream-list.js new file mode 100644 index 0000000..238d41e --- /dev/null +++ b/static/js/stream-list.js @@ -0,0 +1,34 @@ +var app = new Vue({ + el: '#app', + data: { + streams: [], + isLoading: true + }, + methods: { + isPublishing(stream) { + return stream.publish_counter > 0 + }, + detailLink(id) { + return `${id}/` + }, + deleteLink(id) { + return `${id}/delete` + }, + fetchData() { + axios + .get('/api/v1/streams/') + .then(response => { + this.streams = response.data + this.isLoading = false + }) + } + }, + mounted () { + axios.defaults.xsrfCookieName = 'csrftoken' + axios.defaults.xsrfHeaderName = 'X-CSRFTOKEN' + this.fetchData() + setInterval(function () { + this.fetchData(); + }.bind(this), 5000); + } +})