This commit is contained in:
James Read 2026-05-19 21:05:52 +00:00 committed by GitHub
commit 3f937c98ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
300 changed files with 10472 additions and 779 deletions

View File

@ -17,25 +17,30 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v6
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Set up QEMU - name: Set up QEMU
id: qemu id: qemu
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v4
with: with:
image: tonistiigi/binfmt:latest image: tonistiigi/binfmt:latest
platforms: arm64,arm platforms: arm64,arm
- name: Setup node - name: Setup node (npm cache)
uses: actions/setup-node@v4 if: github.event_name != 'pull_request'
uses: actions/setup-node@v6.4.0
with: with:
cache: 'npm' cache: 'npm'
cache-dependency-path: frontend/package-lock.json cache-dependency-path: frontend/package-lock.json
- name: Setup node
if: github.event_name == 'pull_request'
uses: actions/setup-node@v6.4.0
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v5 uses: actions/setup-go@v6
with: with:
go-version-file: 'service/go.mod' go-version-file: 'service/go.mod'
cache: true cache: true
@ -45,7 +50,7 @@ jobs:
run: go version run: go version
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v3 uses: docker/login-action@v4
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_KEY }} password: ${{ secrets.DOCKERHUB_KEY }}
@ -74,7 +79,7 @@ jobs:
run: cd integration-tests && make -w run: cd integration-tests && make -w
- name: Archive integration tests - name: Archive integration tests
uses: actions/upload-artifact@v4.3.1 uses: actions/upload-artifact@v7
if: always() if: always()
with: with:
name: "OliveTin-integration-tests-${{ env.DATE }}-${{ github.sha }}" name: "OliveTin-integration-tests-${{ env.DATE }}-${{ github.sha }}"

34
.github/workflows/docs-antora.yml vendored Normal file
View File

@ -0,0 +1,34 @@
name: Antora docs
on:
push:
paths:
- 'docs/**'
- 'local-antora-playbook.yml'
- 'local-antora-playbook-ci.yml'
- '.github/workflows/docs-antora.yml'
pull_request:
paths:
- 'docs/**'
- 'local-antora-playbook.yml'
- 'local-antora-playbook-ci.yml'
- '.github/workflows/docs-antora.yml'
jobs:
antora:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install Antora toolchain
run: npm i antora@3.1.14 asciidoctor-kroki@0.18.1 @asciidoctor/tabs@1.0.0-beta.6
- name: Generate docs site (smoke)
run: npx antora local-antora-playbook-ci.yml --log-level info

2
.gitignore vendored
View File

@ -20,3 +20,5 @@ integration-tests/configs/authRequireGuestsToLogin/sessions.yaml
webui webui
webui.dev webui.dev
sessions.yaml sessions.yaml
docs/build/
build/

View File

@ -58,6 +58,7 @@ make
The project layout is reasonably straightforward; The project layout is reasonably straightforward;
* See the `Makefile` for common targets. This project was originally created on top of Fedora, but it should be usable on Debian/your faveourite distro with minor changes (if any). * See the `Makefile` for common targets. This project was originally created on top of Fedora, but it should be usable on Debian/your faveourite distro with minor changes (if any).
* End-user documentation (AsciiDoc for link:https://docs.olivetin.app[docs.olivetin.app]) lives in `docs/` as an Antora component; the published site is built from the separate link:https://github.com/OliveTin/docs.olivetin.app[docs.olivetin.app] repository.
* The API is defined in protobuf+Connect RPC - you will need to `make proto`. * The API is defined in protobuf+Connect RPC - you will need to `make proto`.
* The Go daemon is built from the `cmd` and `internal` directories mostly. * The Go daemon is built from the `cmd` and `internal` directories mostly.
* The webui is just a single page application with a bit of Javascript in the `webui` directory. This can happily be hosted on another webserver. * The webui is just a single page application with a bit of Javascript in the `webui` directory. This can happily be hosted on another webserver.

View File

@ -1,17 +1,17 @@
# Multi-arch Dockerfile for GoReleaser (dockers_v2). # Multi-arch Dockerfile for GoReleaser (dockers_v2).
# Base image :43 is used without arch suffix so the registry can supply the right # Base image :44 is used without arch suffix so the registry can supply the right
# platform (manifest list). TARGETPLATFORM is set by BuildKit for COPY. # platform (manifest list). TARGETPLATFORM is set by BuildKit for COPY.
# For custom/local single-arch builds, use Dockerfile.singlearch instead. # For custom/local single-arch builds, use Dockerfile.singlearch instead.
ARG TARGETPLATFORM ARG TARGETPLATFORM
FROM registry.fedoraproject.org/fedora-minimal:43 AS olivetin-tmputils FROM registry.fedoraproject.org/fedora-minimal:44 AS olivetin-tmputils
RUN microdnf -y install dnf-plugins-core && \ RUN microdnf -y install dnf-plugins-core && \
dnf-3 config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo && \ dnf-3 config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo && \
microdnf install -y docker-ce-cli docker-compose-plugin && microdnf clean all microdnf install -y docker-ce-cli docker-compose-plugin && microdnf clean all
FROM registry.fedoraproject.org/fedora-minimal:43 FROM registry.fedoraproject.org/fedora-minimal:44
LABEL org.opencontainers.image.source https://github.com/OliveTin/OliveTin LABEL org.opencontainers.image.source https://github.com/OliveTin/OliveTin
LABEL org.opencontainers.image.title OliveTin LABEL org.opencontainers.image.title OliveTin

View File

@ -1,10 +1,10 @@
FROM --platform=linux/amd64 registry.fedoraproject.org/fedora-minimal:43-x86_64 AS olivetin-tmputils FROM --platform=linux/amd64 registry.fedoraproject.org/fedora-minimal:44-x86_64 AS olivetin-tmputils
RUN microdnf -y install dnf-plugins-core && \ RUN microdnf -y install dnf-plugins-core && \
dnf-3 config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo && \ dnf-3 config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo && \
microdnf install -y docker-ce-cli docker-compose-plugin && microdnf clean all microdnf install -y docker-ce-cli docker-compose-plugin && microdnf clean all
FROM --platform=linux/amd64 registry.fedoraproject.org/fedora-minimal:43-x86_64 FROM --platform=linux/amd64 registry.fedoraproject.org/fedora-minimal:44-x86_64
LABEL org.opencontainers.image.source https://github.com/OliveTin/OliveTin LABEL org.opencontainers.image.source https://github.com/OliveTin/OliveTin
LABEL org.opencontainers.image.title OliveTin LABEL org.opencontainers.image.title OliveTin

View File

@ -20,6 +20,8 @@
All documentation can be found at [docs.olivetin.app](https://docs.olivetin.app). This includes installation and usage guide, etc. All documentation can be found at [docs.olivetin.app](https://docs.olivetin.app). This includes installation and usage guide, etc.
The AsciiDoc sources for that site live in this repository under [`docs/`](docs/) (Antora component). The [docs.olivetin.app](https://github.com/OliveTin/docs.olivetin.app) repository contains the Antora playbook, theme supplemental files, and the workflow that publishes GitHub Pages.
## Use cases ## Use cases
**Safely** give access to commands, for less technical people; **Safely** give access to commands, for less technical people;

15
docs/antora.yml Normal file
View File

@ -0,0 +1,15 @@
---
name: ROOT
title: OliveTin
version: ''
display_version: 'Version 3k'
start_page: index.adoc
asciidoc:
attributes:
source-language: asciidoc@
table-caption: false
toclevels: 2
nav:
- modules/ROOT/nav.adoc

View File

@ -0,0 +1,40 @@
#!/usr/bin/env python3
import glob
import re
nav_file = open('nav.adoc', 'r')
nav_string = nav_file.read()
adoc_files = glob.glob('pages/**/*.adoc', recursive=True)
filelist = dict()
for file in adoc_files:
with open(file, 'r') as handle:
content = handle.read()
matches = re.findall(r'<<(.*?),?([\w\- ]+)>>', content)
for match in matches:
m = match
if match[0] == "":
m = match[1]
else:
m = match[0]
if content.count("#" + m) != 1:
if file not in filelist:
filelist[file] = list()
filelist[file].append(m)
print("Files:", len(filelist))
for file in filelist.keys():
print(file)
for match in filelist[file]:
print("\t", match)

View File

@ -0,0 +1,24 @@
#!/usr/bin/env python3
import glob
import re
adoc_files = glob.glob('pages/**/*.adoc', recursive=True)
filelist = list()
for file in adoc_files:
with open(file, 'r') as handle:
content = handle.read()
matches = re.findall('^= ', content, re.MULTILINE)
if len(matches) == 0:
filelist.append(file)
print("Files:", len(filelist))
for file in filelist:
print(file)

View File

@ -0,0 +1,25 @@
#!/usr/bin/env python3
# find .adoc files that are not navigable from the nav.adoc file
import glob
nav_file = open('nav.adoc', 'r')
nav_string = nav_file.read()
adoc_files = glob.glob('pages/**/*.adoc', recursive=True)
unnavigable_files = []
for file in adoc_files:
filename = file.replace("pages/", "")
if filename not in nav_string:
unnavigable_files.append(filename)
unnavigable_files = sorted(unnavigable_files)
print("Unnavigable files:", len(unnavigable_files))
for file in unnavigable_files:
print(file)

View File

@ -0,0 +1,15 @@
actions:
- title: Unicode (emoji) alias icon
shell: echo "Hello!"
icon: smile
- title: Unicode (emoji) icon
shell: echo "Hello!"
icon: "&#128526;"
- title: Iconify Icon
icon: <iconify-icon icon="ant-design:bug-filled"></iconify-icon>
- title: HTML Image (jpg/png/gif/etc) icon
shell: echo "Hello!"
icon: '<img src = "custom-webui/icons/mrgreen.gif" style = "width: 1em;" />'

View File

@ -0,0 +1,9 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: olivetin-config
data:
config.yaml: |
actions:
- title: "Hello world!"
shell: echo 'Hello World!'

View File

@ -0,0 +1,37 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: olivetin
spec:
replicas: 1
selector:
matchLabels:
app: olivetin
template:
metadata:
labels:
app: olivetin
spec:
containers:
- name: olivetin
image: docker.io/jamesread/olivetin:latest
ports:
- containerPort: 1337
volumeMounts:
- name: olivetin-config
mountPath: "/config"
readOnly: true
livenessProbe:
exec:
command:
- curl
- localhost:1337
initialDelaySeconds: 5
periodSeconds: 30
volumes:
- name: olivetin-config
configMap:
name: olivetin-config

View File

@ -0,0 +1,21 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: olivetin-ingress
spec:
defaultBackend:
service:
name: olivetin
port:
number: 1337
rules:
- host: olivetin.apps.ocp.teratan.net
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: olivetin
port:
number: 1337

View File

@ -0,0 +1,19 @@
services:
app:
image: 'jc21/nginx-proxy-manager:latest'
restart: unless-stopped
ports:
- '80:80'
- '81:81'
- '443:443'
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
olivetin:
container_name: olivetin
image: jamesread/olivetin
volumes:
- ./OliveTin:/config # replace host path or volume as needed
ports:
- "1337:1337"
restart: unless-stopped

View File

@ -0,0 +1,25 @@
server {
listen 443 ssl;
ssl_certificate "/etc/nginx/conf.d/server.crt";
ssl_certificate_key "/etc/nginx/conf.d/server.key";
access_log /var/log/nginx/ot.access.log main;
error_log /var/log/nginx/ot.error.log notice;
server_name olivetin.example.com;
location / {
proxy_pass http://localhost:1337/;
proxy_redirect http://localhost:1337/ http://localhost/OliveTin/;
}
location /websocket {
proxy_set_header Upgrade "websocket";
proxy_set_header Connection "upgrade";
proxy_pass http://localhost:1337/websocket;
proxy_read_timeout 600s;
proxy_send_timeout 600s;
}
}

View File

@ -0,0 +1,48 @@
# This config has two actions which are applied to all "container" entities
# found in the entity file.
#
# Docs: http://localhost/docs.olivetin.app/docs/entities.html
actions:
- title: Start {{ container.Names }}
icon: box
shell: docker start {{ container.Names }}
entity: container
triggers:
- Update container entity file
- title: Stop {{ container.Names }}
icon: box
shell: docker stop {{ container.Names }}
entity: container
triggers:
- Update container entity file
# This is a hidden action, that is run on startup, and every 5 minutes, and
# when the above start/stop commands are run (see the `triggers` property).
- title: Update container entity file
shell: 'docker ps -a --format json > /etc/OliveTin/entities/containers.json'
hidden: true
execOnStartup: true
execOnCron: '*/5 * * * *'
# Docs: http://docs.olivetin.app/entities.html
entities:
- file: /etc/OliveTin/entities/containers.json
name: container
# The only way to properly use entities, are to use them with a `fieldset` on
# a dashboard.
dashboards:
# This is the second dashboard.
- title: My Containers
contents:
- title: 'Container {{ container.Names }} ({{ container.Image }})'
entity: container
type: fieldset
contents:
- type: display
title: |
{{ container.RunningFor }} <br /><br /><strong>{{ container.State }}</strong>
- title: 'Start {{ container.Names }}'
- title: 'Stop {{ container.Names }}'

View File

@ -0,0 +1,2 @@
{"Command":"\"/bin/bash\"","CreatedAt":"2024-02-28 22:33:35 +0000 GMT","ID":"fcf468e18a0e","Image":"fedora","Labels":"maintainer=Clement Verna \u003ccverna@fedoraproject.org\u003e","LocalVolumes":"0","Mounts":"","Names":"minecraft","Networks":"bridge","Ports":"","RunningFor":"3 minutes ago","Size":"0B","State":"created","Status":"Created"}
{"Command":"\"/bin/bash\"","CreatedAt":"2024-02-23 23:18:57 +0000 GMT","ID":"442dd6fe316a","Image":"fedora","Labels":"maintainer=Clement Verna \u003ccverna@fedoraproject.org\u003e","LocalVolumes":"0","Mounts":"","Names":"brave_shirley","Networks":"bridge","Ports":"","RunningFor":"4 days ago","Size":"0B","State":"created","Status":"Created"}

View File

@ -0,0 +1,32 @@
actions:
- title: check log directory
hidden: true
shell: |
function addDirectory {
COUNT=$(ls -l $1 | wc -l)
echo "- directory: $1" >> /etc/OliveTin/entities/directories.yaml
echo " count: $COUNT" >> /etc/OliveTin/entities/directories.yaml
}
truncate -s 0 /etc/OliveTin/entities/directories.yaml
addDirectory /var/log/
addDirectory /home/xconspirisist/logs
execOnStartup: true
execOnCron: "* * * * *"
- title: clean {{ log_directory.directory }} ({{log_directory.count }} files)
shell: |
echo "Removing all files in {{ log_directory.directory }}"
entity: log_directory
entities:
- name: log_directory
file: /etc/OliveTin/entities/directories.yaml
dashboards:
- title: Log Actions
contents:
- entity: log_directory
type: fieldset
contents:
- title: clean {{ log_directory.directory }} ({{log_directory.count }} files)

View File

@ -0,0 +1,28 @@
logLevel: "INFO"
actions:
- title: Turn heating up
icon: '&#128316;'
shell: /opt/heating.sh up
- title: Turn heating down
icon: '&#128317;'
shell: /opt/heating.sh down
entities:
- file: /etc/OliveTin/entities/heating.yaml
name: heating
dashboards:
- title: Heating Control Panel
contents:
- title: "{{ heater.title }}"
entity: heating
type: fieldset
contents:
- type: display
title: |
<span class = "icon">&#127777;</span> <br />{{ heating.temperature }}
- title: Turn heating up
- title: Turn heating down

View File

@ -0,0 +1,2 @@
- title: Main heater
temperature: 20 degrees

View File

@ -0,0 +1,35 @@
const myPassword = 'sekrit'
const domMain = document.getElementsByTagName('main')[0]
domMain.style.display = 'none'
const domPassword = document.createElement('input')
const domLogin = document.createElement('button')
function checkPassword () {
if (domPassword.value === myPassword) {
domMain.style.display = 'block'
domPassword.remove()
domLogin.remove()
} else {
window.alert('Incorrect password. Please try again.')
}
}
function setupPasswordForm () {
domPassword.setAttribute('type', 'password')
domPassword.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
checkPassword()
}
})
domLogin.innerText = 'Login'
domLogin.onclick = checkPassword
const domHeader = document.querySelector('header')
domHeader.appendChild(domPassword)
domHeader.appendChild(domLogin)
}
document.addEventListener('DOMContentLoaded', setupPasswordForm)

View File

@ -0,0 +1,36 @@
actions:
- title: Stop {{ systemd_unit.unit }}
shell: systemctl stop {{ systemd_unit.unit }}
icon: <iconify-icon icon="zondicons:hand-stop"></iconify-icon>
entity: systemd_unit
triggers:
- Update services file
- title: Start {{ systemd_unit.unit }}
shell: systemctl start {{ systemd_unit.unit }}
icon: <iconify-icon icon="ic:round-directions-run"></iconify-icon>
entity: systemd_unit
triggers:
- Update services file
- title: Update services file
shell: systemctl list-units -a -o json --no-pager | jq -c 'map(select (.unit | contains ("upsilon", "podman", "boot.mount"))) | .[]' > /etc/OliveTin/entities/systemd_units.json
hidden: true
execOnStartup: true
entities:
- file: /etc/OliveTin/entities/systemd_units.json
name: systemd_unit
dashboards:
- title: My Services
contents:
- title: '{{ systemd_unit.description }}'
type: fieldset
entity: systemd_unit
contents:
- title: 'Status: {{ systemd_unit.sub }}'
type: display
- title: Start {{ systemd_unit.unit }}
- title: Stop {{ systemd_unit.unit }}

View File

@ -0,0 +1,4 @@
{"unit":"boot.mount","load":"loaded","active":"active","sub":"mounted","description":"/boot"}
{"unit":"podman.service","load":"loaded","active":"inactive","sub":"dead","description":"Podman API Service"}
{"unit":"upsilon-drone.service","load":"loaded","active":"active","sub":"running","description":"upsilon-drone"}
{"unit":"podman.socket","load":"loaded","active":"active","sub":"listening","description":"Podman API Socket"}

View File

@ -0,0 +1,10 @@
actions:
- title: WakeOnLan Server1
shell: ether-wake A8:5E:45:E4:FF:2A
icon: ping
- title: Install ether-wake on startup
shell: microdnf install -y net-tools
hidden: true
execOnStartup: true
timeout: 120

View File

@ -0,0 +1,6 @@
- title: WakeOnLan Server1
# The r0gger/docker-wake-on-lan is a minimal container for WOL
# that can be run on the host network.
# It is not required to run the OliveTin container on the host network.
shell: |
docker run --rm --name wake-on-lan --net=host -e MAC='A8:5E:45:E4:FF:2A' r0gger/docker-wake-on-lan

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Some files were not shown because too many files have changed in this diff Show More