Application Icon upload (#341)

* core: add initial implementation for File Upload

* root: add volumes to docker-compose for file upload

* helm: add pvc for uploads

* core: allow meta_icon to be overwritten with static files
This commit is contained in:
Jens L 2020-11-23 20:50:19 +01:00 committed by GitHub
parent 91e9f176a5
commit 665839133f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 139 additions and 21 deletions

3
.gitignore vendored
View File

@ -197,5 +197,6 @@ local.env.yml
**/charts/*.tgz
# Selenium Screenshots
selenium_screenshots/**
selenium_screenshots/
backups/
media/

View File

@ -25,6 +25,8 @@ services:
PASSBOOK_REDIS__HOST: redis
PASSBOOK_POSTGRESQL__HOST: postgresql
PASSBOOK_POSTGRESQL__PASSWORD: ${PG_PASS}
volumes:
- ./media:/media
ports:
- 8000
networks:
@ -60,11 +62,13 @@ services:
labels:
traefik.enable: 'true'
traefik.docker.network: internal
traefik.http.routers.static-router.rule: PathPrefix(`/static`, `/robots.txt`, `/favicon.ico`)
traefik.http.routers.static-router.rule: PathPrefix(`/static`, `/media`, `/robots.txt`, `/favicon.ico`)
traefik.http.routers.static-router.tls: 'true'
traefik.http.routers.static-router.service: static-service
traefik.http.services.static-service.loadbalancer.healthcheck.path: /
traefik.http.services.static-service.loadbalancer.server.port: '80'
volumes:
- ./media:/media
traefik:
image: traefik:2.3
command:

15
helm/templates/pvc.yaml Normal file
View File

@ -0,0 +1,15 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ include "passbook.fullname" . }}-uploads
labels:
app.kubernetes.io/name: {{ include "passbook.name" . }}
helm.sh/chart: {{ include "passbook.chart" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi

View File

@ -48,3 +48,10 @@ spec:
limits:
cpu: 20m
memory: 20M
volumeMounts:
- name: passbook-uploads
mountPath: /usr/share/nginx/html/media
volumes:
- name: passbook-uploads
persistentVolumeClaim:
claimName: {{ include "passbook.fullname" . }}-uploads

View File

@ -90,6 +90,9 @@ spec:
secretKeyRef:
name: "{{ .Release.Name }}-postgresql"
key: "postgresql-password"
volumeMounts:
- name: passbook-uploads
mountPath: /media
ports:
- name: http
containerPort: 8000
@ -115,3 +118,7 @@ spec:
limits:
cpu: 300m
memory: 500M
volumes:
- name: passbook-uploads
persistentVolumeClaim:
claimName: {{ include "passbook.fullname" . }}-uploads

View File

@ -22,7 +22,7 @@ class ApplicationSerializer(ModelSerializer):
"slug",
"provider",
"meta_launch_url",
"meta_icon_url",
"meta_icon",
"meta_description",
"meta_publisher",
"policies",

View File

@ -23,14 +23,13 @@ class ApplicationForm(forms.ModelForm):
"slug",
"provider",
"meta_launch_url",
"meta_icon_url",
"meta_icon",
"meta_description",
"meta_publisher",
]
widgets = {
"name": forms.TextInput(),
"meta_launch_url": forms.TextInput(),
"meta_icon_url": forms.TextInput(),
"meta_publisher": forms.TextInput(),
}
help_texts = {
@ -44,7 +43,7 @@ class ApplicationForm(forms.ModelForm):
field_classes = {"provider": GroupedModelChoiceField}
labels = {
"meta_launch_url": _("Launch URL"),
"meta_icon_url": _("Icon URL"),
"meta_icon": _("Icon"),
"meta_description": _("Description"),
"meta_publisher": _("Publisher"),
}

View File

@ -0,0 +1,24 @@
# Generated by Django 3.1.3 on 2020-11-23 17:19
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("passbook_core", "0014_auto_20201018_1158"),
]
operations = [
migrations.RemoveField(
model_name="application",
name="meta_icon_url",
),
migrations.AddField(
model_name="application",
name="meta_icon",
field=models.FileField(
blank=True, default="", upload_to="application-icons/"
),
),
]

View File

@ -171,7 +171,8 @@ class Application(PolicyBindingModel):
)
meta_launch_url = models.URLField(default="", blank=True)
meta_icon_url = models.TextField(default="", blank=True)
# For template applications, this can be set to /static/passbook/applications/*
meta_icon = models.FileField(upload_to="application-icons/", default="", blank=True)
meta_description = models.TextField(default="", blank=True)
meta_publisher = models.TextField(default="", blank=True)

View File

@ -13,12 +13,12 @@
{% if applications %}
<div class="pf-l-gallery pf-m-gutter">
{% for app in applications %}
<a href="{{ app.get_launch_url }}" class="pf-c-card pf-m-hoverable pf-m-compact">
<a href="{{ app.get_launch_url }}" class="pf-c-card pf-m-hoverable pf-m-compact pb-root-link">
<div class="pf-c-card__header">
{% if not app.meta_icon_url %}
<i class="pf-icon pf-icon-arrow"></i>
{% if app.meta_icon %}
<img class="app-icon pf-c-avatar" src="{{ app.meta_icon.url }}" alt="{% trans 'Application Icon' %}">
{% else %}
<img class="app-icon pf-c-avatar" src="{{ app.meta_icon_url }}" alt="{% trans 'Application Icon' %}">
<i class="pf-icon pf-icon-arrow"></i>
{% endif %}
</div>
<div class="pf-c-card__title">

View File

@ -57,6 +57,26 @@
{% endif %}
</div>
</div>
{% elif field.field.widget|fieldtype == "ClearableFileInput" %}
<div class="pf-c-form__group-label">
<label class="pf-c-form__label" for="{{ field.name }}-{{ forloop.counter0 }}">
<span class="pf-c-form__label-text">{{ field.label }}</span>
{% if field.field.required %}
<span class="pf-c-form__label-required" aria-hidden="true">&#42;</span>
{% endif %}
</label>
</div>
<div class="pf-c-form__group-control">
<div class="c-form__horizontal-group">
<div class="pf-c-file-upload">
<div class="pf-c-file-upload__file-select">
<div class="pf-c-input-group">
{{ field|css_class:"pf-c-form-control" }}
</div>
</div>
</div>
</div>
</div>
{% else %}
<div class="pf-c-form__group-label">
<label class="pf-c-form__label" for="{{ field.name }}-{{ forloop.counter0 }}">

View File

@ -1,9 +1,10 @@
"""passbook URL Configuration"""
from django.urls import path
from passbook.core.views import impersonate, overview, shell, user
from passbook.core.views import impersonate, library, shell, user
urlpatterns = [
path("", shell.ShellView.as_view(), name="shell"),
# User views
path("-/user/", user.UserSettingsView.as_view(), name="user-settings"),
path("-/user/tokens/", user.TokenListView.as_view(), name="user-tokens"),
@ -22,9 +23,8 @@ urlpatterns = [
user.TokenDeleteView.as_view(),
name="user-tokens-delete",
),
# Overview
path("", shell.ShellView.as_view(), name="shell"),
path("-/overview/", overview.OverviewView.as_view(), name="overview"),
# Libray
path("-/overview/", library.LibraryView.as_view(), name="overview"),
# Impersonation
path(
"-/impersonation/<int:user_id>/",

View File

@ -1,4 +1,4 @@
"""passbook overview views"""
"""passbook library view"""
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView
@ -7,11 +7,11 @@ from passbook.core.models import Application
from passbook.policies.engine import PolicyEngine
class OverviewView(LoginRequiredMixin, TemplateView):
class LibraryView(LoginRequiredMixin, TemplateView):
"""Overview for logged in user, incase user opens passbook directly
and is not being forwarded"""
template_name = "overview/index.html"
template_name = "library.html"
def get_context_data(self, **kwargs):
kwargs["applications"] = []

View File

@ -48,6 +48,7 @@ LOGGER = structlog.get_logger()
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
STATIC_ROOT = BASE_DIR + "/static"
MEDIA_ROOT = BASE_DIR + "/media"
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/
@ -338,6 +339,7 @@ if not DEBUG and _ERROR_REPORTING:
# https://docs.djangoproject.com/en/2.1/howto/static-files/
STATIC_URL = "/static/"
MEDIA_URL = "/"
structlog.configure_once(

View File

@ -1,5 +1,6 @@
"""passbook URL Configuration"""
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import include, path
from django.views.generic import RedirectView
@ -60,4 +61,10 @@ urlpatterns += [
if settings.DEBUG:
import debug_toolbar
urlpatterns = [path("-/debug/", include(debug_toolbar.urls))] + urlpatterns
urlpatterns = (
[
path("-/debug/", include(debug_toolbar.urls)),
]
+ static("/media/", document_root=settings.MEDIA_ROOT)
+ urlpatterns
)

View File

@ -6498,9 +6498,11 @@ definitions:
type: string
format: uri
maxLength: 200
meta_icon_url:
title: Meta icon url
meta_icon:
title: Meta icon
type: string
readOnly: true
format: uri
meta_description:
title: Meta description
type: string

View File

@ -4,6 +4,14 @@ title: Kubernetes installation
For a mid to high-load installation, Kubernetes is recommended. passbook is installed using a helm-chart.
To install passbook using the helm chart, run these commands:
```
helm repo add passbook https://docker.beryju.org/chartrepo/passbook
helm repo update
helm repo install passbook/passbook --devel -f values.yaml
```
This installation automatically applies database migrations on startup. After the installation is done, you can use `pbadmin` as username and password.
```yaml

View File

@ -0,0 +1,21 @@
---
title: Upgrading to 0.13
---
**WIP**
# TODO: Changelog for 0.13
## Upgrading
### docker-compose
Docker-compose users should download the latest docker-compose file from [here](https://raw.githubusercontent.com/BeryJu/passbook/master/docker-compose.yml).
This includes a new shared volume, which is used for file Uploads.
Afterwards, you can simply run `docker-compose up -d` and then the normal upgrade command of `docker-compose run --rm server migrate`.
### Kubernetes
The Helm chart contains a new PVC which is used to store all the files uploaded by users. This PVC is shared between the Server pods and the static pods.