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:
parent
91e9f176a5
commit
665839133f
|
@ -197,5 +197,6 @@ local.env.yml
|
|||
**/charts/*.tgz
|
||||
|
||||
# Selenium Screenshots
|
||||
selenium_screenshots/**
|
||||
selenium_screenshots/
|
||||
backups/
|
||||
media/
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -22,7 +22,7 @@ class ApplicationSerializer(ModelSerializer):
|
|||
"slug",
|
||||
"provider",
|
||||
"meta_launch_url",
|
||||
"meta_icon_url",
|
||||
"meta_icon",
|
||||
"meta_description",
|
||||
"meta_publisher",
|
||||
"policies",
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
|
|
|
@ -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/"
|
||||
),
|
||||
),
|
||||
]
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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">
|
|
@ -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">*</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 }}">
|
||||
|
|
|
@ -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>/",
|
||||
|
|
|
@ -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"] = []
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
Reference in New Issue