make stream and restreamconfig listviews reactive with vuejs against restapi
This commit is contained in:
parent
2f5c5b20c5
commit
9b231f6859
|
@ -2,6 +2,8 @@
|
||||||
static/css/font-awesome.min.css
|
static/css/font-awesome.min.css
|
||||||
static/js/bootstrap.bundle.min.js
|
static/js/bootstrap.bundle.min.js
|
||||||
static/js/jquery.min.js
|
static/js/jquery.min.js
|
||||||
|
static/js/vue.min.js
|
||||||
|
static/js/axios.min.js
|
||||||
static/fonts
|
static/fonts
|
||||||
|
|
||||||
staticfiles
|
staticfiles
|
||||||
|
|
15
Dockerfile
15
Dockerfile
|
@ -5,11 +5,6 @@ WORKDIR /app
|
||||||
ENV PYTHONDONTWRITEBYTECODE 1
|
ENV PYTHONDONTWRITEBYTECODE 1
|
||||||
ENV PYTHONUNBUFFERED 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
|
# install required packages
|
||||||
RUN apk add --no-cache postgresql-dev gcc python3-dev musl-dev gettext postgresql-client nginx supervisor
|
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 --chown=portier:portier . /app
|
||||||
|
|
||||||
# add static external libraries for frontend
|
# add static external libraries for frontend
|
||||||
RUN wget http://code.jquery.com/jquery-${JQUERY_VERSION}.min.js -O /app/static/js/jquery.min.js \
|
RUN ./fetch_frontend_libs.sh \
|
||||||
&& wget https://stackpath.bootstrapcdn.com/bootstrap/${BOOTSTRAP_VERSION}/js/bootstrap.bundle.min.js -O /app/static/js/bootstrap.bundle.min.js \
|
&& chown -R portier:portier static/
|
||||||
&& 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/
|
|
||||||
|
|
||||||
# collect static files and compile localized strings
|
# collect static files and compile localized strings
|
||||||
RUN ./manage.py collectstatic --noinput --link
|
RUN ./manage.py collectstatic --noinput --link
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,11 +1,37 @@
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
{% load fontawesome_5 %}
|
||||||
|
|
||||||
{% block 'content' %}
|
{% block 'content' %}
|
||||||
<div class="jumbotron">
|
<div class="jumbotron">
|
||||||
<h1 class="display-4">Hello, world!</h1>
|
<h1 class="display-4">Streaming Magic!</h1>
|
||||||
<p class="leader">Nothing to see here yet. We're working on it, though!</p>
|
|
||||||
<hr class="my-4">
|
<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>
|
<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-lg" href="https://github.com/chaoswest-tv/portier" role="button">Code on GitHub</a>
|
<a class="btn btn-primary btn" href="https://github.com/chaoswest-tv/portier" role="button">Code on GitHub</a>
|
||||||
</div>
|
</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 %}
|
{% endblock %}
|
||||||
|
|
|
@ -20,7 +20,8 @@ class ApplicationViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
class StreamSerializer(ObjectPermissionsAssignmentMixin, serializers.ModelSerializer):
|
class StreamSerializer(ObjectPermissionsAssignmentMixin, serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Stream
|
model = Stream
|
||||||
fields = ['id', 'stream', 'name', 'application']
|
fields = '__all__'
|
||||||
|
read_only_fields = ['publish_counter']
|
||||||
|
|
||||||
def get_permissions_map(self, created):
|
def get_permissions_map(self, created):
|
||||||
current_user = self.context['request'].user
|
current_user = self.context['request'].user
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
{% load bootstrap4 %}
|
{% load bootstrap4 %}
|
||||||
{% load fontawesome_5 %}
|
{% load fontawesome_5 %}
|
||||||
{% load guardian_tags %}
|
|
||||||
|
|
||||||
{% block 'content' %}
|
{% block 'content' %}
|
||||||
|
|
||||||
|
@ -19,36 +19,45 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr class="my-4">
|
<hr class="my-4">
|
||||||
<table class="table">
|
<div id="app">
|
||||||
<thead class="thead-light">
|
<div v-if="isLoading">
|
||||||
<tr>
|
<div class="my-4 text-center">
|
||||||
<th scope="col">{% trans "name" %}</th>
|
<div class="spinner-border" role="status">
|
||||||
<th scope="col">{% trans "active" %}</th>
|
<span class="sr-only">{% trans "loading..." %}</span>
|
||||||
<th scope="col" class="text-right">{% trans "actions" %}</th>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
</thead>
|
</div>
|
||||||
<tbody>
|
<table v-else class="table">
|
||||||
{% for object in object_list %}
|
<thead class="thead-light">
|
||||||
{% get_obj_perms user for object as "obj_perms" %}
|
<tr>
|
||||||
{% if "view_restreamconfig" in obj_perms %}
|
<th scope="col">{% trans "name" %}</th>
|
||||||
<tr>
|
<th scope="col">{% trans "active" %}</th>
|
||||||
<th scope="row">{{ object.name }}</th>
|
<th scope="col" class="text-right">{% trans "actions" %}</th>
|
||||||
<td>
|
</tr>
|
||||||
{% if object.active %}
|
</thead>
|
||||||
<span class="text-success">{% fa5_icon 'check-circle' %}</span>
|
<tbody>
|
||||||
{% else %}
|
<tr v-for="cfg in cfgs">
|
||||||
<span class="text-danger">{% fa5_icon 'times-circle' %}</span>
|
<th scope="row">{% verbatim %}{{cfg.name}}{% endverbatim %}</th>
|
||||||
{% endif %}
|
<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>
|
||||||
<td align="right">
|
<td v-else>
|
||||||
<a href="{% url 'restream:restreamconfig_detail' pk=object.pk %}" type="button" class="btn btn-sm btn-outline-primary">{% trans "details" %}</a>
|
<button v-on:click="toggleActive(cfg)" type="button" class="btn btn-outline-danger">
|
||||||
{% if "delete_restreamconfig" in obj_perms %}
|
{% fa5_icon 'times-circle' %}
|
||||||
<a href="{% url 'restream:restreamconfig_delete' pk=object.pk %}" type="button" class="btn btn-sm btn-outline-danger">{% fa5_icon 'trash' %}</a>
|
</button>
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
|
<td align="right">
|
||||||
|
<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>
|
</tr>
|
||||||
{% endif %}
|
</tbody>
|
||||||
{% endfor %}
|
</table>
|
||||||
</tbody>
|
</div>
|
||||||
</table>
|
|
||||||
|
<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 %}
|
{% endblock %}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.contrib.auth.decorators import login_required
|
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.utils.decorators import method_decorator
|
||||||
from django.views.generic import ListView, DetailView, CreateView, DeleteView, UpdateView
|
from django.views.generic import ListView, DetailView, CreateView, DeleteView, UpdateView
|
||||||
from guardian.decorators import permission_required_or_403
|
from guardian.decorators import permission_required_or_403
|
||||||
|
@ -12,6 +13,7 @@ from . import forms
|
||||||
@method_decorator(login_required, name='dispatch')
|
@method_decorator(login_required, name='dispatch')
|
||||||
@method_decorator(permission_required_or_403('restream.add_restreamconfig'),
|
@method_decorator(permission_required_or_403('restream.add_restreamconfig'),
|
||||||
name='dispatch')
|
name='dispatch')
|
||||||
|
@method_decorator(ensure_csrf_cookie, name='dispatch')
|
||||||
class RestreamConfigList(ListView):
|
class RestreamConfigList(ListView):
|
||||||
model = models.RestreamConfig
|
model = models.RestreamConfig
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load static %}
|
||||||
{% load bootstrap4 %}
|
{% load bootstrap4 %}
|
||||||
{% load fontawesome_5 %}
|
{% load fontawesome_5 %}
|
||||||
{% load guardian_tags %}
|
{% load guardian_tags %}
|
||||||
|
@ -18,36 +19,39 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr class="my-4">
|
<hr class="my-4">
|
||||||
<table class="table">
|
<div id="app">
|
||||||
<thead class="thead-light">
|
<div v-if="isLoading">
|
||||||
<tr>
|
<div class="my-4 text-center">
|
||||||
<th scope="col">{% trans "name" %}</th>
|
<div class="spinner-border" role="status">
|
||||||
<th scope="col">{% trans "receiving" %}</th>
|
<span class="sr-only">{% trans "loading..." %}</span>
|
||||||
<th scope="col" class="text-right">{% trans "actions" %}</th>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
</thead>
|
</div>
|
||||||
<tbody>
|
<table v-else class="table">
|
||||||
{% for object in object_list %}
|
<thead class="thead-light">
|
||||||
{% get_obj_perms user for object as "obj_perms" %}
|
<tr>
|
||||||
{% if "view_stream" in obj_perms %}
|
<th scope="col">{% trans "name" %}</th>
|
||||||
<tr>
|
<th scope="col">{% trans "receiving" %}</th>
|
||||||
<th scope="ro">{{ object.name }}</th>
|
<th scope="col" class="text-right">{% trans "actions" %}</th>
|
||||||
<td>
|
</tr>
|
||||||
{% if object.publish_counter > 0 %}
|
</thead>
|
||||||
<span class="text-success">{% fa5_icon 'check-circle' %}</span>
|
<tbody>
|
||||||
{% else %}
|
<tr v-for="stream in streams">
|
||||||
<span class="text-danger">{% fa5_icon 'times-circle' %}</span>
|
<th scope="row">{% verbatim %}{{ stream.name }}{% endverbatim %}</th>
|
||||||
{% endif %}
|
<td>
|
||||||
</td>
|
<span v-if="isPublishing(stream)" class="text-success">{% fa5_icon 'check-circle' %} <span class="badge badge-success">{% verbatim %}{{ stream.publish_counter}}{% endverbatim %}</span></span>
|
||||||
<td align="right">
|
<span v-else class="text-danger">{% fa5_icon 'times-circle' %}</span>
|
||||||
<a href="{% url 'rtmp:stream_detail' pk=object.pk %}" type="button" class="btn btn-sm btn-outline-primary ">{% trans "details" %}</a>
|
</td>
|
||||||
{% if "delete_stream" in obj_perms %}
|
<td align="right">
|
||||||
<a href="{% url 'rtmp:stream_delete' pk=object.pk %}" type="button" class="btn btn-sm btn-outline-danger">{% fa5_icon 'trash' %}</a>
|
<a v-bind:href="detailLink(stream.id)" type="button" class="btn btn-sm btn-outline-primary">{% trans "details" %}</a>
|
||||||
{% endif %}
|
<a v-bind:href="deleteLink(stream.id)" type="button" class="btn btn-sm btn-outline-danger">{% fa5_icon 'trash' %}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
</tbody>
|
||||||
{% endfor %}
|
</table>
|
||||||
</tbody>
|
</div>
|
||||||
</table>
|
|
||||||
|
<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 %}
|
{% endblock %}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
})
|
|
@ -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);
|
||||||
|
}
|
||||||
|
})
|
Loading…
Reference in New Issue