Merge pull request #7032 from okxlin/maint-pr-7025-whodb-publish

maint: publish rebuilt WhoDB 0.129.0
This commit is contained in:
okxlin
2026-09-19 11:27:03 +08:00
committed by GitHub
20 changed files with 208 additions and 409 deletions
-88
View File
@@ -1,88 +0,0 @@
additionalProperties:
formFields:
- default: 8080
edit: true
envKey: PANEL_APP_PORT_HTTP
labelEn: HTTP Port
labelZh: HTTP 端口
label:
en: HTTP Port
zh: HTTP 端口
zh-Hant: HTTP 埠
ja: HTTP ポート
ko: HTTP 포트
ru: HTTP-порт
ms: Port HTTP
pt-br: Porta HTTP
required: true
rule: paramPort
type: number
- default: ./data
edit: true
envKey: APP_DATA_DIR
labelEn: Data Directory
labelZh: 数据目录
label:
en: Data Directory
zh: 数据目录
zh-Hant: 資料目錄
ja: データディレクトリ
ko: 데이터 디렉터리
ru: Каталог данных
ms: Direktori data
pt-br: Diretorio de dados
required: true
type: text
- default: ./db-data
edit: true
envKey: DB_DATA_DIR
labelEn: Local Database Directory
labelZh: 本地数据库目录
label:
en: Local Database Directory
zh: 本地数据库目录
zh-Hant: 本機資料庫目錄
ja: ローカルデータベースディレクトリ
ko: 로컬 데이터베이스 디렉터리
ru: Каталог локальных баз данных
ms: Direktori pangkalan data setempat
pt-br: Diretorio de bancos de dados locais
required: true
type: text
- default: generate
edit: true
envKey: WHODB_ENCRYPTION_KEY
labelEn: Session Encryption Key
labelZh: 会话加密密钥
label:
en: Session Encryption Key
zh: 会话加密密钥
zh-Hant: 工作階段加密金鑰
ja: セッション暗号化キー
ko: 세션 암호화 키
ru: Ключ шифрования сеансов
ms: Kunci penyulitan sesi
pt-br: Chave de criptografia da sessao
required: true
type: password
- default: "false"
edit: true
envKey: WHODB_SECURE
labelEn: HTTPS Secure Cookie
labelZh: HTTPS 安全 Cookie
label:
en: HTTPS Secure Cookie
zh: HTTPS 安全 Cookie
zh-Hant: HTTPS 安全 Cookie
ja: HTTPS セキュア Cookie
ko: HTTPS 보안 쿠키
ru: Защищенные Cookie для HTTPS
ms: Cookie selamat HTTPS
pt-br: Cookie seguro HTTPS
required: true
type: select
values:
- label: "false"
value: "false"
- label: "true"
value: "true"
View File
-22
View File
@@ -1,22 +0,0 @@
services:
whodb:
image: clidey/whodb:0.127.0
container_name: ${CONTAINER_NAME}
restart: always
networks:
- 1panel-network
ports:
- "${PANEL_APP_PORT_HTTP}:8080"
environment:
- WHODB_ENCRYPTION_KEY=${WHODB_ENCRYPTION_KEY}
- WHODB_SECURE=${WHODB_SECURE}
- WHODB_DATA_DIR=/data
volumes:
- "${APP_DATA_DIR}:/data"
- "${DB_DATA_DIR}:/db"
labels:
createdBy: "Apps"
networks:
1panel-network:
external: true
@@ -1,6 +1,6 @@
CONTAINER_NAME=whodb-compose-check
PANEL_APP_PORT_HTTP=8080
APP_DATA_DIR=./data
DB_DATA_DIR=./db-data
WHODB_ENCRYPTION_KEY=generate
WHODB_SECURE=false
CONTAINER_NAME=whodb
+128
View File
@@ -0,0 +1,128 @@
additionalProperties:
formFields:
- default: 8080
edit: true
envKey: PANEL_APP_PORT_HTTP
labelEn: HTTP Port
labelZh: HTTP 端口
label:
en: HTTP Port
zh: HTTP 端口
zh-hant: HTTP 埠
ja: HTTP ポート
ko: HTTP 포트
ru: HTTP-порт
ms: Port HTTP
pt-br: Porta HTTP
tr: HTTP Bağlantı Noktası
es-es: Puerto HTTP
fa: درگاه HTTP
lo: ພອດ HTTP
required: true
rule: paramPort
type: number
- default: ./data
edit: true
envKey: APP_DATA_DIR
labelEn: Data Directory
labelZh: 数据目录
label:
en: Data Directory
zh: 数据目录
zh-hant: 資料目錄
ja: データディレクトリ
ko: 데이터 디렉터리
ru: Каталог данных
ms: Direktori data
pt-br: Diretorio de dados
tr: Veri Dizini
es-es: Directorio de datos
fa: فهرست داده‌ها
lo: ໂຟນເດີຂໍ້ມູນ
required: true
type: text
- default: ./db-data
edit: true
envKey: DB_DATA_DIR
labelEn: Local Database Directory
labelZh: 本地数据库目录
label:
en: Local Database Directory
zh: 本地数据库目录
zh-hant: 本機資料庫目錄
ja: ローカルデータベースディレクトリ
ko: 로컬 데이터베이스 디렉터리
ru: Каталог локальных баз данных
ms: Direktori pangkalan data setempat
pt-br: Diretorio de bancos de dados locais
tr: Yerel Veritabanı Dizini
es-es: Directorio de base de datos local
fa: فهرست پایگاه داده محلی
lo: ໂຟນເດີຖານຂໍ້ມູນທ້ອງຖິ່ນ
required: true
type: text
- default: generate
edit: true
envKey: WHODB_ENCRYPTION_KEY
labelEn: Session Encryption Key
labelZh: 会话加密密钥
label:
en: Session Encryption Key
zh: 会话加密密钥
zh-hant: 工作階段加密金鑰
ja: セッション暗号化キー
ko: 세션 암호화 키
ru: Ключ шифрования сеансов
ms: Kunci penyulitan sesi
pt-br: Chave de criptografia da sessao
tr: Oturum Şifreleme Anahtarı
es-es: Clave de cifrado de sesión
fa: کلید رمزنگاری نشست
lo: ຄີເຂົ້າລະຫັດເຊດຊັນ
required: true
type: password
- default: 'false'
edit: true
envKey: WHODB_SECURE
labelEn: HTTPS Secure Cookie
labelZh: HTTPS 安全 Cookie
label:
en: HTTPS Secure Cookie
zh: HTTPS 安全 Cookie
zh-hant: HTTPS 安全 Cookie
ja: HTTPS セキュア Cookie
ko: HTTPS 보안 쿠키
ru: Защищенные Cookie для HTTPS
ms: Cookie selamat HTTPS
pt-br: Cookie seguro HTTPS
tr: HTTPS Güvenli Çerezi
es-es: Cookie segura HTTPS
fa: کوکی امن HTTPS
lo: ຄຸກກີປອດໄພ HTTPS
required: true
type: select
values:
- label: 'false'
value: 'false'
- label: 'true'
value: 'true'
- default: Asia/Shanghai
edit: true
envKey: TZ
labelEn: Timezone
labelZh: 时区
label:
en: Timezone
zh: 时区
zh-hant: 時區
ja: タイムゾーン
ko: 시간대
ru: Часовой пояс
ms: Zon waktu
pt-br: Fuso horário
tr: Saat Dilimi
es-es: Zona horaria
fa: منطقه زمانی
lo: ເຂດເວລາ
required: true
type: text
@@ -1,6 +1,6 @@
services:
whodb:
image: clidey/whodb:latest
image: "clidey/whodb:0.129.0"
container_name: ${CONTAINER_NAME}
restart: always
networks:
@@ -62,6 +62,40 @@ set_env_value() {
mv -f -- "$temp_file" "$ENV_FILE"
}
resolve_app_path() {
local key="$1"
local raw="$2"
local clean candidate resolved current part
local -a parts=()
case "$raw" in
""|/*|.|..|../*|*/../*|*/..) echo "unsafe ${key} path" >&2; return 1 ;;
esac
if [[ "$raw" =~ [[:cntrl:]] ]]; then
echo "unsafe ${key} path" >&2
return 1
fi
clean="${raw#./}"
[[ -n "$clean" ]] || { echo "unsafe ${key} path" >&2; return 1; }
command -v realpath >/dev/null 2>&1 || { echo "realpath is required" >&2; return 1; }
candidate="$ROOT_DIR/$clean"
resolved="$(realpath -m -- "$candidate")" || { echo "unsafe ${key} path" >&2; return 1; }
case "$resolved" in
"$ROOT_DIR"/*) ;;
*) echo "unsafe ${key} path" >&2; return 1 ;;
esac
current="$ROOT_DIR"
IFS='/' read -r -a parts <<< "$clean"
for part in "${parts[@]}"; do
[[ -z "$part" || "$part" == "." ]] && continue
current="$current/$part"
if [[ -L "$current" ]]; then
echo "unsafe ${key} path" >&2
return 1
fi
done
printf '%s\n' "$resolved"
}
generate_key() {
local value=""
if command -v openssl >/dev/null 2>&1; then
@@ -89,20 +123,7 @@ write_key_cache() {
data_dir_raw="$(read_effective_value APP_DATA_DIR)"
[[ -n "$data_dir_raw" ]] || data_dir_raw="./data"
case "$data_dir_raw" in
*$'\n'* | *$'\r'* | *\\* | *'$'* | *'#'* | *'"'* | *"'") fail "APP_DATA_DIR contains unsupported dotenv characters" ;;
esac
case "$data_dir_raw" in
/*) data_dir_abs="$(realpath -m -- "$data_dir_raw")" ;;
*)
data_dir_abs="$(realpath -m -- "${ROOT_DIR}/${data_dir_raw#./}")"
case "$data_dir_abs" in
"${ROOT_DIR}" | "${ROOT_DIR}"/*) ;;
*) fail "Relative APP_DATA_DIR must stay inside the application directory" ;;
esac
;;
esac
data_dir_abs="$(resolve_app_path "APP_DATA_DIR" "$data_dir_raw")"
[[ "$data_dir_abs" != "/" ]] || fail "APP_DATA_DIR must not be the filesystem root"
if [[ -e "$data_dir_abs" && ! -d "$data_dir_abs" ]]; then
@@ -112,20 +133,7 @@ mkdir -p -- "$data_dir_abs"
db_data_dir_raw="$(read_effective_value DB_DATA_DIR)"
[[ -n "$db_data_dir_raw" ]] || db_data_dir_raw="./db-data"
case "$db_data_dir_raw" in
*$'\n'* | *$'\r'* | *\\* | *'$'* | *'#'* | *'"'* | *"'") fail "DB_DATA_DIR contains unsupported dotenv characters" ;;
esac
case "$db_data_dir_raw" in
/*) db_data_dir_abs="$(realpath -m -- "$db_data_dir_raw")" ;;
*)
db_data_dir_abs="$(realpath -m -- "${ROOT_DIR}/${db_data_dir_raw#./}")"
case "$db_data_dir_abs" in
"${ROOT_DIR}" | "${ROOT_DIR}"/*) ;;
*) fail "Relative DB_DATA_DIR must stay inside the application directory" ;;
esac
;;
esac
db_data_dir_abs="$(resolve_app_path "DB_DATA_DIR" "$db_data_dir_raw")"
[[ "$db_data_dir_abs" != "/" ]] || fail "DB_DATA_DIR must not be the filesystem root"
[[ "$db_data_dir_abs" != "$data_dir_abs" ]] || fail "DB_DATA_DIR must differ from APP_DATA_DIR"
@@ -159,7 +167,7 @@ secure_cookie="$(read_effective_value WHODB_SECURE)"
[[ "$secure_cookie" == "true" || "$secure_cookie" == "false" ]] || fail "WHODB_SECURE must be true or false"
write_key_cache "$cache_file" "$encryption_key"
set_env_value APP_DATA_DIR "$data_dir_raw"
set_env_value DB_DATA_DIR "$db_data_dir_raw"
set_env_value APP_DATA_DIR "$data_dir_abs"
set_env_value DB_DATA_DIR "$db_data_dir_abs"
set_env_value WHODB_ENCRYPTION_KEY "$encryption_key"
set_env_value WHODB_SECURE "$secure_cookie"
+35
View File
@@ -0,0 +1,35 @@
# WhoDB
## Introduction
WhoDB is a lightweight browser workspace for exploring, querying, and editing multiple database systems.
## Features
- Browse schemas and edit data across multiple database engines.
- Run queries, inspect relationships, and import or export data.
- Optionally connect local or hosted AI providers.
- Persist encrypted database login sessions.
- Persist and connect local SQLite or DuckDB files through a separate directory.
## Access and configuration
After installation, open `http://<server-ip>:8080`; the actual published port is the value of `PANEL_APP_PORT_HTTP` in the installation form.
- `APP_DATA_DIR` is mounted at `/data` for encrypted sessions and the key cache.
- `DB_DATA_DIR` is mounted at `/db` for local SQLite and DuckDB files. Enter a path relative to `/db`, such as `example.db`, in WhoDB.
- Leave `WHODB_ENCRYPTION_KEY` as `generate` to create and persist a 64-character hexadecimal key. Do not replace the key while existing sessions are in use.
- Set `WHODB_SECURE` to `true` when WhoDB is behind an HTTPS reverse proxy.
## Security and data handling
WhoDB stores database credentials in encrypted browser sessions. Protect the application `.env`, `APP_DATA_DIR`, `DB_DATA_DIR`, and backups. The Community image does not provide a separate global access login, so do not expose the port to an untrusted network; use a VPN, IP allowlist, or an authenticated 1Panel reverse proxy. Optional AI providers may receive user requests and database context, so review the selected provider's data and credential policy.
Back up both persistent directories before upgrades, migrations, or uninstalling.
## References
- Website: <https://whodb.com/>
- Source: <https://github.com/clidey/whodb>
- Documentation: <https://docs.whodb.com/>
- Docker persistence: <https://github.com/clidey/whodb/blob/main/README.md#docker-with-persistent-sessions>
+5 -1
View File
@@ -13,12 +13,16 @@ additionalProperties:
description:
en: A lightweight workspace for multiple databases
zh: 轻量级多数据库管理工作台
zh-Hant: 輕量級多資料庫管理工作台
zh-hant: 輕量級多資料庫管理工作台
ja: 複数データベース向けの軽量管理ワークスペース
ko: 여러 데이터베이스를 위한 가벼운 관리 작업 공간
ru: Легковесная рабочая среда для управления разными базами данных
ms: Ruang kerja ringan untuk mengurus pelbagai pangkalan data
pt-br: Espaco de trabalho leve para gerenciar varios bancos de dados
tr: Birden fazla veritabanı için hafif bir çalışma alanı
es-es: Espacio de trabajo ligero para múltiples bases de datos
fa: محیط کاری سبک برای چندین پایگاه داده
lo: ພື້ນທີ່ເຮັດວຽກຂະໜາດເບົາສຳລັບຫຼາຍຖານຂໍ້ມູນ
type: tool
crossVersionUpdate: true
limit: 1
-6
View File
@@ -1,6 +0,0 @@
PANEL_APP_PORT_HTTP=8080
APP_DATA_DIR=./data
DB_DATA_DIR=./db-data
WHODB_ENCRYPTION_KEY=generate
WHODB_SECURE=false
CONTAINER_NAME=whodb
-88
View File
@@ -1,88 +0,0 @@
additionalProperties:
formFields:
- default: 8080
edit: true
envKey: PANEL_APP_PORT_HTTP
labelEn: HTTP Port
labelZh: HTTP 端口
label:
en: HTTP Port
zh: HTTP 端口
zh-Hant: HTTP 埠
ja: HTTP ポート
ko: HTTP 포트
ru: HTTP-порт
ms: Port HTTP
pt-br: Porta HTTP
required: true
rule: paramPort
type: number
- default: ./data
edit: true
envKey: APP_DATA_DIR
labelEn: Data Directory
labelZh: 数据目录
label:
en: Data Directory
zh: 数据目录
zh-Hant: 資料目錄
ja: データディレクトリ
ko: 데이터 디렉터리
ru: Каталог данных
ms: Direktori data
pt-br: Diretorio de dados
required: true
type: text
- default: ./db-data
edit: true
envKey: DB_DATA_DIR
labelEn: Local Database Directory
labelZh: 本地数据库目录
label:
en: Local Database Directory
zh: 本地数据库目录
zh-Hant: 本機資料庫目錄
ja: ローカルデータベースディレクトリ
ko: 로컬 데이터베이스 디렉터리
ru: Каталог локальных баз данных
ms: Direktori pangkalan data setempat
pt-br: Diretorio de bancos de dados locais
required: true
type: text
- default: generate
edit: true
envKey: WHODB_ENCRYPTION_KEY
labelEn: Session Encryption Key
labelZh: 会话加密密钥
label:
en: Session Encryption Key
zh: 会话加密密钥
zh-Hant: 工作階段加密金鑰
ja: セッション暗号化キー
ko: 세션 암호화 키
ru: Ключ шифрования сеансов
ms: Kunci penyulitan sesi
pt-br: Chave de criptografia da sessao
required: true
type: password
- default: "false"
edit: true
envKey: WHODB_SECURE
labelEn: HTTPS Secure Cookie
labelZh: HTTPS 安全 Cookie
label:
en: HTTPS Secure Cookie
zh: HTTPS 安全 Cookie
zh-Hant: HTTPS 安全 Cookie
ja: HTTPS セキュア Cookie
ko: HTTPS 보안 쿠키
ru: Защищенные Cookie для HTTPS
ms: Cookie selamat HTTPS
pt-br: Cookie seguro HTTPS
required: true
type: select
values:
- label: "false"
value: "false"
- label: "true"
value: "true"
View File
View File
-165
View File
@@ -1,165 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
ENV_FILE="${ENV_FILE:-${ROOT_DIR}/.env}"
fail() {
printf '%s\n' "$1" >&2
exit 1
}
strip_matching_quotes() {
local value="$1"
if [[ ${#value} -ge 2 ]]; then
if [[ "${value:0:1}" == '"' && "${value: -1}" == '"' ]]; then
value="${value:1:${#value}-2}"
elif [[ "${value:0:1}" == "'" && "${value: -1}" == "'" ]]; then
value="${value:1:${#value}-2}"
fi
fi
printf '%s\n' "$value"
}
read_env_value() {
local key="$1"
local value=""
if [[ -f "$ENV_FILE" ]]; then
value="$(grep -E "^${key}=" "$ENV_FILE" | tail -n 1 | cut -d '=' -f 2- || true)"
fi
strip_matching_quotes "$value"
}
read_effective_value() {
local key="$1"
if [[ -v "$key" ]]; then
strip_matching_quotes "${!key}"
else
read_env_value "$key"
fi
}
set_env_value() {
local key="$1"
local value="$2"
local temp_file
temp_file="$(mktemp "${ROOT_DIR}/.whodb-env.tmp.XXXXXX")"
awk -v key="$key" -v value="$value" '
BEGIN { written = 0 }
$0 ~ "^" key "=" {
if (!written) {
print key "=" value
written = 1
}
next
}
{ print }
END {
if (!written) print key "=" value
}
' "$ENV_FILE" > "$temp_file"
chmod --reference="$ENV_FILE" "$temp_file"
mv -f -- "$temp_file" "$ENV_FILE"
}
generate_key() {
local value=""
if command -v openssl >/dev/null 2>&1; then
value="$(openssl rand -hex 32)"
elif [[ -r /dev/urandom ]] && command -v od >/dev/null 2>&1; then
value="$(od -An -N32 -tx1 /dev/urandom | tr -d ' \n')"
fi
[[ "$value" =~ ^[0-9a-f]{64}$ ]] || fail "Unable to generate a secure WhoDB encryption key"
printf '%s\n' "$value"
}
write_key_cache() {
local cache_file="$1"
local value="$2"
local temp_file
umask 077
temp_file="$(mktemp "${cache_file}.tmp.XXXXXX")"
printf '%s\n' "$value" > "$temp_file"
chmod 600 "$temp_file"
mv -f -- "$temp_file" "$cache_file"
}
[[ -f "$ENV_FILE" ]] || fail "$ENV_FILE not found"
[[ ! -L "$ENV_FILE" ]] || fail "$ENV_FILE must not be a symbolic link"
data_dir_raw="$(read_effective_value APP_DATA_DIR)"
[[ -n "$data_dir_raw" ]] || data_dir_raw="./data"
case "$data_dir_raw" in
*$'\n'* | *$'\r'* | *\\* | *'$'* | *'#'* | *'"'* | *"'") fail "APP_DATA_DIR contains unsupported dotenv characters" ;;
esac
case "$data_dir_raw" in
/*) data_dir_abs="$(realpath -m -- "$data_dir_raw")" ;;
*)
data_dir_abs="$(realpath -m -- "${ROOT_DIR}/${data_dir_raw#./}")"
case "$data_dir_abs" in
"${ROOT_DIR}" | "${ROOT_DIR}"/*) ;;
*) fail "Relative APP_DATA_DIR must stay inside the application directory" ;;
esac
;;
esac
[[ "$data_dir_abs" != "/" ]] || fail "APP_DATA_DIR must not be the filesystem root"
if [[ -e "$data_dir_abs" && ! -d "$data_dir_abs" ]]; then
fail "APP_DATA_DIR must be a directory"
fi
mkdir -p -- "$data_dir_abs"
db_data_dir_raw="$(read_effective_value DB_DATA_DIR)"
[[ -n "$db_data_dir_raw" ]] || db_data_dir_raw="./db-data"
case "$db_data_dir_raw" in
*$'\n'* | *$'\r'* | *\\* | *'$'* | *'#'* | *'"'* | *"'") fail "DB_DATA_DIR contains unsupported dotenv characters" ;;
esac
case "$db_data_dir_raw" in
/*) db_data_dir_abs="$(realpath -m -- "$db_data_dir_raw")" ;;
*)
db_data_dir_abs="$(realpath -m -- "${ROOT_DIR}/${db_data_dir_raw#./}")"
case "$db_data_dir_abs" in
"${ROOT_DIR}" | "${ROOT_DIR}"/*) ;;
*) fail "Relative DB_DATA_DIR must stay inside the application directory" ;;
esac
;;
esac
[[ "$db_data_dir_abs" != "/" ]] || fail "DB_DATA_DIR must not be the filesystem root"
[[ "$db_data_dir_abs" != "$data_dir_abs" ]] || fail "DB_DATA_DIR must differ from APP_DATA_DIR"
if [[ -e "$db_data_dir_abs" && ! -d "$db_data_dir_abs" ]]; then
fail "DB_DATA_DIR must be a directory"
fi
mkdir -p -- "$db_data_dir_abs"
cache_file="${data_dir_abs}/.whodb_encryption_key"
[[ ! -L "$cache_file" ]] || fail "WhoDB encryption key cache must not be a symbolic link"
if [[ -e "$cache_file" && ! -f "$cache_file" ]]; then
fail "WhoDB encryption key cache must be a regular file"
fi
cached_key=""
if [[ -s "$cache_file" ]]; then
cached_key="$(sed -n '1p' "$cache_file")"
[[ "$cached_key" =~ ^[0-9a-f]{64}$ ]] || fail "Persisted WhoDB encryption key is invalid"
fi
requested_key="$(read_effective_value WHODB_ENCRYPTION_KEY)"
if [[ -z "$requested_key" || "$requested_key" == "generate" ]]; then
encryption_key="${cached_key:-$(generate_key)}"
else
[[ "$requested_key" =~ ^[0-9a-fA-F]{64}$ ]] || fail "WHODB_ENCRYPTION_KEY must be generate or exactly 64 hexadecimal characters"
encryption_key="${requested_key,,}"
fi
secure_cookie="$(read_effective_value WHODB_SECURE)"
[[ -n "$secure_cookie" ]] || secure_cookie=false
[[ "$secure_cookie" == "true" || "$secure_cookie" == "false" ]] || fail "WHODB_SECURE must be true or false"
write_key_cache "$cache_file" "$encryption_key"
set_env_value APP_DATA_DIR "$data_dir_raw"
set_env_value DB_DATA_DIR "$db_data_dir_raw"
set_env_value WHODB_ENCRYPTION_KEY "$encryption_key"
set_env_value WHODB_SECURE "$secure_cookie"
-2
View File
@@ -1,2 +0,0 @@
#!/bin/bash
docker-compose down --volumes
-5
View File
@@ -1,5 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
exec "${SCRIPT_DIR}/init.sh"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB