make stream and restreamconfig listviews reactive with vuejs against restapi

This commit is contained in:
Jan Koppe 2020-05-31 20:25:39 +02:00
parent 2f5c5b20c5
commit 9b231f6859
Signed by: thunfisch
GPG Key ID: BE935B0735A2129B
10 changed files with 204 additions and 80 deletions

2
.gitignore vendored
View File

@ -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

View File

@ -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

20
fetch_frontend_libs.sh Executable file
View File

@ -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

View File

@ -1,11 +1,37 @@
{% extends 'base.html' %}
{% load fontawesome_5 %}
{% block 'content' %}
<div class="jumbotron">
<h1 class="display-4">Hello, world!</h1>
<p class="leader">Nothing to see here yet. We're working on it, though!</p>
<h1 class="display-4">Streaming Magic!</h1>
<hr class="my-4">
<p>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.</p>
<a class="btn btn-primary btn-lg" href="https://github.com/chaoswest-tv/portier" role="button">Code on GitHub</a>
<p>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.</p>
<a class="btn btn-primary btn" href="https://github.com/chaoswest-tv/portier" role="button">Code on GitHub</a>
</div>
<div class="card-deck">
<div class="card text-center">
<div class="card-body">
<h1 class="card-title text-center">{% fa5_icon 'check-double' %}</h1>
<h4 class="card-subtitle text-center mb-2">Redundancy</h4>
<hr class="mb-3">
<p class="card-text">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!</p>
</div>
</div>
<div class="card text-center">
<div class="card-body">
<h1 class="card-title">{% fa5_icon 'code-branch' %}</h1>
<h4 class="card-subtitle mb-2">Restreaming</h4>
<hr class="mb-3">
<p class="card-text">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.</p>
</div>
</div>
<div class="card text-center">
<div class="card-body">
<h1 class="card-title text-center">{% fa5_icon 'hands-helping' %}</h1>
<h4 class="card-subtitle text-center mb-2">Awesome Support</h4>
<hr class="mb-3">
<p class="card-text">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.</p>
</div>
</div>
</div>
{% endblock %}

View File

@ -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

View File

@ -1,8 +1,8 @@
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% load bootstrap4 %}
{% load fontawesome_5 %}
{% load guardian_tags %}
{% block 'content' %}
@ -19,7 +19,15 @@
</div>
</div>
<hr class="my-4">
<table class="table">
<div id="app">
<div v-if="isLoading">
<div class="my-4 text-center">
<div class="spinner-border" role="status">
<span class="sr-only">{% trans "loading..." %}</span>
</div>
</div>
</div>
<table v-else class="table">
<thead class="thead-light">
<tr>
<th scope="col">{% trans "name" %}</th>
@ -28,27 +36,28 @@
</tr>
</thead>
<tbody>
{% for object in object_list %}
{% get_obj_perms user for object as "obj_perms" %}
{% if "view_restreamconfig" in obj_perms %}
<tr>
<th scope="row">{{ object.name }}</th>
<td>
{% if object.active %}
<span class="text-success">{% fa5_icon 'check-circle' %}</span>
{% else %}
<span class="text-danger">{% fa5_icon 'times-circle' %}</span>
{% endif %}
<tr v-for="cfg in cfgs">
<th scope="row">{% verbatim %}{{cfg.name}}{% endverbatim %}</th>
<td v-if="cfg.active">
<button v-on:click="toggleActive(cfg)" type="button" class="btn btn-outline-success">
{% fa5_icon 'check-circle' %}
</button>
</td>
<td v-else>
<button v-on:click="toggleActive(cfg)" type="button" class="btn btn-outline-danger">
{% fa5_icon 'times-circle' %}
</button>
</td>
<td align="right">
<a href="{% url 'restream:restreamconfig_detail' pk=object.pk %}" type="button" class="btn btn-sm btn-outline-primary">{% trans "details" %}</a>
{% if "delete_restreamconfig" in obj_perms %}
<a href="{% url 'restream:restreamconfig_delete' pk=object.pk %}" type="button" class="btn btn-sm btn-outline-danger">{% fa5_icon 'trash' %}</a>
{% endif %}
<a v-bind:href="detailLink(cfg.id)" type="button" class="btn btn-sm btn-outline-primary">{% trans "details" %}</a>
<a v-bind:href="deleteLink(cfg.id)" type="button" class="btn btn-sm btn-outline-danger">{% fa5_icon 'trash' %}</a>
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
<script src="{% static 'js/vue.min.js' %}"></script>
<script src="{% static 'js/axios.min.js' %}"></script>
<script src="{% static 'js/restreamconfig-list.js' %}"></script>
{% endblock %}

View File

@ -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

View File

@ -1,5 +1,6 @@
{% extends 'base.html' %}
{% load i18n %}
{% load static %}
{% load bootstrap4 %}
{% load fontawesome_5 %}
{% load guardian_tags %}
@ -18,7 +19,15 @@
</div>
</div>
<hr class="my-4">
<table class="table">
<div id="app">
<div v-if="isLoading">
<div class="my-4 text-center">
<div class="spinner-border" role="status">
<span class="sr-only">{% trans "loading..." %}</span>
</div>
</div>
</div>
<table v-else class="table">
<thead class="thead-light">
<tr>
<th scope="col">{% trans "name" %}</th>
@ -27,27 +36,22 @@
</tr>
</thead>
<tbody>
{% for object in object_list %}
{% get_obj_perms user for object as "obj_perms" %}
{% if "view_stream" in obj_perms %}
<tr>
<th scope="ro">{{ object.name }}</th>
<tr v-for="stream in streams">
<th scope="row">{% verbatim %}{{ stream.name }}{% endverbatim %}</th>
<td>
{% if object.publish_counter > 0 %}
<span class="text-success">{% fa5_icon 'check-circle' %}</span>
{% else %}
<span class="text-danger">{% fa5_icon 'times-circle' %}</span>
{% endif %}
<span v-if="isPublishing(stream)" class="text-success">{% fa5_icon 'check-circle' %} <span class="badge badge-success">{% verbatim %}{{ stream.publish_counter}}{% endverbatim %}</span></span>
<span v-else class="text-danger">{% fa5_icon 'times-circle' %}</span>
</td>
<td align="right">
<a href="{% url 'rtmp:stream_detail' pk=object.pk %}" type="button" class="btn btn-sm btn-outline-primary ">{% trans "details" %}</a>
{% if "delete_stream" in obj_perms %}
<a href="{% url 'rtmp:stream_delete' pk=object.pk %}" type="button" class="btn btn-sm btn-outline-danger">{% fa5_icon 'trash' %}</a>
{% endif %}
<a v-bind:href="detailLink(stream.id)" type="button" class="btn btn-sm btn-outline-primary">{% trans "details" %}</a>
<a v-bind:href="deleteLink(stream.id)" type="button" class="btn btn-sm btn-outline-danger">{% fa5_icon 'trash' %}</a>
</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
</div>
<script src="{% static 'js/vue.min.js' %}"></script>
<script src="{% static 'js/axios.min.js' %}"></script>
<script src="{% static 'js/stream-list.js' %}"></script>
{% endblock %}

View File

@ -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()
}
})

34
static/js/stream-list.js Normal file
View File

@ -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);
}
})