Skip to content

Commit

Permalink
feat: add tooltip with Plex token instructions (#120)
Browse files Browse the repository at this point in the history
  • Loading branch information
phob authored Feb 11, 2025
2 parents 14705ad + 1393f56 commit 8603173
Show file tree
Hide file tree
Showing 19 changed files with 397 additions and 188 deletions.
86 changes: 18 additions & 68 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -1,102 +1,52 @@
name: Docker

# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
name: Docker Image CI

on:
push:
branches: [ "main" ]
# Publish semver tags as releases.
tags: [ 'v*.*.*' ]
release:
types: [published]
workflow_dispatch:

env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}


jobs:
build:

build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
# This is used to complete the identity challenge
# with sigstore/fulcio when running outside of PRs.
id-token: write


steps:
- name: Checkout repository
uses: actions/checkout@v4

# Install the cosign tool except on PR
# https://github.com/sigstore/cosign-installer
- name: Install cosign
if: github.event_name != 'pull_request'
uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 #v3.5.0
with:
cosign-release: 'v2.2.4'

# Set up BuildKit Docker container builder to be able to build
# multi-platform images and export cache
# https://github.com/docker/setup-buildx-action
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
uses: docker/setup-buildx-action@v3

# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@96383f45573cb7f253c731d3b3ab81c87ef81934 # v5.0.0
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix=,suffix=,format=short
type=raw,value=latest,enable={{is_default_branch}}
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@0565240e2d4ab88bba5387d719585280857ece09 # v5.0.0
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

# Sign the resulting Docker image digest except on PRs.
# This will only write to the public Rekor transparency log when the Docker
# repository is public to avoid leaking data. If you would like to publish
# transparency data even for private images, pass --force to cosign below.
# https://github.com/sigstore/cosign
- name: Sign the published Docker image
if: ${{ github.event_name != 'pull_request' }}
env:
# https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable
TAGS: ${{ steps.meta.outputs.tags }}
DIGEST: ${{ steps.build-and-push.outputs.digest }}
# This step uses the identity token to provision an ephemeral certificate
# against the sigstore community Fulcio instance.
run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}

- name: Cleanup Docker
uses: dataaxiom/ghcr-cleanup-action@v1
with:
token: ${{ secrets.MY_PAT }}
delete-untagged: true
delete-ghost-images: true
delete-orphaned-images: true
103 changes: 103 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,47 @@ MediaFlick is a media management tool that allows you to scan your media library

## Installation

MediaFlick can be installed in multiple ways:

### Docker Installation (Recommended)

#### Using Docker Run

```bash
docker run -d \
--name mediaflick \
-p 3000:3000 \
-v /mnt/zurg:/mnt/zurg \
-v /opt/mediaflick/config:/config \
-v /opt/mediaflick/logs:/logs \
ghcr.io/phob/mediaflick:latest
```

#### Using Docker Compose

Create a `docker-compose.yml` file:

```yaml
services:
mediaflick:
image: ghcr.io/phob/mediaflick:latest
container_name: mediaflick
ports:
- "3000:3000"
volumes:
- /mnt/zurg:/mnt/zurg
- /opt/mediaflick/config:/config
- /opt/mediaflick/logs:/logs
restart: unless-stopped
```
Then run:
```bash
docker-compose up -d
```

### Manual Installation

MediaFlick consists of two components: a backend service (.NET) and a frontend web application (Next.js). Both need to be installed and running for the application to work properly.

### Backend Installation
Expand Down Expand Up @@ -91,3 +132,65 @@ After installation, you'll need to configure both components:
2. Frontend configuration can be done through the web interface under Settings.

For detailed configuration options, please refer to the [Configuration Guide](docs/configuration.md).

### Saltbox Installation

For Saltbox users, you can integrate MediaFlick with Authelia authentication and Traefik. Create a `docker-compose.yml` file in your `/opt/mediaflick/` directory:

```yaml
services:
mediaflick:
restart: unless-stopped
container_name: mediaflick
image: ghcr.io/phob/mediaflick:latest
hostname: mediaflick
environment:
- PUID=1000
- PGID=1000
- TZ=Etc/UTC
networks:
- saltbox
labels:
com.github.saltbox.saltbox_managed: true
traefik.enable: true
traefik.http.routers.mediaflick-http.entrypoints: web
traefik.http.routers.mediaflick-http.middlewares: globalHeaders@file,redirect-to-https@docker,robotHeaders@file,cloudflarewarp@docker,authelia@docker
traefik.http.routers.mediaflick-http.rule: Host(`mediaflick.yourdomain.com`)
traefik.http.routers.mediaflick-http.service: mediaflick
traefik.http.routers.mediaflick.entrypoints: websecure
traefik.http.routers.mediaflick.middlewares: globalHeaders@file,secureHeaders@file,robotHeaders@file,cloudflarewarp@docker,authelia@docker
traefik.http.routers.mediaflick.rule: Host(`mediaflick.yourdomain.com`)
traefik.http.routers.mediaflick.service: mediaflick
traefik.http.routers.mediaflick.tls.certresolver: cfdns
traefik.http.routers.mediaflick.tls.options: securetls@file
traefik.http.services.mediaflick.loadbalancer.server.port: 3000
volumes:
- /opt/mediaflick/config:/config
- /opt/mediaflick/logs:/logs
- /mnt/unionfs:/mnt/unionfs
- /etc/localtime:/etc/localtime:ro
- /mnt/zurg:/mnt/zurg
- /mnt/organized:/mnt/organized

networks:
saltbox:
external: true
```
Then create the necessary directories and set permissions:
```bash
mkdir -p /opt/mediaflick/{config,logs}
chown -R 1000:1000 /opt/mediaflick
```

Start the container:

```bash
cd /opt/mediaflick
docker-compose up -d
```

MediaFlick will be available at `https://mediaflick.yourdomain.com` with Authelia authentication through Traefik.

**Note**: Make sure you have properly configured your Saltbox installation with Traefik and Authelia before proceeding.
21 changes: 10 additions & 11 deletions mediaflick/src/components/media-search/media-search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@ export function MediaSearch({ mediaType, onMediaSelect, className, label = "Sear
<PopoverTrigger asChild>
<Button
variant="outline"
type="button"
aria-haspopup="listbox"
role="combobox"
aria-expanded={open}
className="w-full justify-between"
>
Expand All @@ -95,26 +94,26 @@ export function MediaSearch({ mediaType, onMediaSelect, className, label = "Sear
) : (
searchResults.map((item) => (
<CommandItem
key={item.TmdbId}
value={item.TmdbId.toString()}
onSelect={() => handleMediaSelect(item.TmdbId, item.Title)}
key={item.tmdbId}
value={item.tmdbId.toString()}
onSelect={() => handleMediaSelect(item.tmdbId, item.title)}
className="flex items-center gap-2 p-2"
>
{item.PosterPath && (
{item.posterPath && (
<div className="relative h-[69px] w-[46px] shrink-0">
<Image
src={getTMDBImageUrl(item.PosterPath, "w92") ?? ""}
alt={item.Title}
src={getTMDBImageUrl(item.posterPath, "w92") ?? ""}
alt={item.title}
fill
sizes="46px"
className="rounded object-cover"
/>
</div>
)}
<div className="flex flex-col">
<span className="font-medium">{item.Title}</span>
{item.Year && (
<span className="text-sm text-muted-foreground">({item.Year})</span>
<span className="font-medium">{item.title}</span>
{item.year && (
<span className="text-sm text-muted-foreground">({item.year})</span>
)}
</div>
</CommandItem>
Expand Down
25 changes: 23 additions & 2 deletions mediaflick/src/components/settings/plex-config.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Input } from "@/components/ui/input"
import { ConfigurationPayload } from "@/lib/api/types"
import { SectionTitle } from "./section-title"
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
import { Info } from "lucide-react"

type PlexConfigProps = {
config: ConfigurationPayload
Expand Down Expand Up @@ -45,10 +47,29 @@ const PlexConfig = ({ config, onConfigChange }: PlexConfigProps) => {
</div>
</div>
<div className="space-y-2">
<label htmlFor="plex-token" className="text-sm text-gray-400">Plex Token</label>
<div className="flex items-center gap-2">
<label htmlFor="plex-token" className="text-sm text-gray-400">Plex Token</label>
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Info className="h-4 w-4 text-gray-500" />
</TooltipTrigger>
<TooltipContent className="max-w-[300px] p-4">
<p className="text-sm">To get your Plex token:</p>
<ol className="list-decimal ml-4 mt-2 text-sm space-y-1">
<li>Sign in to Plex Web App</li>
<li>Click any media item</li>
<li>Click the three dots (⋯) and select &quot;Get Info&quot;</li>
<li>Click &quot;View XML&quot; in the lower-left corner</li>
<li>Find &quot;X-Plex-Token=&quot; in the URL</li>
</ol>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<Input
id="plex-token"
type="password"
type="text"
value={config.plex.plexToken}
onChange={(e) => onConfigChange({
...config,
Expand Down
2 changes: 1 addition & 1 deletion mediaflick/src/components/settings/tmdb-config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const TMDbConfig = ({ config, onConfigChange }: TMDbConfigProps) => {
<label htmlFor="tmdb-api-key" className="text-sm text-gray-400">API Key</label>
<Input
id="tmdb-api-key"
type="password"
type="text"
value={config.tmDb.apiKey}
onChange={(e) => onConfigChange({
...config,
Expand Down
2 changes: 0 additions & 2 deletions mediaflick/src/components/ui/popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
Expand All @@ -26,7 +25,6 @@ const PopoverContent = React.forwardRef<
)}
{...props}
/>
</PopoverPrimitive.Portal>
))
PopoverContent.displayName = PopoverPrimitive.Content.displayName

Expand Down
20 changes: 10 additions & 10 deletions mediaflick/src/lib/api/types.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
export interface MediaSearchResult {
Title: string
Year?: number
Genres: string[]
TmdbId: number
PosterPath: string
BackdropPath: string
Overview: string
ReleaseDate: string
Runtime: number
Rating: number
title: string
year?: number
genres: string[]
tmdbId: number
posterPath: string
backdropPath: string
overview: string
releaseDate: string
runtime: number
rating: number
}

export interface MediaInfo {
Expand Down
1 change: 1 addition & 0 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
</PackageVersion>
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="9.0.1" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="9.1.0" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.1" />
<PackageVersion Include="NetEscapades.Configuration.Yaml" Version="3.1.0" />
<PackageVersion Include="NSubstitute" Version="5.3.0" />
Expand Down
Loading

0 comments on commit 8603173

Please sign in to comment.