Compare commits
22 commits
Author | SHA1 | Date | |
---|---|---|---|
asonix | 2be2635eb3 | ||
asonix | 7465e57265 | ||
asonix | 15b30b320d | ||
asonix | 670f37510a | ||
asonix | 89f99df2fb | ||
asonix | fdc3c00eb8 | ||
asonix | 122c17424c | ||
asonix | 976b9851d3 | ||
asonix | bf9d2e8a6e | ||
asonix | d9d8b196a1 | ||
asonix | 7c40c73fdd | ||
asonix | bcdf965d67 | ||
asonix | 94c3400bbf | ||
asonix | 0c66f533d1 | ||
asonix | 03a513e2b2 | ||
asonix | 28b64d9839 | ||
asonix | b9216705d3 | ||
asonix | 1e0c1585d9 | ||
asonix | e2f5f6fae9 | ||
asonix | 8ae9530b17 | ||
asonix | d23ef5f64d | ||
asonix | 6e0d60d854 |
421
.drone.yml
421
.drone.yml
|
@ -1,421 +0,0 @@
|
|||
kind: pipeline
|
||||
type: docker
|
||||
name: clippy
|
||||
|
||||
platform:
|
||||
arch: amd64
|
||||
|
||||
clone:
|
||||
disable: true
|
||||
|
||||
steps:
|
||||
- name: clone
|
||||
image: alpine/git:latest
|
||||
user: root
|
||||
commands:
|
||||
- git clone $DRONE_GIT_HTTP_URL .
|
||||
- git checkout $DRONE_COMMIT
|
||||
- chown -R 991:991 .
|
||||
|
||||
- name: clippy
|
||||
image: asonix/rust-builder:latest-linux-amd64
|
||||
pull: always
|
||||
commands:
|
||||
- rustup component add clippy
|
||||
- cargo clippy -- -D warnings
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
- tag
|
||||
|
||||
---
|
||||
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: tests
|
||||
|
||||
platform:
|
||||
arch: amd64
|
||||
|
||||
clone:
|
||||
disable: true
|
||||
|
||||
steps:
|
||||
- name: clone
|
||||
image: alpine/git:latest
|
||||
user: root
|
||||
commands:
|
||||
- git clone $DRONE_GIT_HTTP_URL .
|
||||
- git checkout $DRONE_COMMIT
|
||||
- chown -R 991:991 .
|
||||
|
||||
- name: tests
|
||||
image: asonix/rust-builder:latest-linux-amd64
|
||||
pull: always
|
||||
commands:
|
||||
- cargo test
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
- tag
|
||||
|
||||
---
|
||||
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: check-amd64
|
||||
|
||||
platform:
|
||||
arch: amd64
|
||||
|
||||
clone:
|
||||
disable: true
|
||||
|
||||
steps:
|
||||
- name: clone
|
||||
image: alpine/git:latest
|
||||
user: root
|
||||
commands:
|
||||
- git clone $DRONE_GIT_HTTP_URL .
|
||||
- git checkout $DRONE_COMMIT
|
||||
- chown -R 991:991 .
|
||||
|
||||
- name: check
|
||||
image: asonix/rust-builder:latest-linux-amd64
|
||||
pull: always
|
||||
commands:
|
||||
- cargo check --target=$TARGET
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
---
|
||||
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: build-amd64
|
||||
|
||||
platform:
|
||||
arch: amd64
|
||||
|
||||
clone:
|
||||
disable: true
|
||||
|
||||
steps:
|
||||
- name: clone
|
||||
image: alpine/git:latest
|
||||
user: root
|
||||
commands:
|
||||
- git clone $DRONE_GIT_HTTP_URL .
|
||||
- git checkout $DRONE_COMMIT
|
||||
- chown -R 991:991 .
|
||||
|
||||
- name: build
|
||||
image: asonix/rust-builder:latest-linux-amd64
|
||||
pull: always
|
||||
commands:
|
||||
- cargo build --target=$TARGET --release
|
||||
- $TOOL-strip target/$TARGET/release/pict-rs-admin
|
||||
- cp target/$TARGET/release/pict-rs-admin .
|
||||
- cp pict-rs-admin pict-rs-admin-linux-amd64
|
||||
|
||||
- name: push
|
||||
image: plugins/docker:20
|
||||
settings:
|
||||
username: asonix
|
||||
password:
|
||||
from_secret: dockerhub_token
|
||||
repo: asonix/pictrs-admin
|
||||
dockerfile: docker/drone/Dockerfile
|
||||
auto_tag: true
|
||||
auto_tag_suffix: linux-amd64
|
||||
build_args:
|
||||
- REPO_ARCH=amd64
|
||||
|
||||
- name: publish
|
||||
image: plugins/gitea-release:1
|
||||
settings:
|
||||
api_key:
|
||||
from_secret: gitea_token
|
||||
base_url: https://git.asonix.dog
|
||||
files:
|
||||
- pict-rs-admin-linux-amd64
|
||||
|
||||
depends_on:
|
||||
- clippy
|
||||
- tests
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- tag
|
||||
|
||||
---
|
||||
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: check-arm64v8
|
||||
|
||||
platform:
|
||||
arch: amd64
|
||||
|
||||
clone:
|
||||
disable: true
|
||||
|
||||
steps:
|
||||
- name: clone
|
||||
image: alpine/git:latest
|
||||
user: root
|
||||
commands:
|
||||
- git clone $DRONE_GIT_HTTP_URL .
|
||||
- git checkout $DRONE_COMMIT
|
||||
- chown -R 991:991 .
|
||||
|
||||
- name: check
|
||||
image: asonix/rust-builder:latest-linux-arm64v8
|
||||
pull: always
|
||||
commands:
|
||||
- cargo check --target=$TARGET
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
---
|
||||
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: build-arm64v8
|
||||
|
||||
platform:
|
||||
arch: amd64
|
||||
|
||||
clone:
|
||||
disable: true
|
||||
|
||||
steps:
|
||||
- name: clone
|
||||
image: alpine/git:latest
|
||||
user: root
|
||||
commands:
|
||||
- git clone $DRONE_GIT_HTTP_URL .
|
||||
- git checkout $DRONE_COMMIT
|
||||
- chown -R 991:991 .
|
||||
|
||||
- name: build
|
||||
image: asonix/rust-builder:latest-linux-arm64v8
|
||||
pull: always
|
||||
commands:
|
||||
- cargo build --target=$TARGET --release
|
||||
- $TOOL-strip target/$TARGET/release/pict-rs-admin
|
||||
- cp target/$TARGET/release/pict-rs-admin .
|
||||
- cp pict-rs-admin pict-rs-admin-linux-arm64v8
|
||||
|
||||
- name: push
|
||||
image: plugins/docker:20
|
||||
settings:
|
||||
username: asonix
|
||||
password:
|
||||
from_secret: dockerhub_token
|
||||
repo: asonix/pictrs-admin
|
||||
dockerfile: docker/drone/Dockerfile
|
||||
auto_tag: true
|
||||
auto_tag_suffix: linux-arm64v8
|
||||
build_args:
|
||||
- REPO_ARCH=arm64v8
|
||||
|
||||
- name: publish
|
||||
image: plugins/gitea-release:1
|
||||
settings:
|
||||
api_key:
|
||||
from_secret: gitea_token
|
||||
base_url: https://git.asonix.dog
|
||||
files:
|
||||
- pict-rs-admin-linux-arm64v8
|
||||
|
||||
depends_on:
|
||||
- clippy
|
||||
- tests
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- tag
|
||||
|
||||
---
|
||||
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: check-arm32v7
|
||||
|
||||
platform:
|
||||
arch: amd64
|
||||
|
||||
clone:
|
||||
disable: true
|
||||
|
||||
steps:
|
||||
- name: clone
|
||||
image: alpine/git:latest
|
||||
user: root
|
||||
commands:
|
||||
- git clone $DRONE_GIT_HTTP_URL .
|
||||
- git checkout $DRONE_COMMIT
|
||||
- chown -R 991:991 .
|
||||
|
||||
- name: check
|
||||
image: asonix/rust-builder:latest-linux-arm32v7
|
||||
pull: always
|
||||
commands:
|
||||
- cargo check --target=$TARGET
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
---
|
||||
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: build-arm32v7
|
||||
|
||||
platform:
|
||||
arch: amd64
|
||||
|
||||
clone:
|
||||
disable: true
|
||||
|
||||
steps:
|
||||
- name: clone
|
||||
image: alpine/git:latest
|
||||
user: root
|
||||
commands:
|
||||
- git clone $DRONE_GIT_HTTP_URL .
|
||||
- git checkout $DRONE_COMMIT
|
||||
- chown -R 991:991 .
|
||||
|
||||
- name: build
|
||||
image: asonix/rust-builder:latest-linux-arm32v7
|
||||
pull: always
|
||||
commands:
|
||||
- cargo build --target=$TARGET --release
|
||||
- $TOOL-strip target/$TARGET/release/pict-rs-admin
|
||||
- cp target/$TARGET/release/pict-rs-admin .
|
||||
- cp pict-rs-admin pict-rs-admin-linux-arm32v7
|
||||
|
||||
- name: push
|
||||
image: plugins/docker:20
|
||||
settings:
|
||||
username: asonix
|
||||
password:
|
||||
from_secret: dockerhub_token
|
||||
repo: asonix/pictrs-admin
|
||||
dockerfile: docker/drone/Dockerfile
|
||||
auto_tag: true
|
||||
auto_tag_suffix: linux-arm32v7
|
||||
build_args:
|
||||
- REPO_ARCH=arm32v7
|
||||
|
||||
- name: publish
|
||||
image: plugins/gitea-release:1
|
||||
settings:
|
||||
api_key:
|
||||
from_secret: gitea_token
|
||||
base_url: https://git.asonix.dog
|
||||
files:
|
||||
- pict-rs-admin-linux-arm32v7
|
||||
|
||||
depends_on:
|
||||
- clippy
|
||||
- tests
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- tag
|
||||
|
||||
---
|
||||
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: manifest
|
||||
|
||||
platform:
|
||||
arch: amd64
|
||||
|
||||
clone:
|
||||
disable: true
|
||||
|
||||
steps:
|
||||
- name: clone
|
||||
image: alpine/git:latest
|
||||
user: root
|
||||
commands:
|
||||
- git clone $DRONE_GIT_HTTP_URL .
|
||||
- git checkout $DRONE_COMMIT
|
||||
- chown -R 991:991 .
|
||||
|
||||
- name: manifest
|
||||
image: plugins/manifest:1
|
||||
settings:
|
||||
username: asonix
|
||||
password:
|
||||
from_secret: dockerhub_token
|
||||
dump: true
|
||||
auto_tag: true
|
||||
ignore_missing: true
|
||||
spec: docker/drone/manifest.tmpl
|
||||
|
||||
|
||||
depends_on:
|
||||
- build-amd64
|
||||
- build-arm64v8
|
||||
- build-arm32v7
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- tag
|
||||
|
||||
---
|
||||
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: publish-crate
|
||||
|
||||
platform:
|
||||
arch: amd64
|
||||
|
||||
clone:
|
||||
disable: true
|
||||
|
||||
steps:
|
||||
- name: clone
|
||||
image: alpine/git:latest
|
||||
user: root
|
||||
commands:
|
||||
- git clone $DRONE_GIT_HTTP_URL .
|
||||
- git checkout $DRONE_COMMIT
|
||||
- chown -R 991:991 .
|
||||
|
||||
- name: publish
|
||||
image: asonix/rust-builder:latest-linux-amd64
|
||||
pull: always
|
||||
environment:
|
||||
CRATES_IO_TOKEN:
|
||||
from_secret: crates_io_token
|
||||
commands:
|
||||
- cargo publish --token $CRATES_IO_TOKEN
|
||||
|
||||
depends_on:
|
||||
- build-amd64
|
||||
- build-arm64v8
|
||||
- build-arm32v7
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- tag
|
61
.forgejo/workflows/check.yaml
Normal file
61
.forgejo/workflows/check.yaml
Normal file
|
@ -0,0 +1,61 @@
|
|||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
clippy:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: docker.io/asonix/actions-base-image:0.1
|
||||
steps:
|
||||
-
|
||||
name: Checkout pict-rs-admin
|
||||
uses: https://github.com/actions/checkout@v4
|
||||
-
|
||||
name: Cargo Cache
|
||||
uses: https://git.asonix.dog/asonix/actions/cache-rust-dependencies@main
|
||||
-
|
||||
name: Clippy
|
||||
run: |
|
||||
cargo clippy --no-default-features -- -D warnings
|
||||
|
||||
tests:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: docker.io/asonix/actions-base-image:0.1
|
||||
steps:
|
||||
-
|
||||
name: Checkout pict-rs-admin
|
||||
uses: https://github.com/actions/checkout@v4
|
||||
-
|
||||
name: Cargo Cache
|
||||
uses: https://git.asonix.dog/asonix/actions/cache-rust-dependencies@main
|
||||
-
|
||||
name: Test
|
||||
run: cargo test
|
||||
|
||||
check:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- x86_64-unknown-linux-musl
|
||||
- armv7-unknown-linux-musleabihf
|
||||
- aarch64-unknown-linux-musl
|
||||
runs-on: docker
|
||||
container:
|
||||
image: docker.io/asonix/actions-base-image:0.1
|
||||
steps:
|
||||
-
|
||||
name: Checkout pict-rs-admin
|
||||
uses: https://github.com/actions/checkout@v4
|
||||
-
|
||||
name: Cargo Cache
|
||||
uses: https://git.asonix.dog/asonix/actions/cache-rust-dependencies@main
|
||||
-
|
||||
name: Debug builds
|
||||
run: cargo zigbuild --target ${{ matrix.target }}
|
225
.forgejo/workflows/publish.yaml
Normal file
225
.forgejo/workflows/publish.yaml
Normal file
|
@ -0,0 +1,225 @@
|
|||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
|
||||
env:
|
||||
REGISTRY_IMAGE: asonix/pictrs-admin
|
||||
|
||||
jobs:
|
||||
clippy:
|
||||
runs-on: base-image
|
||||
container:
|
||||
image: docker.io/asonix/actions-base-image:0.1
|
||||
steps:
|
||||
-
|
||||
name: Checkout pict-rs-admin
|
||||
uses: https://github.com/actions/checkout@v4
|
||||
-
|
||||
name: Cargo Cache
|
||||
uses: https://git.asonix.dog/asonix/actions/cache-rust-dependencies@main
|
||||
-
|
||||
name: Clippy
|
||||
run: |
|
||||
cargo clippy --no-default-features -- -D warnings
|
||||
|
||||
tests:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: docker.io/asonix/actions-base-image:0.1
|
||||
steps:
|
||||
-
|
||||
name: Checkout pict-rs-admin
|
||||
uses: https://github.com/actions/checkout@v4
|
||||
-
|
||||
name: Cargo Cache
|
||||
uses: https://git.asonix.dog/asonix/actions/cache-rust-dependencies@main
|
||||
-
|
||||
name: Test
|
||||
run: cargo test
|
||||
|
||||
build:
|
||||
needs:
|
||||
- clippy
|
||||
- tests
|
||||
runs-on: docker
|
||||
container:
|
||||
image: docker.io/asonix/actions-base-image:0.1
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
info:
|
||||
- target: x86_64-unknown-linux-musl
|
||||
artifact: linux-amd64
|
||||
platform: linux/amd64
|
||||
- target: armv7-unknown-linux-musleabihf
|
||||
artifact: linux-arm32v7
|
||||
platform: linux/arm/v7
|
||||
- target: aarch64-unknown-linux-musl
|
||||
artifact: linux-arm64v8
|
||||
platform: linux/arm64
|
||||
steps:
|
||||
-
|
||||
name: Checkout pict-rs-admin
|
||||
uses: https://github.com/actions/checkout@v4
|
||||
-
|
||||
name: Cargo Cache
|
||||
uses: https://git.asonix.dog/asonix/actions/cache-rust-dependencies@main
|
||||
-
|
||||
name: Prepare Platform
|
||||
run: |
|
||||
platform=${{ matrix.info.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
uses: https://github.com/docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY_IMAGE }}
|
||||
flavor: |
|
||||
latest=auto
|
||||
suffix=-${{ matrix.info.artifact }}
|
||||
tags: |
|
||||
type=raw,value=latest,enable={{ is_default_branch }}
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: https://github.com/docker/setup-qemu-action@v3
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: https://github.com/docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Docker login
|
||||
uses: https://github.com/docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
-
|
||||
name: Compile pict-rs-admin
|
||||
run: cargo zigbuild --target ${{ matrix.info.target }} --release
|
||||
-
|
||||
name: Prepare artifacts
|
||||
run: |
|
||||
mkdir artifacts
|
||||
cp target/${{ matrix.info.target }}/release/pict-rs-admin artifacts/pict-rs-admin-${{ matrix.info.artifact }}
|
||||
-
|
||||
uses: https://github.com/actions/upload-artifact@v3
|
||||
with:
|
||||
name: binaries
|
||||
path: artifacts/
|
||||
-
|
||||
name: Prepare binary
|
||||
run: |
|
||||
cp target/${{ matrix.info.target }}/release/pict-rs-admin docker/forgejo/pict-rs-admin
|
||||
-
|
||||
name: Build and push ${{ matrix.info.platform }} docker image
|
||||
id: build
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./docker/forgejo
|
||||
platforms: ${{ matrix.info.platform }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
outputs: type=image,name=${{ env.REGISTRY_IMAGE }},name-canonical=true,push=true
|
||||
-
|
||||
name: Export digest
|
||||
run: |
|
||||
mkdir -p /tmp/digests
|
||||
digest="${{ steps.build.outputs.digest }}"
|
||||
touch "/tmp/digests/${digest#sha256:}"
|
||||
echo "Created /tmp/digests/${digest#sha256:}"
|
||||
shell: bash
|
||||
-
|
||||
name: Upload ${{ matrix.info.platform }} digest
|
||||
uses: https://github.com/actions/upload-artifact@v3
|
||||
with:
|
||||
name: digests
|
||||
path: /tmp/digests/*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
publish-docker:
|
||||
runs-on: docker
|
||||
container:
|
||||
image: docker.io/asonix/actions-base-image:0.1
|
||||
needs: [build]
|
||||
steps:
|
||||
-
|
||||
name: Download digests
|
||||
uses: https://github.com/actions/download-artifact@v3
|
||||
with:
|
||||
name: digests
|
||||
path: /tmp/digests
|
||||
pattern: digests-*
|
||||
merge-multiple: true
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Docker login
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
uses: https://github.com/docker/metadata-action@v5
|
||||
with:
|
||||
images: ${{ env.REGISTRY_IMAGE }}
|
||||
flavor: |
|
||||
latest=auto
|
||||
tags: |
|
||||
type=raw,value=latest,enable={{ is_default_branch }}
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
-
|
||||
name: Create manifest list and push
|
||||
working-directory: /tmp/digests
|
||||
run: |
|
||||
tags=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "${DOCKER_METADATA_OUTPUT_JSON}")
|
||||
images=$(printf "${{ env.REGISTRY_IMAGE }}@sha256:%s " *)
|
||||
echo "Running 'docker buildx imagetools create ${tags[@]} ${images[@]}'"
|
||||
docker buildx imagetools create ${tags[@]} ${images[@]}
|
||||
shell: bash
|
||||
-
|
||||
name: Inspect Image
|
||||
run: |
|
||||
docker buildx imagetools inspect ${{ env.REGISTRY_IMAGE }}:${{ steps.meta.outputs.version }}
|
||||
|
||||
publish-forgejo:
|
||||
needs: [build]
|
||||
runs-on: docker
|
||||
container:
|
||||
image: docker.io/asonix/actions-base-image:0.1
|
||||
steps:
|
||||
- uses: https://github.com/actions/download-artifact@v3
|
||||
with:
|
||||
name: binaries
|
||||
path: artifacts/
|
||||
merge-multiple: true
|
||||
- uses: actions/forgejo-release@v1
|
||||
with:
|
||||
direction: upload
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
release-dir: artifacts/
|
||||
|
||||
publish-crate:
|
||||
needs: [build]
|
||||
runs-on: docker
|
||||
container:
|
||||
image: docker.io/asonix/actions-base-image:0.1
|
||||
steps:
|
||||
-
|
||||
name: Checkout pict-rs-admin
|
||||
uses: https://github.com/actions/checkout@v4
|
||||
-
|
||||
name: Cargo Cache
|
||||
uses: https://git.asonix.dog/asonix/actions/cache-rust-dependencies@main
|
||||
-
|
||||
name: Publish Crate
|
||||
run: cargo publish --token ${{ secrets.CRATES_IO_TOKEN }}
|
1697
Cargo.lock
generated
1697
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
29
Cargo.toml
29
Cargo.toml
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "pict-rs-admin"
|
||||
description = "An example pict-rs admin tool"
|
||||
version = "0.1.1"
|
||||
version = "0.2.0"
|
||||
authors = ["asonix <asonix@asonix.dog>"]
|
||||
license = "AGPL-3.0"
|
||||
readme = "README.md"
|
||||
|
@ -12,25 +12,34 @@ build = "src/build.rs"
|
|||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[profile.release]
|
||||
strip = true
|
||||
|
||||
[build-dependencies]
|
||||
ructe = "0.17.0"
|
||||
|
||||
[dependencies]
|
||||
actix-web = { version = "4.3.1", default-features = false, features = ["macros"] }
|
||||
actix-web = { version = "4.3.1", default-features = false, features = ["macros", "rustls-0_21"] }
|
||||
clap = { version = "4.4.1", features = ["derive", "env"] }
|
||||
color-eyre = "0.6.2"
|
||||
console-subscriber = "0.1.10"
|
||||
minify-html = "0.11.1"
|
||||
opentelemetry = { version = "0.20.0", features = ["rt-tokio"] }
|
||||
opentelemetry-otlp = "0.13.0"
|
||||
console-subscriber = "0.2.0"
|
||||
minify-html = "0.15.0"
|
||||
opentelemetry = "0.21.0"
|
||||
opentelemetry_sdk = { version = "0.21.0", features = ["rt-tokio"] }
|
||||
opentelemetry-otlp = "0.14.0"
|
||||
reqwest = { version = "0.11.20", default-features = false, features = ["json", "rustls-tls", "stream"] }
|
||||
reqwest-middleware = "0.2.3"
|
||||
reqwest-tracing = { version = "0.4.6", features = ["opentelemetry_0_20"] }
|
||||
reqwest-tracing = { version = "0.4.6", features = ["opentelemetry_0_21"] }
|
||||
rustls_021 = { package = "rustls", version = "0.21" }
|
||||
rustls = "0.22"
|
||||
rustls-channel-resolver = "0.1.0"
|
||||
rustls-pemfile = "2.0.0"
|
||||
serde = { version = "1.0.188", features = ["derive"] }
|
||||
tokio = { version = "1", features = ["fs"] }
|
||||
tracing = "0.1.37"
|
||||
tracing-actix-web = { version = "0.7.6", features = ["opentelemetry_0_20", "emit_event_on_error"] }
|
||||
tracing-actix-web = { version = "0.7.6", features = ["opentelemetry_0_21", "emit_event_on_error"] }
|
||||
tracing-error = "0.2.0"
|
||||
tracing-log = "0.1.3"
|
||||
tracing-opentelemetry = "0.20.0"
|
||||
tracing-log = "0.2.0"
|
||||
tracing-opentelemetry = "0.22.0"
|
||||
tracing-subscriber = { version = "0.3.17", features = ["ansi", "env-filter", "fmt"] }
|
||||
url = "2.4.1"
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# pict-rs-admin
|
||||
_A demo application showing off pict-rs administration capibilities_
|
||||
|
||||
![Application](https://git.asonix.dog/asonix/pict-rs-admin/raw/branch/main/images/example.png)
|
||||
|
||||
### Note: pict-rs-admin is not intended to be exposed to the internet.
|
||||
|
||||
## Running
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
ARG REPO_ARCH
|
||||
|
||||
FROM asonix/rust-runner:latest-linux-$REPO_ARCH
|
||||
|
||||
COPY pict-rs-admin /usr/local/bin/pict-rs-admin
|
||||
|
||||
USER app
|
||||
EXPOSE 8080
|
||||
EXPOSE 6669
|
||||
VOLUME /mnt
|
||||
ENTRYPOINT ["/sbin/tini", "--"]
|
||||
CMD ["/usr/local/bin/pict-rs-admin"]
|
|
@ -1,25 +0,0 @@
|
|||
image: asonix/pictrs-admin:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
||||
{{#if build.tags}}
|
||||
tags:
|
||||
{{#each build.tags}}
|
||||
- {{this}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
manifests:
|
||||
-
|
||||
image: asonix/pictrs-admin:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
|
||||
platform:
|
||||
architecture: amd64
|
||||
os: linux
|
||||
-
|
||||
image: asonix/pictrs-admin:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64v8
|
||||
platform:
|
||||
architecture: arm64
|
||||
os: linux
|
||||
variant: v8
|
||||
-
|
||||
image: asonix/pictrs-admin:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm32v7
|
||||
platform:
|
||||
architecture: arm
|
||||
os: linux
|
||||
variant: v7
|
24
docker/forgejo/Dockerfile
Normal file
24
docker/forgejo/Dockerfile
Normal file
|
@ -0,0 +1,24 @@
|
|||
FROM alpine:3.19
|
||||
|
||||
ARG UID=991
|
||||
ARG GID=991
|
||||
|
||||
ENV \
|
||||
UID=${UID} \
|
||||
GID=${GID}
|
||||
|
||||
USER root
|
||||
RUN \
|
||||
addgroup -g "${GID}" app && \
|
||||
adduser -D -G app -u "${UID}" -g "" -h /opt/app app && \
|
||||
apk add tini && \
|
||||
chown -R app:app /mnt
|
||||
|
||||
COPY pict-rs-admin /usr/local/bin/pict-rs-admin
|
||||
|
||||
USER app
|
||||
EXPOSE 6669
|
||||
EXPOSE 8080
|
||||
VOLUME /mnt
|
||||
ENTRYPOINT ["/sbin/tini", "--"]
|
||||
CMD ["/usr/local/bin/pict-rs-admin"]
|
|
@ -8,7 +8,7 @@ services:
|
|||
- ./volumes/pictrs:/mnt
|
||||
|
||||
pictrs-admin:
|
||||
image: asonix/pictrs-admin:0.1.0
|
||||
image: asonix/pictrs-admin:0.2
|
||||
ports:
|
||||
- "8084:8084"
|
||||
restart: always
|
||||
|
|
12
flake.lock
12
flake.lock
|
@ -5,11 +5,11 @@
|
|||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1692799911,
|
||||
"narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=",
|
||||
"lastModified": 1705309234,
|
||||
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44",
|
||||
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -20,11 +20,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1693158576,
|
||||
"narHash": "sha256-aRTTXkYvhXosGx535iAFUaoFboUrZSYb1Ooih/auGp0=",
|
||||
"lastModified": 1706550542,
|
||||
"narHash": "sha256-UcsnCG6wx++23yeER4Hg18CXWbgNpqNXcHIo5/1Y+hc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a999c1cc0c9eb2095729d5aa03e0d8f7ed256780",
|
||||
"rev": "97b17f32362e475016f942bbdfda4a4a72a8a652",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
};
|
||||
|
||||
devShell = with pkgs; mkShell {
|
||||
nativeBuildInputs = [ cargo cargo-outdated cargo-zigbuild clippy gcc protobuf rust-analyzer rustc rustfmt ];
|
||||
nativeBuildInputs = [ cargo cargo-outdated clippy gcc rust-analyzer rustc rustfmt ];
|
||||
|
||||
RUST_SRC_PATH = "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
|
||||
};
|
||||
|
|
BIN
images/example.png
Normal file
BIN
images/example.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 MiB |
|
@ -1,18 +1,14 @@
|
|||
{ lib
|
||||
, nixosTests
|
||||
, protobuf
|
||||
, rustPlatform
|
||||
}:
|
||||
|
||||
rustPlatform.buildRustPackage {
|
||||
pname = "pict-rs-admin";
|
||||
version = "0.1.1";
|
||||
version = "0.2.0";
|
||||
src = ./.;
|
||||
cargoLock.lockFile = ./Cargo.lock;
|
||||
|
||||
PROTOC = "${protobuf}/bin/protoc";
|
||||
PROTOC_INCLUDE = "${protobuf}/include";
|
||||
|
||||
RUSTFLAGS = "--cfg tokio_unstable";
|
||||
|
||||
nativeBuildInputs = [ ];
|
||||
|
|
196
src/main.rs
196
src/main.rs
|
@ -1,18 +1,17 @@
|
|||
use std::net::SocketAddr;
|
||||
use std::{net::SocketAddr, path::PathBuf, time::Duration};
|
||||
|
||||
use actix_web::{
|
||||
body::BodyStream, error::ErrorInternalServerError, web, App, HttpResponse, HttpServer,
|
||||
};
|
||||
use clap::Parser;
|
||||
use console_subscriber::ConsoleLayer;
|
||||
use opentelemetry::{
|
||||
sdk::{propagation::TraceContextPropagator, Resource},
|
||||
KeyValue,
|
||||
};
|
||||
use opentelemetry::KeyValue;
|
||||
use opentelemetry_otlp::WithExportConfig;
|
||||
use opentelemetry_sdk::{propagation::TraceContextPropagator, Resource};
|
||||
use reqwest::{redirect::Policy, Client};
|
||||
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
|
||||
use reqwest_tracing::TracingMiddleware;
|
||||
use rustls_021::ServerConfig;
|
||||
use tracing_actix_web::TracingLogger;
|
||||
use tracing_error::ErrorLayer;
|
||||
use tracing_subscriber::{
|
||||
|
@ -26,7 +25,7 @@ struct Args {
|
|||
short,
|
||||
long,
|
||||
env = "PICTRS_ADMIN__BIND_ADDRESS",
|
||||
default_value = "127.0.0.0:8084"
|
||||
default_value = "[::]:8084"
|
||||
)]
|
||||
bind_address: SocketAddr,
|
||||
#[clap(
|
||||
|
@ -39,8 +38,16 @@ struct Args {
|
|||
pict_rs_api_key: String,
|
||||
#[clap(long, env = "PICTRS_ADMIN__OPENTELEMETRY_URL")]
|
||||
opentelemetry_url: Option<Url>,
|
||||
#[clap(long, env = "PICTRS_ADMIN__OPENTELEMETRY_EVENT_BUFFER_SIZE")]
|
||||
opentelemetry_event_buffer_size: Option<usize>,
|
||||
#[clap(long, env = "PICTRS_ADMIN__CONSOLE_ADDRESS")]
|
||||
console_address: Option<SocketAddr>,
|
||||
#[clap(long, env = "PICTRS_ADMIN__CONSOLE_EVENT_BUFFER_SIZE")]
|
||||
console_event_buffer_size: Option<usize>,
|
||||
#[clap(long, env = "PICTRS_ADMIN__CERTIFICATE")]
|
||||
pict_rs_certificate: Option<PathBuf>,
|
||||
#[clap(long, env = "PICTRS_ADMIN__SERVER_CERTIFICATE")]
|
||||
server_certificate: Option<PathBuf>,
|
||||
#[clap(long, env = "PICTRS_ADMIN__SERVER_PRIVATE_KEY")]
|
||||
server_private_key: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -105,11 +112,14 @@ impl PictrsHash {
|
|||
self.aliases.first().map(|alias| format!("/image/{alias}"))
|
||||
}
|
||||
|
||||
fn is_video(&self) -> bool {
|
||||
self.details
|
||||
.as_ref()
|
||||
.map(|d| d.content_type.starts_with("video"))
|
||||
.unwrap_or(false)
|
||||
fn video_type(&self) -> Option<String> {
|
||||
self.details.as_ref().and_then(|d| {
|
||||
if d.content_type.starts_with("video") {
|
||||
Some(d.content_type.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,7 +144,7 @@ impl PictrsPage {
|
|||
}
|
||||
|
||||
impl PictrsClient {
|
||||
async fn page(&self, slug: Option<String>) -> Result<PageResponse, reqwest_middleware::Error> {
|
||||
async fn page(&self, query: &PageQuery) -> Result<PageResponse, reqwest_middleware::Error> {
|
||||
let mut url = self.pict_rs_endpoint.clone();
|
||||
url.set_path("/internal/hashes");
|
||||
|
||||
|
@ -142,7 +152,7 @@ impl PictrsClient {
|
|||
.client
|
||||
.get(url.as_str())
|
||||
.header("x-api-token", &self.pict_rs_api_key)
|
||||
.query(&PageQuery { slug })
|
||||
.query(query)
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
|
@ -187,13 +197,17 @@ impl PictrsClient {
|
|||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
struct PageQuery {
|
||||
slug: Option<String>,
|
||||
timestamp: Option<String>,
|
||||
}
|
||||
|
||||
async fn index(
|
||||
web::Query(PageQuery { slug }): web::Query<PageQuery>,
|
||||
web::Query(query): web::Query<PageQuery>,
|
||||
client: web::Data<PictrsClient>,
|
||||
) -> Result<HttpResponse, actix_web::Error> {
|
||||
let page = client.page(slug).await.map_err(ErrorInternalServerError)?;
|
||||
let page = client
|
||||
.page(&query)
|
||||
.await
|
||||
.map_err(ErrorInternalServerError)?;
|
||||
|
||||
let page = match page {
|
||||
PageResponse::Ok { page, .. } => page,
|
||||
|
@ -269,6 +283,7 @@ async fn serve_static(name: web::Path<String>) -> HttpResponse {
|
|||
fn init_tracing(
|
||||
service_name: &'static str,
|
||||
opentelemetry_url: Option<&Url>,
|
||||
console_address: Option<SocketAddr>,
|
||||
console_event_buffer_size: Option<usize>,
|
||||
) -> color_eyre::Result<()> {
|
||||
opentelemetry::global::set_text_map_propagator(TraceContextPropagator::new());
|
||||
|
@ -285,19 +300,23 @@ fn init_tracing(
|
|||
.with(format_layer)
|
||||
.with(ErrorLayer::default());
|
||||
|
||||
if let Some(buffer_size) = console_event_buffer_size {
|
||||
let console_layer = ConsoleLayer::builder()
|
||||
.with_default_env()
|
||||
.server_addr(([0, 0, 0, 0], 6669))
|
||||
.event_buffer_capacity(buffer_size)
|
||||
.spawn();
|
||||
if let Some(addr) = console_address {
|
||||
let builder = ConsoleLayer::builder().with_default_env().server_addr(addr);
|
||||
|
||||
let console_layer = if let Some(buffer_size) = console_event_buffer_size {
|
||||
builder.event_buffer_capacity(buffer_size).spawn()
|
||||
} else {
|
||||
builder.spawn()
|
||||
};
|
||||
|
||||
let subscriber = subscriber.with(console_layer);
|
||||
|
||||
init_subscriber(subscriber, targets, opentelemetry_url, service_name)
|
||||
init_subscriber(subscriber, targets, opentelemetry_url, service_name)?;
|
||||
tracing::info!("Starting console on {addr}");
|
||||
} else {
|
||||
init_subscriber(subscriber, targets, opentelemetry_url, service_name)
|
||||
init_subscriber(subscriber, targets, opentelemetry_url, service_name)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_subscriber<S>(
|
||||
|
@ -311,18 +330,19 @@ where
|
|||
for<'a> S: LookupSpan<'a>,
|
||||
{
|
||||
if let Some(url) = opentelemetry_url {
|
||||
let tracer =
|
||||
opentelemetry_otlp::new_pipeline()
|
||||
.tracing()
|
||||
.with_trace_config(opentelemetry::sdk::trace::config().with_resource(
|
||||
Resource::new(vec![KeyValue::new("service.name", service_name)]),
|
||||
))
|
||||
.with_exporter(
|
||||
opentelemetry_otlp::new_exporter()
|
||||
.tonic()
|
||||
.with_endpoint(url.as_str()),
|
||||
)
|
||||
.install_batch(opentelemetry::runtime::Tokio)?;
|
||||
let tracer = opentelemetry_otlp::new_pipeline()
|
||||
.tracing()
|
||||
.with_trace_config(
|
||||
opentelemetry_sdk::trace::config().with_resource(Resource::new(vec![
|
||||
KeyValue::new("service.name", service_name),
|
||||
])),
|
||||
)
|
||||
.with_exporter(
|
||||
opentelemetry_otlp::new_exporter()
|
||||
.tonic()
|
||||
.with_endpoint(url.as_str()),
|
||||
)
|
||||
.install_batch(opentelemetry_sdk::runtime::Tokio)?;
|
||||
|
||||
let otel_layer = tracing_opentelemetry::layer()
|
||||
.with_tracer(tracer)
|
||||
|
@ -338,6 +358,42 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
async fn add_root_certificates(
|
||||
builder: reqwest::ClientBuilder,
|
||||
path: &PathBuf,
|
||||
) -> color_eyre::Result<reqwest::ClientBuilder> {
|
||||
let bytes = tokio::fs::read(path).await?;
|
||||
|
||||
let res = rustls_pemfile::certs(&mut bytes.as_slice()).try_fold(builder, |builder, res| {
|
||||
let cert = res?;
|
||||
let cert = reqwest::Certificate::from_der(&cert)?;
|
||||
|
||||
Ok(builder.add_root_certificate(cert))
|
||||
});
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
async fn open_keys(
|
||||
certificate: &PathBuf,
|
||||
private_key: &PathBuf,
|
||||
) -> color_eyre::Result<rustls_021::sign::CertifiedKey> {
|
||||
let cert_bytes = tokio::fs::read(certificate).await?;
|
||||
let key_bytes = tokio::fs::read(private_key).await?;
|
||||
|
||||
let certs = rustls_pemfile::certs(&mut cert_bytes.as_slice())
|
||||
.map(|res| res.map(|c| rustls_021::Certificate(c.to_vec())))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let key = rustls_pemfile::private_key(&mut key_bytes.as_slice())?
|
||||
.ok_or_else(|| color_eyre::eyre::eyre!("No key in keyfile"))?;
|
||||
|
||||
let signing_key =
|
||||
rustls_021::sign::any_supported_type(&rustls_021::PrivateKey(key.secret_der().to_vec()))?;
|
||||
|
||||
Ok(rustls_021::sign::CertifiedKey::new(certs, signing_key))
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> color_eyre::Result<()> {
|
||||
let Args {
|
||||
|
@ -345,19 +401,29 @@ async fn main() -> color_eyre::Result<()> {
|
|||
pict_rs_endpoint,
|
||||
pict_rs_api_key,
|
||||
opentelemetry_url,
|
||||
opentelemetry_event_buffer_size,
|
||||
console_address,
|
||||
console_event_buffer_size,
|
||||
pict_rs_certificate,
|
||||
server_certificate,
|
||||
server_private_key,
|
||||
} = Args::parse();
|
||||
|
||||
init_tracing(
|
||||
"pict-rs-admin",
|
||||
opentelemetry_url.as_ref(),
|
||||
opentelemetry_event_buffer_size,
|
||||
console_address,
|
||||
console_event_buffer_size,
|
||||
)?;
|
||||
|
||||
let client = Client::builder()
|
||||
.user_agent("pict-rs-admin v0.1.0")
|
||||
.redirect(Policy::none())
|
||||
.build()?;
|
||||
let builder = Client::builder()
|
||||
.user_agent("pict-rs-admin v0.2.0")
|
||||
.redirect(Policy::none());
|
||||
|
||||
let client = if let Some(path) = pict_rs_certificate {
|
||||
add_root_certificates(builder, &path).await?.build()?
|
||||
} else {
|
||||
builder.build()?
|
||||
};
|
||||
|
||||
let client = ClientBuilder::new(client)
|
||||
.with(TracingMiddleware::default())
|
||||
|
@ -369,7 +435,7 @@ async fn main() -> color_eyre::Result<()> {
|
|||
pict_rs_api_key,
|
||||
};
|
||||
|
||||
HttpServer::new(move || {
|
||||
let server = HttpServer::new(move || {
|
||||
App::new()
|
||||
.wrap(TracingLogger::default())
|
||||
.app_data(web::Data::new(client.clone()))
|
||||
|
@ -377,10 +443,44 @@ async fn main() -> color_eyre::Result<()> {
|
|||
.route("/image/{path}", web::get().to(image))
|
||||
.route("/purge/{path}", web::get().to(purge))
|
||||
.route("/static/{path}", web::get().to(serve_static))
|
||||
})
|
||||
.bind(bind_address)?
|
||||
.run()
|
||||
.await?;
|
||||
});
|
||||
|
||||
if let Some((cert, key)) = server_certificate.zip(server_private_key) {
|
||||
let certified_key = open_keys(&cert, &key).await?;
|
||||
|
||||
let (tx, rx) = rustls_channel_resolver::channel::<32>(certified_key);
|
||||
|
||||
let handle = actix_web::rt::spawn(async move {
|
||||
let mut interval = actix_web::rt::time::interval(Duration::from_secs(30));
|
||||
interval.tick().await;
|
||||
|
||||
loop {
|
||||
interval.tick().await;
|
||||
|
||||
match open_keys(&cert, &key).await {
|
||||
Ok(certified_key) => tx.update(certified_key),
|
||||
Err(e) => tracing::error!("Failed to read TLS keys {e}"),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let server_config = ServerConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_no_client_auth()
|
||||
.with_cert_resolver(rx);
|
||||
|
||||
tracing::info!("Starting pict-rs-admin with TLS on {bind_address}");
|
||||
server
|
||||
.bind_rustls_021(bind_address, server_config)?
|
||||
.run()
|
||||
.await?;
|
||||
|
||||
handle.abort();
|
||||
let _ = handle.await;
|
||||
} else {
|
||||
tracing::info!("Starting pict-rs-admin on {bind_address}");
|
||||
server.bind(bind_address)?.run().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -22,6 +22,17 @@ nav a {
|
|||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
nav form {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
nav input {
|
||||
padding: 8px;
|
||||
}
|
||||
nav input[type="submit"] {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
section {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
@ -48,7 +59,8 @@ article {
|
|||
max-width: 100%;
|
||||
}
|
||||
|
||||
img {
|
||||
img,
|
||||
video {
|
||||
border-radius: 3px 3px 0 0;
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
</head>
|
||||
|
||||
<body>
|
||||
@if page.prev_link().is_some() || page.next_link().is_some() {
|
||||
<nav>
|
||||
@if let Some(prev) = page.prev_link() {
|
||||
<a href="@prev">Previous Page</a>
|
||||
|
@ -22,8 +21,11 @@
|
|||
@if let Some(next) = page.next_link() {
|
||||
<a href="@next">Next Page</a>
|
||||
}
|
||||
<form method="GET" action="/">
|
||||
<input type="text" name="timestamp" placeholder="rfc3339 timestamp" />
|
||||
<input type="submit" />
|
||||
</form>
|
||||
</nav>
|
||||
}
|
||||
|
||||
<section>
|
||||
@if page.hashes.is_empty() {
|
||||
|
@ -40,8 +42,8 @@
|
|||
<div class="border">
|
||||
@if let Some(src) = hash.media_link() {
|
||||
<div class="image-box">
|
||||
@if hash.is_video() {
|
||||
<video controls src="@src"></video>
|
||||
@if let Some(video_type) = hash.video_type() {
|
||||
<video controls loop src="@src" type="@video_type"></video>
|
||||
} else {
|
||||
<img src="@src" />
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue