') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); GitHub - MBcom/kioskmanager: Kioskmanager is a web application built with Django for managing and displaying digital content (videos, websites) on multiple screens or browsers, often referred to as kiosks or digital signage displays. · GitHub
Skip to content

Repository files navigation

Kioskmanager

Do you have any screens in kiosk mode, any content managers which want to display websites and videos on your kiosk screens and do you want to empower them to do it by themselfes? Kioskmanager is the right app to do that.

Kiosk Manager logo

Kioskmanager is a web application built with Django for managing and displaying digital content (videos, websites) on multiple screens or browsers, often referred to as kiosks or digital signage displays.

It provides a central admin interface to upload content, organize it into playlists assigned to specific display groups, and manage user access. Browsers connecting to the player endpoint are automatically identified and play the content loop assigned to their group.

Features

  • Content Management: Upload videos and register website URLs via a web-based admin portal.
  • Website Duration: Specify display duration for website content items.
  • Display Groups: Create groups and assign ordered playlists of content (videos/websites) to them.
  • Browser Identification: Player endpoints automatically identify connecting browsers using a persistent local identifier.
  • Group Assignment: Assign identified browsers to specific Display Groups via the admin panel.
  • Looping Playback: Player endpoint (/play/) automatically loops through the assigned group's playlist.
  • Role-Based Access: Django admin with user management and permissions allowing non-superusers to manage content for specific groups.
  • Persistent Media Storage: Uses Kubernetes Persistent Volumes to store uploaded video files.
  • Database Backend: Utilizes PostgreSQL for storing application data.
  • Helm Deployment: Includes a Helm chart for simplified deployment on Kubernetes.
  • Video Caching: Nginx Ingress configuration snippet included for long-term caching of video assets.
  • SSO: Login and manage users and groups using an OIDC provider. See

Kiosk Manager promotion example

Getting Started

For detailed documentation for both administrators and end users, please visit Kioskmanager Documentation.

For deployment instructions, you can follow the steps outlined in this README.

Prerequisites for Deployment

  • Kubernetes cluster (v1.19+ recommended).
  • Helm v3.0+.
  • Nginx Ingress Controller installed and configured in your cluster.
  • A Persistent Volume (PV) provisioner available in your cluster (required if persistence.enabled is true, which is the default).

Installation using Helm

  1. Add the Helm repo:

    helm repo add kioskmanager https://mbcom.github.io/kioskmanager/
    helm repo update
  2. Configure Values:

    • Review and customize values.yaml for your environment.
    • Crucially, you need to set required values like the admin password (adminPassword), database password(s) (postgresql.auth.password or externalDatabase.password), and ingress host (ingress.hosts[0].host).
    • It is strongly recommended to manage sensitive values like passwords using --set flags during installation or dedicated Kubernetes secrets, rather than committing them directly to values.yaml.
  3. Install the Chart:

    • Example using internal PostgreSQL:

      helm install kioskmanager kioskmanager/kioskmanager \
      --namespace <your-namespace> \
      --create-namespace \
      --set ingress.hosts[0].host=kioskmanager.yourdomain.com \
      --set adminPassword=<your-secure-admin-password> \
      --set postgresql.auth.password=<your-secure-db-password>
    • Example using external PostgreSQL:

      helm install kioskmanager kioskmanager/kioskmanager \
      --namespace <your-namespace> \
      --create-namespace \
      --set ingress.hosts[0].host=kioskmanager.yourdomain.com \
      --set adminPassword=<your-secure-admin-password> \
      --set postgresql.enabled=false \
      --set externalDatabase.host=<your-external-db-host> \
      --set externalDatabase.username=<your-external-db-user> \
      --set externalDatabase.password=<your-secure-external-db-password> \
      --set externalDatabase.database=<your-external-db-name>
  4. Verify Deployment: Check the status of the pods, services, ingress, and persistent volume claims:

    kubectl get all -n <your-namespace>
    kubectl get pvc -n <your-namespace>
    kubectl logs deployment/<release-name>-kioskmanager -n <your-namespace># Check application logs

Authentication

Kioskmanager supports two authentication methods for accessing the admin portal, configurable via the Helm chart (auth.method):

  1. Standard Django Login: Uses Django's built-in username and password authentication.
  2. OpenID Connect (OIDC): Allows integration with an external Identity Provider (IdP). When enabled, users can log in via the IdP, and their Django user accounts can be automatically created and updated based on OIDC claims, including group memberships.

Refer to the Admin Guide's authentication section and the Helm chart values for detailed OIDC configuration.

Configuration Values

The following table lists the configurable parameters of the Kioskmanager Helm chart and their default values.

ParameterDescriptionDefault Value
replicaCountNumber of Kioskmanager application pods.1
image.repositoryRequired. Docker image repository for the Kioskmanager application.ghcr.io/mbcom/kioskmanager
image.pullPolicyImage pull policy.IfNotPresent
image.tagImage tag. Defaults to .Chart.AppVersion if not set.""
imagePullSecretsList of secrets for pulling images from private registries.[]
nameOverrideOverride the chart name component for resource names.""
fullnameOverrideFully override the generated resource names.""
secretKeyDjango SECRET_KEY. If empty, a random one is generated and stored in the K8s Secret.""
adminUserUsername for the admin user created by the startup script.admin
adminPasswordRequired. Password for the admin user. Set via --set or values file. Stored in K8s Secret.""
podAnnotationsAnnotations to add to the application pods.{}
podSecurityContextSecurity context for the application pods.{}
securityContextSecurity context for the application container.{}
service.typeKubernetes Service type.ClusterIP
service.portPort the Kubernetes Service listens on.80
service.targetPortPort the application container listens on (e.g., Gunicorn/uWSGI port). Must match app config.8000
ingress.enabledEnable Ingress resource creation. Requires an Ingress Controller (like Nginx).true
ingress.classNameIngress Class Name for the Ingress controller.nginx
ingress.annotationsAdditional annotations for the Ingress resource (e.g., cert-manager, custom snippets).See values.yaml for defaults
ingress.hostsList of host configurations for the Ingress. Required.[{host: chart-example.local, paths: [{path: /, pathType: Prefix}]}]
ingress.hosts[].hostRequired. Hostname to route traffic from.chart-example.local
ingress.hosts[].pathsList of paths for the host.[{path: /, pathType: Prefix}]
ingress.tlsTLS configuration for Ingress (list of secrets and hosts).[]
persistence.enabledEnable PersistentVolumeClaim for media storage. Requires a PV provisioner.true
persistence.storageClassStorageClass name for the PVC. Use "-" for default or specify name. Leave empty to use cluster default.nil
persistence.accessModesAccess modes for the PVC.[ReadWriteOnce]
persistence.sizeSize of the Persistent Volume for media files.10Gi
persistence.mountPathPath inside the container where the media volume is mounted. Must align with Django's MEDIA_ROOT./app/media
postgresql.enabledEnable deployment of the Bitnami PostgreSQL chart as a dependency.true
postgresql.auth.databaseDatabase name for internal PostgreSQL.videolooperdb
postgresql.auth.usernameUsername for internal PostgreSQL.videolooperuser
postgresql.auth.passwordRequired if postgresql.enabled=true. Password for internal PostgreSQL. Set via --set.""
postgresql.*Other Bitnami PostgreSQL chart values can be nested here (e.g., primary.persistence.enabled).See Bitnami chart docs
externalDatabase.hostRequired if postgresql.enabled=false. Hostname or IP of the external PostgreSQL server.""
externalDatabase.portPort of the external PostgreSQL server.5432
externalDatabase.databaseRequired if postgresql.enabled=false. Database name on the external server.videolooperdb
externalDatabase.usernameRequired if postgresql.enabled=false. Username for the external database.videolooperuser
externalDatabase.passwordRequired if postgresql.enabled=false. Password for the external database. Set via --set.""
resourcesCPU/Memory resource requests and limits for the application pods.{} (no defaults)
nodeSelectorNode selector constraints for pod assignment.{}
tolerationsTolerations for pod assignment.[]
affinityAffinity rules for pod assignment.{}
auth.methodAuthentication method: standard (Django default) or oidc (OpenID Connect).standard
oidc.rpClientIdRequired if auth.method=oidc. Client ID for Kioskmanager registered with the OIDC provider.""
oidc.rpClientSecretRequired if auth.method=oidc. Client Secret. Set via --set or a secure values file.""
oidc.opBaseDiscoveryUrlURL to OIDC provider's .well-known/openid-configuration for auto-discovery of endpoints. E.g. https://idp.example.com/auth/realms/myrealm""
oidc.opBaseDiscoveryUrlURL to OIDC provider's .well-known/openid-configuration for auto-discovery of endpoints.""
oidc.opAuthorizationEndpointOIDC Authorization Endpoint URL (if opBaseDiscoveryUrl not used).""
oidc.opTokenEndpointOIDC Token Endpoint URL (if opBaseDiscoveryUrl not used).""
oidc.opUserEndpointOIDC UserInfo Endpoint URL (if opBaseDiscoveryUrl not used).""
oidc.opJwksEndpointOIDC JWKS URI for token signature verification (if opBaseDiscoveryUrl not used).""
oidc.opIssuerEndpointOIDC Issuer URL (optional, sometimes needed for validation). (if opBaseDiscoveryUrl not used).""
oidc.rpSignAlgoAlgorithm used by OIDC provider to sign ID tokens (e.g., RS256).RS256
oidc.rpScopesScopes to request (e.g., openid email profile groups).openid email profile groups
oidc.providerNameName displayed on the OIDC login button (e.g., "Corporate SSO").SSO
oidc.createUserAllow creation of new Django users for authenticated OIDC users.true
oidc.updateUserAttributesUpdate user attributes (email, name) from OIDC claims on each login.true
oidc.usernameClaimOIDC claim used for Django username (e.g., email, preferred_username, sub). Must be unique.email
oidc.claimFirstNameOIDC claim for user's first name.given_name
oidc.claimLastNameOIDC claim for user's last name.family_name
oidc.claimEmailOIDC claim for user's email.email
oidc.groupsClaimNameOIDC claim containing a list of user's groups/roles (e.g., groups). Leave empty to disable OIDC group sync.groups
oidc.rpDjangoGroupsSyncEnabledEnable syncing groups from OIDC claim to Django groups.true
oidc.assignContentManagerIf true, all users which login through oidc will be added to predefined Content Manager group.false
oidc.mapSuperuserStatus.enabledEnable mapping a claim to Django is_superuser status.false
oidc.mapSuperuserStatus.claimNameOIDC claim name used for superuser status mapping.roles
oidc.mapSuperuserStatus.claimValueValue in the superuser claim that grants is_superuser=true.kiosk_admin
oidc.loginRedirectUrlDjango URL to redirect to after successful OIDC login./admin/
oidc.logoutRedirectUrlDjango URL to redirect to after OIDC initiated logout (if supported by IdP and library)./
oidc.stateSizeSize of the OIDC 'state' parameter.32
oidc.nonceSizeSize of the OIDC 'nonce' parameter.

Refer to the values.yaml file for detailed default annotations and structure. For parameters related to the Bitnami PostgreSQL subchart (postgresql.*), please consult the official Bitnami PostgreSQL Helm Chart documentation.

| backup.enabled | Enable periodic PostgreSQL backups using a CronJob | true | | backup.schedule | Cron schedule expression for when to run backups | "0 0 * * 0" (Weekly on Sunday) | | backup.storage | Size of the PersistentVolumeClaim used to store backups | 10Gi |

Refer to the values.yaml file for detailed default annotations and structure. For parameters related to the Bitnami PostgreSQL subchart (postgresql.*), please consult the official Bitnami PostgreSQL Helm Chart documentation.

Development Guide

To set up the development environment for Kioskmanager, follow these steps:

Prerequisites:

  • Python 3
  • Node.JS 12 or above for using docs
  • Git
  1. Clone the Repository:

    git clone https://github.com/mbcom/kioskmanager.git
    cd kioskmanager
  2. Set Environment Variables: Ensure the DJANGO_DEBUG environment variable is set to True for local development:

    export DJANGO_DEBUG=True
  3. Install dependencies: Run the following command to install all dependencies:

    cd src
    pip install -r requirements.txt
  4. Run migrations and create a superuser account: Run database migrations to initialize your local sqlite database:

    cd src
    python3 manage.py migrate

    Create a superadmin user. You need to do that only once:

    cd src
    python manage.py createsuperuser
  5. Run the Application Locally: The application uses SQLite as the default database for local development. Start the Django development server:

    cd src
    python manage.py runserver

    You can now go to http://127.0.0.1:8000/ and login with credentials created in step 4.

  6. Serve Documentation Locally: (optional) Navigate to the docs folder and run the following command to serve the documentation locally:

    cd docs
    npm run docs:dev

    The documentation will be available at http://localhost:5173/kioskmanager/.

Happy coding!

About

Kioskmanager is a web application built with Django for managing and displaying digital content (videos, websites) on multiple screens or browsers, often referred to as kiosks or digital signage displays.

Topics

Resources

Stars

2 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages