add very basic login/logout forms, stream management forms
This commit is contained in:
parent
96f6ebebe1
commit
76aac4d59f
|
@ -9,16 +9,20 @@ services:
|
||||||
- postgres
|
- postgres
|
||||||
- redis
|
- redis
|
||||||
environment:
|
environment:
|
||||||
DEBUG: 1
|
- DEBUG=1
|
||||||
SECRET_KEY: "D4mn1t_Ch4nG3_M3!1!!"
|
- "SECRET_KEY=D4mn1t_Ch4nG3_M3!1!!"
|
||||||
SQL_ENGINE: django.db.backends.postgresql
|
- SQL_ENGINE=django.db.backends.postgresql
|
||||||
SQL_USER: portier
|
- SQL_USER=portier
|
||||||
SQL_PASSWORD: portier
|
- SQL_PASSWORD=portier
|
||||||
SQL_DATABASE: portier
|
- SQL_DATABASE=portier
|
||||||
SQL_HOST: postgres
|
- SQL_HOST=postgres
|
||||||
SQL_PORT: 5432
|
- SQL_PORT=5432
|
||||||
REDIS_HOST: redis
|
- REDIS_HOST=redis
|
||||||
REDIS_PORT: 6379
|
- REDIS_PORT=6379
|
||||||
|
- "EMAIL_FROM=${EMAIL_FROM}"
|
||||||
|
- "EMAIL_HOST=${EMAIL_HOST}"
|
||||||
|
- "EMAIL_HOST_USER=${EMAIL_HOST_USER}"
|
||||||
|
- "EMAIL_HOST_PASSWORD=${EMAIL_HOST_PASSWORD}"
|
||||||
redis:
|
redis:
|
||||||
image: redis:5-alpine
|
image: redis:5-alpine
|
||||||
postgres:
|
postgres:
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
{% block 'body' %}
|
{% block 'content' %}
|
||||||
<main class="container" role="main">
|
<div class="jumbotron">
|
||||||
<h1>Portier</h1>
|
<h1 class="display-4">Hello, world!</h1>
|
||||||
<p class="leader">Nothing to see here yet. We're working on it, though!</p>
|
<p class="leader">Nothing to see here yet. We're working on it, though!</p>
|
||||||
<p><a href="https://github.com/chaoswest-tv/portier">See progress on GitHub</a></p>
|
<hr class="my-4">
|
||||||
</main>
|
<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>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -36,6 +36,7 @@ INSTALLED_APPS = [
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
'django_registration',
|
||||||
'bootstrap4',
|
'bootstrap4',
|
||||||
'fa',
|
'fa',
|
||||||
'portal.apps.PortalConfig',
|
'portal.apps.PortalConfig',
|
||||||
|
@ -108,26 +109,37 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Django-Registration
|
||||||
|
|
||||||
|
ACCOUNT_ACTIVATION_DAYS = 7
|
||||||
|
|
||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/3.0/topics/i18n/
|
# https://docs.djangoproject.com/en/3.0/topics/i18n/
|
||||||
|
|
||||||
LANGUAGE_CODE = 'en-us'
|
LANGUAGE_CODE = 'en-us'
|
||||||
|
|
||||||
TIME_ZONE = 'UTC'
|
TIME_ZONE = 'UTC'
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
USE_L10N = True
|
USE_L10N = True
|
||||||
|
|
||||||
USE_TZ = True
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/3.0/howto/static-files/
|
# https://docs.djangoproject.com/en/3.0/howto/static-files/
|
||||||
|
|
||||||
STATIC_URL = '/static/'
|
STATIC_URL = '/static/'
|
||||||
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
|
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
|
||||||
|
STATICFILES_DIRS = [
|
||||||
|
os.path.join(BASE_DIR, "static"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# E-mail
|
||||||
|
DEFAULT_FROM_EMAIL = os.environ.get('EMAIL_FROM')
|
||||||
|
EMAIL_HOST = os.environ.get('EMAIL_HOST')
|
||||||
|
EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER')
|
||||||
|
EMAIL_HOST_PASSWORD = os.environ.get('EMAIL_HOST_PASSWORD')
|
||||||
|
EMAIL_PORT = os.environ.get('EMAIL_PORT', default=465)
|
||||||
|
EMAIL_USE_SSL = int(os.environ.get('EMAIL_USE_SSL', default=1))
|
||||||
|
|
||||||
|
|
||||||
# Celery
|
# Celery
|
||||||
|
|
|
@ -17,6 +17,8 @@ from django.contrib import admin
|
||||||
from django.urls import include, path
|
from django.urls import include, path
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
path('accounts/', include('django_registration.backends.activation.urls')),
|
||||||
|
path('accounts/', include('django.contrib.auth.urls')),
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('rtmp/', include('rtmp.urls')),
|
path('rtmp/', include('rtmp.urls')),
|
||||||
path('concierge/', include('concierge.urls')),
|
path('concierge/', include('concierge.urls')),
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
django>=3.0
|
django>=3.0
|
||||||
|
django-registration>=3.1
|
||||||
django-bootstrap4
|
django-bootstrap4
|
||||||
django-fa
|
django-fa
|
||||||
celery>=4.4
|
celery>=4.4
|
||||||
|
|
|
@ -11,5 +11,8 @@ class RestreamConfig(models.Model):
|
||||||
name = models.CharField(max_length=100)
|
name = models.CharField(max_length=100)
|
||||||
active = models.BooleanField()
|
active = models.BooleanField()
|
||||||
|
|
||||||
|
def class_name(self):
|
||||||
|
return self.__class__.__name__
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '{} to {}'.format(self.stream, self.name)
|
return '{} to {}'.format(self.stream, self.name)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.urls import reverse
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
@ -8,6 +9,9 @@ from . import signals
|
||||||
class Application(models.Model):
|
class Application(models.Model):
|
||||||
name = models.CharField(max_length=100, unique=True, help_text=_("rtmp_application_name"))
|
name = models.CharField(max_length=100, unique=True, help_text=_("rtmp_application_name"))
|
||||||
|
|
||||||
|
def class_name(self):
|
||||||
|
return self.__class__.__name__
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
@ -50,5 +54,11 @@ class Stream(models.Model):
|
||||||
)
|
)
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('rtmp:stream_detail', kwargs={'pk': self.pk})
|
||||||
|
|
||||||
|
def class_name(self):
|
||||||
|
return self.__class__.__name__
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load bootstrap4 %}
|
||||||
|
{% load font_awesome %}
|
||||||
|
{% block 'content' %}
|
||||||
|
<h6>Create new stream configuration</h6>
|
||||||
|
<hr class="my-4">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm border-right">
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="alert alert-warning" role="alert">
|
||||||
|
When deleting a Stream configuration, you will also delete all other configurations that depend on this stream!
|
||||||
|
</div>
|
||||||
|
<p>Are you sure you want to delete the Stream configuration "{{ object }}"?</p>
|
||||||
|
{% buttons %}
|
||||||
|
<button type="submit" class="btn btn-danger" value="login">
|
||||||
|
{% fa 'trash' %} Delete
|
||||||
|
</button>
|
||||||
|
{% endbuttons %}
|
||||||
|
<input type="hidden" name="next" value="{{ next }}">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm">
|
||||||
|
<h5>Deleting configurations</h5>
|
||||||
|
<ul>
|
||||||
|
{% for object in to_delete %}
|
||||||
|
{% if object.name %}
|
||||||
|
<li>
|
||||||
|
<strong>{{ object.name }}</strong> - <span>{{ object.class_name }}</span>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<ul>
|
||||||
|
{% for nested_object in object %}
|
||||||
|
<li>
|
||||||
|
<strong>{{ nested_object.name }}</strong> - <span>{{ nested_object.class_name }}</span>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,71 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load bootstrap4 %}
|
||||||
|
{% load font_awesome %}
|
||||||
|
{% block 'content' %}
|
||||||
|
<h6>Show stream configuration details</h6>
|
||||||
|
<hr class="my-4">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm border-right">
|
||||||
|
<dl class="row">
|
||||||
|
<dt class="col-sm-3">Name</dt>
|
||||||
|
<dd class="col-sm-9">{{ object.name }}</dd>
|
||||||
|
<dt class="col-sm-3">Application</dt>
|
||||||
|
<dd class="col-sm-9">{{ object.application }}</dd>
|
||||||
|
</dl>
|
||||||
|
<p><strong>Do not share the stream ID.</strong> It serves as a secret key, and anyone that knows this key could send content to your stream.</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm">
|
||||||
|
<h6>How to configure your Encoder</h6>
|
||||||
|
<hr class="my-4">
|
||||||
|
<p>Set the following <strong>stream server</strong> in your encoder application:</p>
|
||||||
|
<code>rtmp://TODO TODO SERVER BASE URL/{{ object.application }}/</code>
|
||||||
|
<p>Set the following <strong>stream ID</strong> in your encoder application:</p>
|
||||||
|
<div class="input-group mb-4" id="show_hide_stream_key">
|
||||||
|
<input class="form-control" type="password" value="{{ object.stream }}">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<a href="" class="btn btn-outline-secondary"><i class="fa fa-eye-slash" aria-hidden="true"></i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p>You may need to use the full URL instead:</p>
|
||||||
|
<div class="input-group mb-4" id="show_hide_stream_url">
|
||||||
|
<input class="form-control" type="password" value="rtmp://TODO TODO TODO SERVER BASE URL/{{ object.stream }}">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<a href="" class="btn btn-outline-secondary"><i class="fa fa-eye-slash" aria-hidden="true"></i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
$(function () {
|
||||||
|
$('[data-toggle="popover"]').popover()
|
||||||
|
})
|
||||||
|
$(function() {
|
||||||
|
$("#show_hide_stream_key a").on('click', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
if ($('#show_hide_stream_key input').attr('type') == 'text') {
|
||||||
|
$('#show_hide_stream_key input').attr('type', 'password');
|
||||||
|
$('#show_hide_stream_key i').addClass('fa-eye-slash');
|
||||||
|
$('#show_hide_stream_key i').removeClass('fa-eye');
|
||||||
|
} else if ($('#show_hide_stream_key input').attr('type') == 'password') {
|
||||||
|
$('#show_hide_stream_key input').attr('type', 'text');
|
||||||
|
$('#show_hide_stream_key i').removeClass('fa-eye-slash');
|
||||||
|
$('#show_hide_stream_key i').addClass('fa-eye');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$(function() {
|
||||||
|
$("#show_hide_stream_url a").on('click', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
if ($('#show_hide_stream_url input').attr('type') == 'text') {
|
||||||
|
$('#show_hide_stream_url input').attr('type', 'password');
|
||||||
|
$('#show_hide_stream_url i').addClass('fa-eye-slash');
|
||||||
|
$('#show_hide_stream_url i').removeClass('fa-eye');
|
||||||
|
} else if ($('#show_hide_stream_url input').attr('type') == 'password') {
|
||||||
|
$('#show_hide_stream_url input').attr('type', 'text');
|
||||||
|
$('#show_hide_stream_url i').removeClass('fa-eye-slash');
|
||||||
|
$('#show_hide_stream_url i').addClass('fa-eye');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,23 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load bootstrap4 %}
|
||||||
|
{% block 'content' %}
|
||||||
|
<h6>Create new stream configuration</h6>
|
||||||
|
<hr class="my-4">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm border-right">
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
{% buttons %}
|
||||||
|
<button type="submit" class="btn btn-primary" value="login">
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
{% endbuttons %}
|
||||||
|
<input type="hidden" name="next" value="{{ next }}">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm">
|
||||||
|
<p>Enter a descriptive name, and select one of the applications.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,38 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load bootstrap4 %}
|
||||||
|
{% load font_awesome %}
|
||||||
|
{% block 'content' %}
|
||||||
|
<h6>Stream configurations</h6>
|
||||||
|
<hr class="my-4">
|
||||||
|
<div class="btn-toolbar mb-4" role="toolbar">
|
||||||
|
<div class="btn-group" role="group">
|
||||||
|
<a href="{% url 'rtmp:stream_create' %}" type="button" class="btn btn-sm btn-outline-success">{% fa 'plus' %} Create</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table class="table">
|
||||||
|
<thead class="thead-dark">
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Name</th>
|
||||||
|
<th scope="col" class="text-right">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for object in object_list %}
|
||||||
|
<tr>
|
||||||
|
<th scope="ro">{{ object.name }}</th>
|
||||||
|
<td align="right">
|
||||||
|
<div class="btn-group" role="group" aria-label="Basic example">
|
||||||
|
<a href="{% url 'rtmp:stream_detail' pk=object.pk %}" type="button" class="btn btn-sm btn-primary">Details</a>
|
||||||
|
<a href="{% url 'rtmp:stream_delete' pk=object.pk %}" type="button" class="btn btn-sm btn-danger">{% fa 'trash' %}</a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<script>
|
||||||
|
$(function () {
|
||||||
|
$('[data-toggle="popover"]').popover()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -1,6 +1,12 @@
|
||||||
from django.urls import path
|
from django.urls import path
|
||||||
from . import views
|
from . import views
|
||||||
|
|
||||||
|
app_name = 'rtmp'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('callback/srs', views.callback_srs, name='callback_srs'),
|
path('callback/srs', views.callback_srs, name='callback_srs'),
|
||||||
|
path('streams/', views.StreamList.as_view(), name='stream_list'),
|
||||||
|
path('streams/<int:pk>/', views.StreamDetail.as_view(), name='stream_detail'),
|
||||||
|
path('streams/<int:pk>/delete', views.StreamDelete.as_view(), name='stream_delete'),
|
||||||
|
path('streams/create', views.StreamCreate.as_view(), name='stream_create'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.http import HttpResponse
|
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.contrib.admin.utils import NestedObjects
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
from django.views.generic import ListView, DetailView, CreateView, DeleteView
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
@ -40,3 +45,36 @@ def callback_srs(request):
|
||||||
stream.on_unpublish(param=param)
|
stream.on_unpublish(param=param)
|
||||||
|
|
||||||
return HttpResponse('0')
|
return HttpResponse('0')
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(login_required, name='dispatch')
|
||||||
|
class StreamList(ListView):
|
||||||
|
model = models.Stream
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(login_required, name='dispatch')
|
||||||
|
class StreamDetail(DetailView):
|
||||||
|
model = models.Stream
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(login_required, name='dispatch')
|
||||||
|
class StreamCreate(CreateView):
|
||||||
|
model = models.Stream
|
||||||
|
fields = ["name", "application"]
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(login_required, name='dispatch')
|
||||||
|
class StreamDelete(DeleteView):
|
||||||
|
model = models.Stream
|
||||||
|
success_url = reverse_lazy('rtmp:stream_list')
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
collector = NestedObjects(using='default')
|
||||||
|
collector.collect([self.object])
|
||||||
|
|
||||||
|
context['to_delete'] = collector.nested()
|
||||||
|
|
||||||
|
print(context['to_delete'])
|
||||||
|
return context
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
{% load static %}
|
||||||
{% load bootstrap4 %}
|
{% load bootstrap4 %}
|
||||||
{% load font_awesome %}
|
{% load font_awesome %}
|
||||||
|
|
||||||
|
@ -6,7 +7,7 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
|
||||||
<title>{% block 'title' %}portier{% endblock %}</title>
|
<title>{% block 'title' %}portier{% endblock %}</title>
|
||||||
{% bootstrap_css %}
|
{% bootstrap_css %}
|
||||||
|
@ -15,8 +16,51 @@
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
{% bootstrap_messages %}
|
<header>
|
||||||
{% block 'body' %}
|
<nav class="navbar navbar-expand-md navbar-light bg-light mb-4 border boder-bottom-0">
|
||||||
|
<span class="navbar-brand mb-0 h1">
|
||||||
|
<a href="{% url 'index' %}">Portier</a>
|
||||||
|
</span>
|
||||||
|
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
|
<span class="navbar-toggler-icon"></span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||||
|
<ul class="navbar-nav mr-auto">
|
||||||
|
</ul>
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
Configuration
|
||||||
|
</a>
|
||||||
|
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
|
||||||
|
<a class="dropdown-item" href="#">Something else here</a>
|
||||||
|
<div class="dropdown-divider"></div>
|
||||||
|
<h6 class="dropdown-header">Streaming</h6>
|
||||||
|
<a class="dropdown-item" href="{% url 'rtmp:stream_list' %}">Streams</a>
|
||||||
|
<a class="dropdown-item" href="#">Restream</a>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
<li class="nav-item">
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
<a class="nav-link logout" href="{% url 'logout' %}?next={% url 'index' %}">Logout</a>
|
||||||
|
{% else %}
|
||||||
|
<a class="nav-link login" href="{% url 'login' %}?next={% url 'index' %}">Login</a>
|
||||||
|
{% endif %}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
<main class="container" roles="main">
|
||||||
|
{% block 'content' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
</main>
|
||||||
|
<footer class="text-muted">
|
||||||
|
<div class="container">
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load bootstrap4 %}
|
||||||
|
{% block 'content' %}
|
||||||
|
<h1>Login</h1>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm border-right">
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
{% buttons %}
|
||||||
|
<button type="submit" class="btn btn-primary" value="login">
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
{% endbuttons %}
|
||||||
|
<input type="hidden" name="next" value="{{ next }}">
|
||||||
|
</form>
|
||||||
|
<p><a href="{% url 'password_reset' %}">Lost password?</a></p>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load bootstrap4 %}
|
||||||
|
{% block 'content' %}
|
||||||
|
<h1>Password Change</h1>
|
||||||
|
<p>It frickin' worked! OMG.</p>
|
||||||
|
<a class="btn btn-primary btn-lg" href="{% url 'index' %}">Back to safety</a>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,22 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load bootstrap4 %}
|
||||||
|
{% block 'content' %}
|
||||||
|
<h1>Password Change</h1>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm border-right">
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
{% buttons %}
|
||||||
|
<button type="submit" class="btn btn-primary" value="login">
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
{% endbuttons %}
|
||||||
|
<input type="hidden" name="next" value="{{ next }}">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm">
|
||||||
|
<p>Please enter a name for your new stream configuration. This is only used for identification purposes.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,22 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load bootstrap4 %}
|
||||||
|
{% block 'content' %}
|
||||||
|
<h1>Password Reset</h1>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm border-right">
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% bootstrap_form form %}
|
||||||
|
{% buttons %}
|
||||||
|
<button type="submit" class="btn btn-primary" value="login">
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
{% endbuttons %}
|
||||||
|
<input type="hidden" name="next" value="{{ next }}">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm">
|
||||||
|
<p>Forgotten your password? Enter your email address, and we’ll email instructions for setting a new one.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
Loading…
Reference in New Issue