mirror of
https://github.com/community-scripts/ProxmoxVE.git
synced 2025-11-24 14:35:23 +00:00
Compare commits
47 Commits
MickLesk-p
...
2025-11-23
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
279ed5325c | ||
|
|
b66fb43fff | ||
|
|
ff7b321065 | ||
|
|
6a5ad42279 | ||
|
|
4631f1c09e | ||
|
|
6bcf8b3097 | ||
|
|
7ac9023a57 | ||
|
|
07e46a4585 | ||
|
|
4003aad7d5 | ||
|
|
b9809d6768 | ||
|
|
70d59b825c | ||
|
|
72a39012b6 | ||
|
|
4134f68fb4 | ||
|
|
c9e7662c5c | ||
|
|
4d6fa0a73d | ||
|
|
84412d9d9c | ||
|
|
581fed294f | ||
|
|
c72a810179 | ||
|
|
a335779c5f | ||
|
|
05f3be1dbb | ||
|
|
040ccaafe7 | ||
|
|
156d6e46a1 | ||
|
|
b6a47aa6c5 | ||
|
|
fc15ef3fd1 | ||
|
|
899e1f6b1f | ||
|
|
afe330173e | ||
|
|
89198b0601 | ||
|
|
b5c82137cb | ||
|
|
ca7478f815 | ||
|
|
5410542d25 | ||
|
|
a08869b843 | ||
|
|
9777abe56d | ||
|
|
fae0552c04 | ||
|
|
c4ad9b5361 | ||
|
|
61477cc3f8 | ||
|
|
265a02286c | ||
|
|
e4b2e051bf | ||
|
|
e05a351712 | ||
|
|
053e5ab128 | ||
|
|
1f6a141341 | ||
|
|
b47d4747d6 | ||
|
|
27bd55364c | ||
|
|
8ad68e7c97 | ||
|
|
088186712a | ||
|
|
6dd0195912 | ||
|
|
afa385d225 | ||
|
|
f43efc1431 |
56
CHANGELOG.md
56
CHANGELOG.md
@@ -10,6 +10,62 @@
|
||||
> [!CAUTION]
|
||||
Exercise vigilance regarding copycat or coat-tailing sites that seek to exploit the project's popularity for potentially malicious purposes.
|
||||
|
||||
## 2025-11-24
|
||||
|
||||
## 2025-11-23
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- core: remove journal log rotation [@MickLesk](https://github.com/MickLesk) ([#9392](https://github.com/community-scripts/ProxmoxVE/pull/9392))
|
||||
- [LibreNMS] Correcting mariadb sed string for Debian 13 default in install/librenms-install.sh, website config for Debian 13 #9369 [@htmlspinnr](https://github.com/htmlspinnr) ([#9370](https://github.com/community-scripts/ProxmoxVE/pull/9370))
|
||||
- fix: Snipe-IT update check failure [@ruanmed](https://github.com/ruanmed) ([#9371](https://github.com/community-scripts/ProxmoxVE/pull/9371))
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- PVE Kernel Clean: Add info about currently running kernel [@tremor021](https://github.com/tremor021) ([#9388](https://github.com/community-scripts/ProxmoxVE/pull/9388))
|
||||
|
||||
- #### 🔧 Refactor
|
||||
|
||||
- Update glpi-install.sh to remove install.php [@CrazyWolf13](https://github.com/CrazyWolf13) ([#9378](https://github.com/community-scripts/ProxmoxVE/pull/9378))
|
||||
|
||||
### 🌐 Website
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- fix: enhance back navigation in NotFoundPage component and remove unused deps [@BramSuurdje](https://github.com/BramSuurdje) ([#9341](https://github.com/community-scripts/ProxmoxVE/pull/9341))
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- feat(frontend): add script disable functionality with visual indicators [@AlphaLawless](https://github.com/AlphaLawless) ([#9374](https://github.com/community-scripts/ProxmoxVE/pull/9374))
|
||||
|
||||
## 2025-11-22
|
||||
|
||||
### 🆕 New Scripts
|
||||
|
||||
- Upgopher ([#9360](https://github.com/community-scripts/ProxmoxVE/pull/9360))
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
- Expand support to Proxmox VE 9.1 in VM scripts [@MickLesk](https://github.com/MickLesk) ([#9351](https://github.com/community-scripts/ProxmoxVE/pull/9351))
|
||||
|
||||
- #### 🐞 Bug Fixes
|
||||
|
||||
- fix: Snipe-IT install and update failure due to new repository url [@ruanmed](https://github.com/ruanmed) ([#9362](https://github.com/community-scripts/ProxmoxVE/pull/9362))
|
||||
- glpi - allow migration of existing databases [@moodyblue](https://github.com/moodyblue) ([#9353](https://github.com/community-scripts/ProxmoxVE/pull/9353))
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- Refactor cleanup steps to use cleanup_lxc function (install/ Folder) [@MickLesk](https://github.com/MickLesk) ([#9354](https://github.com/community-scripts/ProxmoxVE/pull/9354))
|
||||
- Remove redundant cleanup steps from update scripts (ct/ Folder) [@MickLesk](https://github.com/MickLesk) ([#9359](https://github.com/community-scripts/ProxmoxVE/pull/9359))
|
||||
|
||||
### 🌐 Website
|
||||
|
||||
- #### ✨ New Features
|
||||
|
||||
- Refactor /data page [@BramSuurdje](https://github.com/BramSuurdje) ([#9343](https://github.com/community-scripts/ProxmoxVE/pull/9343))
|
||||
|
||||
## 2025-11-21
|
||||
|
||||
### 🚀 Updated Scripts
|
||||
|
||||
@@ -57,15 +57,6 @@ function update_script() {
|
||||
$STD composer install --no-dev --prefer-dist
|
||||
php artisan 2fauth:install
|
||||
$STD systemctl restart nginx
|
||||
|
||||
msg_info "Cleaning Up"
|
||||
if dpkg -l | grep -q 'php8.2'; then
|
||||
$STD apt remove --purge -y php8.2*
|
||||
fi
|
||||
$STD apt -y autoremove
|
||||
$STD apt -y autoclean
|
||||
$STD apt -y clean
|
||||
msg_ok "Cleanup Completed"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
|
||||
@@ -62,6 +62,7 @@ function update_script() {
|
||||
cd /opt/adventurelog/frontend || exit
|
||||
$STD pnpm i
|
||||
$STD pnpm build
|
||||
rm -rf /opt/adventurelog-backup
|
||||
msg_ok "Updated ${APP}"
|
||||
|
||||
msg_info "Starting Services"
|
||||
@@ -69,10 +70,6 @@ function update_script() {
|
||||
systemctl start adventurelog-backend
|
||||
systemctl start adventurelog-frontend
|
||||
msg_ok "Services Started"
|
||||
|
||||
msg_info "Cleaning Up"
|
||||
rm -rf /opt/adventurelog-backup
|
||||
msg_ok "Cleaned"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
|
||||
@@ -38,15 +38,13 @@ function update_script() {
|
||||
curl -fsSL -o tika-server-standard-${RELEASE}.jar "https://dlcdn.apache.org/tika/${RELEASE}/tika-server-standard-${RELEASE}.jar"
|
||||
mv --force tika-server-standard.jar tika-server-standard-prev-version.jar
|
||||
mv tika-server-standard-${RELEASE}.jar tika-server-standard.jar
|
||||
rm -rf /opt/apache-tika/tika-server-standard-prev-version.jar
|
||||
echo "${RELEASE}" >/opt/${APP}_version.txt
|
||||
msg_ok "Updated ${APP} to v${RELEASE}"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start apache-tika
|
||||
msg_ok "Started Service"
|
||||
msg_info "Cleaning Up"
|
||||
rm -rf /opt/apache-tika/tika-server-standard-prev-version.jar
|
||||
msg_ok "Cleanup Completed"
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "No update required. ${APP} is already at v${RELEASE}"
|
||||
|
||||
@@ -31,15 +31,11 @@ function update_script() {
|
||||
fi
|
||||
|
||||
if check_for_gh_release "authelia" "authelia/authelia"; then
|
||||
$STD apt-get update
|
||||
$STD apt-get -y upgrade
|
||||
$STD apt update
|
||||
$STD apt -y upgrade
|
||||
|
||||
fetch_and_deploy_gh_release "authelia" "authelia/authelia" "binary"
|
||||
|
||||
msg_info "Cleaning Up"
|
||||
$STD apt-get -y autoremove
|
||||
$STD apt-get -y autoclean
|
||||
msg_ok "Cleanup Completed"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
|
||||
@@ -39,16 +39,13 @@ function update_script() {
|
||||
curl -fsSL "https://github.com/garethgeorge/backrest/releases/download/v${RELEASE}/backrest_Linux_x86_64.tar.gz" -o "$temp_file"
|
||||
tar xzf $temp_file -C /opt/backrest/bin
|
||||
chmod +x /opt/backrest/bin/backrest
|
||||
rm -f "$temp_file"
|
||||
echo "${RELEASE}" >/opt/${APP}_version.txt
|
||||
msg_ok "Updated ${APP} to ${RELEASE}"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start backrest
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -f "$temp_file"
|
||||
msg_ok "Cleaned up"
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "No update required. ${APP} is already at ${RELEASE}"
|
||||
|
||||
@@ -47,15 +47,12 @@ function update_script() {
|
||||
chmod -R 755 /opt/baikal/
|
||||
cd /opt/baikal
|
||||
$STD composer install
|
||||
rm -rf /opt/baikal-backup
|
||||
msg_ok "Configured Baikal"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start apache2
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -rf /opt/baikal-backup
|
||||
msg_ok "Cleaned"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
|
||||
@@ -54,15 +54,12 @@ function update_script() {
|
||||
$STD php artisan route:cache
|
||||
$STD php artisan event:cache
|
||||
chown -R www-data:www-data /opt/bar-assistant
|
||||
rm -rf /opt/bar-assistant-backup
|
||||
msg_ok "Updated Bar-Assistant"
|
||||
|
||||
msg_info "Starting nginx"
|
||||
systemctl start nginx
|
||||
msg_ok "Started nginx"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -rf /opt/bar-assistant-backup
|
||||
msg_ok "Cleaned"
|
||||
fi
|
||||
|
||||
if check_for_gh_release "vue-salt-rim" "karlomikus/vue-salt-rim"; then
|
||||
@@ -81,15 +78,12 @@ function update_script() {
|
||||
cd /opt/vue-salt-rim
|
||||
$STD npm install
|
||||
$STD npm run build
|
||||
rm -rf /opt/vue-salt-rim-backup
|
||||
msg_ok "Updated Vue Salt Rim"
|
||||
|
||||
msg_info "Starting nginx"
|
||||
systemctl start nginx
|
||||
msg_ok "Started nginx"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -rf /opt/vue-salt-rim-backup
|
||||
msg_ok "Cleaned"
|
||||
fi
|
||||
|
||||
if check_for_gh_release "meilisearch" "meilisearch/meilisearch"; then
|
||||
|
||||
@@ -57,15 +57,12 @@ function update_script() {
|
||||
chmod -R 755 /opt/bookstack /opt/bookstack/bootstrap/cache /opt/bookstack/public/uploads /opt/bookstack/storage
|
||||
chmod -R 775 /opt/bookstack/storage /opt/bookstack/bootstrap/cache /opt/bookstack/public/uploads
|
||||
chmod -R 640 /opt/bookstack/.env
|
||||
rm -rf /opt/bookstack-backup
|
||||
msg_ok "Configured BookStack"
|
||||
|
||||
msg_info "Starting Apache2"
|
||||
systemctl start apache2
|
||||
msg_ok "Started Apache2"
|
||||
|
||||
msg_info "Cleaning Up"
|
||||
rm -rf /opt/bookstack-backup
|
||||
msg_ok "Cleaned"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
|
||||
@@ -109,6 +109,7 @@ function update_script() {
|
||||
fi
|
||||
$STD uv run python manage.py migrate --noinput
|
||||
$STD uv run python manage.py collectstatic --noinput
|
||||
rm -f /tmp/dispatcharr_db_*.sql
|
||||
msg_ok "Migrations Complete"
|
||||
|
||||
msg_info "Starting Services"
|
||||
@@ -117,10 +118,6 @@ function update_script() {
|
||||
systemctl start dispatcharr-celerybeat
|
||||
systemctl start dispatcharr-daphne
|
||||
msg_ok "Started Services"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -f /tmp/dispatcharr_db_*.sql
|
||||
msg_ok "Cleanup completed"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
|
||||
@@ -76,11 +76,6 @@ function update_script() {
|
||||
portainer/agent
|
||||
msg_ok "Updated Portainer Agent"
|
||||
fi
|
||||
|
||||
msg_info "Cleaning up"
|
||||
$STD apt-get -y autoremove
|
||||
$STD apt-get -y autoclean
|
||||
msg_ok "Cleanup complete"
|
||||
msg_ok "Updated successfully!"
|
||||
exit
|
||||
}
|
||||
|
||||
@@ -50,16 +50,13 @@ function update_script() {
|
||||
$STD turbo run build --filter=@documenso/remix
|
||||
$STD npm run prisma:migrate-deploy
|
||||
$STD turbo daemon stop
|
||||
rm -rf /opt/v${RELEASE}.zip
|
||||
echo "${RELEASE}" >/opt/${APP}_version.txt
|
||||
msg_ok "Updated ${APP}"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start documenso
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning Up"
|
||||
rm -rf /opt/v${RELEASE}.zip
|
||||
msg_ok "Cleaned"
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "No update required. ${APP} is already at ${RELEASE}"
|
||||
|
||||
@@ -47,16 +47,13 @@ function update_script() {
|
||||
|
||||
msg_info "Installing EMQX"
|
||||
$STD apt-get install -y "$DEB_FILE"
|
||||
rm -f "$DEB_FILE"
|
||||
echo "$RELEASE" >~/.emqx
|
||||
msg_ok "Installed EMQX v${RELEASE}"
|
||||
|
||||
msg_info "Starting EMQX"
|
||||
systemctl start emqx
|
||||
echo "$RELEASE" >~/.emqx
|
||||
msg_ok "Started EMQX"
|
||||
|
||||
msg_info "Cleaning Up"
|
||||
rm -f "$DEB_FILE"
|
||||
msg_ok "Cleanup Completed"
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "No update required. EMQX is already at v${RELEASE}"
|
||||
|
||||
@@ -48,16 +48,13 @@ function update_script() {
|
||||
temp_file=$(mktemp)
|
||||
curl -fsSL https://fileflows.com/downloads/zip -o "$temp_file"
|
||||
$STD unzip -o -d /opt/fileflows "$temp_file"
|
||||
rm -rf "$temp_file"
|
||||
rm -rf "$backup_filename"
|
||||
msg_ok "Updated $APP to latest version"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start fileflows
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning Up"
|
||||
rm -rf "$temp_file"
|
||||
rm -rf "$backup_filename"
|
||||
msg_ok "Cleanup Completed"
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "No update required. ${APP} is already at latest version"
|
||||
|
||||
@@ -56,10 +56,6 @@ function update_script() {
|
||||
msg_info "Starting Service"
|
||||
systemctl start ghostfolio
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning Up"
|
||||
$STD npm cache clean --force
|
||||
msg_ok "Cleanup Completed"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
|
||||
6
ct/headers/upgopher
Normal file
6
ct/headers/upgopher
Normal file
@@ -0,0 +1,6 @@
|
||||
__ __ __
|
||||
/ / / /___ ____ _____ ____ / /_ ___ _____
|
||||
/ / / / __ \/ __ `/ __ \/ __ \/ __ \/ _ \/ ___/
|
||||
/ /_/ / /_/ / /_/ / /_/ / /_/ / / / / __/ /
|
||||
\____/ .___/\__, /\____/ .___/_/ /_/\___/_/
|
||||
/_/ /____/ /_/
|
||||
@@ -38,11 +38,6 @@ function update_script() {
|
||||
$STD apt-get update
|
||||
$STD apt-get -y upgrade
|
||||
msg_ok "Updating Packages"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
apt-get -y autoremove
|
||||
apt-get -y autoclean
|
||||
msg_ok "Cleaning up"
|
||||
fi
|
||||
|
||||
cd /opt/jellyseerr
|
||||
|
||||
@@ -49,15 +49,12 @@ function update_script() {
|
||||
cp -a keycloak.old/conf/. keycloak/conf/
|
||||
cp -a keycloak.old/providers/. keycloak/providers/ 2>/dev/null || true
|
||||
cp -a keycloak.old/themes/. keycloak/themes/ 2>/dev/null || true
|
||||
rm -rf keycloak.old
|
||||
msg_ok "Updated Keycloak"
|
||||
|
||||
msg_info "Restarting Service"
|
||||
systemctl restart keycloak
|
||||
msg_ok "Restarted Service"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -rf keycloak.old
|
||||
msg_ok "Cleanup complete"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
|
||||
@@ -50,15 +50,12 @@ function update_script() {
|
||||
$STD yarn install
|
||||
$STD yarn build
|
||||
chown -R www-data:www-data /opt/koillection/public/uploads
|
||||
rm -r /opt/koillection-backup
|
||||
msg_ok "Updated Koillection"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start apache2
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -r /opt/koillection-backup
|
||||
msg_ok "Cleaned"
|
||||
msg_ok "Updated Successfully!"
|
||||
fi
|
||||
exit
|
||||
|
||||
@@ -52,17 +52,14 @@ function update_script() {
|
||||
$STD yarn web:build
|
||||
$STD yarn prisma:deploy
|
||||
[ -d /opt/data.bak ] && mv /opt/data.bak /opt/linkwarden/data
|
||||
rm -rf ~/.cargo/registry ~/.cargo/git ~/.cargo/.package-cache ~/.rustup
|
||||
rm -rf /root/.cache/yarn
|
||||
rm -rf /opt/linkwarden/.next/cache
|
||||
msg_ok "Updated ${APP}"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start linkwarden
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -rf ~/.cargo/registry ~/.cargo/git ~/.cargo/.package-cache ~/.rustup
|
||||
rm -rf /root/.cache/yarn
|
||||
rm -rf /opt/linkwarden/.next/cache
|
||||
msg_ok "Cleaned"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
|
||||
@@ -42,15 +42,12 @@ function update_script() {
|
||||
mv /opt/listmonk-backup/config.toml /opt/listmonk/config.toml
|
||||
mv /opt/listmonk-backup/uploads /opt/listmonk/uploads
|
||||
$STD /opt/listmonk/listmonk --upgrade --yes --config /opt/listmonk/config.toml
|
||||
rm -rf /opt/listmonk-backup/
|
||||
msg_ok "Configured listmonk"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start listmonk
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -rf /opt/listmonk-backup/
|
||||
msg_ok "Cleaned"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
|
||||
@@ -53,15 +53,12 @@ function update_script() {
|
||||
msg_info "Configuring LubeLogger"
|
||||
chmod 700 /opt/lubelogger/CarCareTracker
|
||||
cp -rf /tmp/lubeloggerData/* /opt/lubelogger/
|
||||
rm -rf /tmp/lubeloggerData
|
||||
msg_ok "Configured LubeLogger"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start lubelogger
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -rf /tmp/lubeloggerData
|
||||
msg_ok "Cleaned"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
|
||||
@@ -38,15 +38,9 @@ function update_script() {
|
||||
curl -fsSL -o "$DEB_FILE" "$DEB_URL"
|
||||
$STD apt install "$DEB_FILE" -y
|
||||
systemctl restart lyrion
|
||||
$STD rm -f "$DEB_FILE"
|
||||
echo "${RELEASE}" >/opt/${APP}_version.txt
|
||||
msg_ok "Updated $APP to ${RELEASE}"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
$STD rm -f "$DEB_FILE"
|
||||
$STD apt -y autoremove
|
||||
$STD apt -y autoclean
|
||||
$STD apt -y clean
|
||||
msg_ok "Cleaned"
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "$APP is already up to date (${RELEASE})"
|
||||
|
||||
@@ -55,17 +55,13 @@ function update_script() {
|
||||
mv /usr/local/bin/minio /usr/local/bin/minio_bak
|
||||
curl -fsSL "https://dl.min.io/server/minio/release/linux-amd64/minio" -o /usr/local/bin/minio
|
||||
chmod +x /usr/local/bin/minio
|
||||
rm -f /usr/local/bin/minio_bak
|
||||
echo "${RELEASE}" >/opt/${APP}_version.txt
|
||||
msg_ok "Updated ${APP}"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start minio
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -f /usr/local/bin/minio_bak
|
||||
msg_ok "Cleaned"
|
||||
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "No update required. ${APP} is already at ${RELEASE}"
|
||||
|
||||
@@ -52,15 +52,12 @@ function update_script() {
|
||||
$STD php artisan monica:update --force
|
||||
chown -R www-data:www-data /opt/monica
|
||||
chmod -R 775 /opt/monica/storage
|
||||
rm -r /opt/monica-backup
|
||||
msg_ok "Configured monica"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start apache2
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -r /opt/monica-backup
|
||||
msg_ok "Cleaned"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
|
||||
@@ -57,17 +57,14 @@ function update_script() {
|
||||
fi
|
||||
|
||||
$STD /opt/netbox/upgrade.sh
|
||||
rm -r "/opt/v${RELEASE}.zip"
|
||||
rm -r /opt/netbox-backup
|
||||
echo "${RELEASE}" >/opt/${APP}_version.txt
|
||||
msg_ok "Updated $APP to v${RELEASE}"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start netbox netbox-rq
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -r "/opt/v${RELEASE}.zip"
|
||||
rm -r /opt/netbox-backup
|
||||
msg_ok "Cleaned"
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "No update required. ${APP} is already at v${RELEASE}"
|
||||
|
||||
@@ -41,15 +41,12 @@ function update_script() {
|
||||
cd /opt
|
||||
curl -fsSL "https://nextpvr.com/nextpvr-helper.deb" -o $(basename "https://nextpvr.com/nextpvr-helper.deb")
|
||||
$STD dpkg -i nextpvr-helper.deb
|
||||
rm -rf /opt/nextpvr-helper.deb
|
||||
msg_ok "Updated ${APP}"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start nextpvr-server
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning Up"
|
||||
rm -rf /opt/nextpvr-helper.deb
|
||||
msg_ok "Cleaned"
|
||||
msg_ok "Updated successfully!"
|
||||
exit
|
||||
}
|
||||
|
||||
@@ -42,17 +42,13 @@ function update_script() {
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
export DEBCONF_NOWARNINGS=yes
|
||||
$STD dpkg -i nxwitness-server-$RELEASE-linux_x64.deb
|
||||
rm -f /tmp/nxwitness-server-$RELEASE-linux_x64.deb
|
||||
echo "${RELEASE}" >/opt/${APP}_version.txt
|
||||
msg_ok "Updated ${APP}"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start networkoptix-root-tool networkoptix-mediaserver
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -f /tmp/nxwitness-server-$RELEASE-linux_x64.deb
|
||||
msg_ok "Cleaned"
|
||||
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "No update required. ${APP} is already at ${RELEASE}"
|
||||
|
||||
@@ -50,17 +50,13 @@ function update_script() {
|
||||
msg_info "Updating ${APP} to ${LATEST_VERSION}"
|
||||
curl -fsSL https://nightly.odoo.com/${RELEASE}/nightly/deb/odoo_${RELEASE}.latest_all.deb -o /opt/odoo.deb
|
||||
$STD apt install -y /opt/odoo.deb
|
||||
rm -f /opt/odoo.deb
|
||||
echo "$LATEST_VERSION" >/opt/${APP}_version.txt
|
||||
msg_ok "Updated ${APP} to ${LATEST_VERSION}"
|
||||
|
||||
msg_info "Starting ${APP} service"
|
||||
msg_info "Starting Service"
|
||||
systemctl start odoo
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning Up"
|
||||
rm -f /opt/odoo.deb
|
||||
msg_ok "Cleaned"
|
||||
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "No update required. ${APP} is already at ${LATEST_VERSION}"
|
||||
|
||||
@@ -43,16 +43,13 @@ function update_script() {
|
||||
mkdir -p /usr/local/lib/ollama
|
||||
tar -xzf "${TMP_TAR}" -C /usr/local/lib/ollama
|
||||
ln -sf /usr/local/lib/ollama/bin/ollama /usr/local/bin/ollama
|
||||
rm -f "${TMP_TAR}"
|
||||
echo "${RELEASE}" >/opt/Ollama_version.txt
|
||||
msg_ok "Updated Ollama to ${RELEASE}"
|
||||
|
||||
msg_info "Starting Services"
|
||||
systemctl start ollama
|
||||
msg_ok "Started Services"
|
||||
|
||||
msg_info "Cleaning Up"
|
||||
rm -f "${TMP_TAR}"
|
||||
msg_ok "Cleaned"
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "No update required. Ollama is already at ${RELEASE}"
|
||||
|
||||
@@ -39,17 +39,14 @@ function update_script() {
|
||||
tar -xzf onedev-latest.tar.gz
|
||||
$STD /opt/onedev-latest/bin/upgrade.sh /opt/onedev
|
||||
RELEASE=$(cat /opt/onedev/release.properties | grep "version" | cut -d'=' -f2)
|
||||
rm -rf /opt/onedev-latest
|
||||
rm -rf /opt/onedev-latest.tar.gz
|
||||
echo "${RELEASE}" >"/opt/${APP}_version.txt"
|
||||
msg_ok "Updated ${APP} to v${RELEASE}"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start onedev
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -rf /opt/onedev-latest
|
||||
rm -rf /opt/onedev-latest.tar.gz
|
||||
msg_ok "Cleaned"
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "No update required. ${APP} is already at v${RELEASE}."
|
||||
|
||||
@@ -65,17 +65,14 @@ EOF
|
||||
$STD pip install --no-cache-dir -r requirements.txt
|
||||
mkdir -p data/chromadb
|
||||
$STD npm install
|
||||
rm -rf /opt/v${RELEASE}.zip
|
||||
rm -rf /opt/paperless-ai_bak
|
||||
echo "${RELEASE}" >/opt/${APP}_version.txt
|
||||
msg_ok "Updated $APP to v${RELEASE}"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start paperless-ai
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning Up"
|
||||
rm -rf /opt/v${RELEASE}.zip
|
||||
rm -rf /opt/paperless-ai_bak
|
||||
msg_ok "Cleanup Completed"
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "No update required. ${APP} is already at v${RELEASE}"
|
||||
|
||||
@@ -47,16 +47,13 @@ function update_script() {
|
||||
export CC=musl-gcc
|
||||
CGO_ENABLED=1 go build -tags musl -o /dev/null github.com/mattn/go-sqlite3
|
||||
CGO_ENABLED=1 go build -tags musl -o paperless-gpt .
|
||||
rm -f $temp_file
|
||||
echo "${RELEASE}" >"/opt/paperless-gpt_version.txt"
|
||||
msg_ok "Updated Paperless-GPT to ${RELEASE}"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start paperless-gpt
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning Up"
|
||||
rm -f $temp_file
|
||||
msg_ok "Cleanup Completed"
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "No update required. ${APP} is already at ${RELEASE}"
|
||||
|
||||
@@ -52,17 +52,14 @@ function update_script() {
|
||||
$STD php bin/console cache:clear
|
||||
$STD php bin/console doctrine:migrations:migrate -n
|
||||
chown -R www-data:www-data /opt/partdb
|
||||
rm -r "/opt/v${RELEASE}.zip"
|
||||
rm -r /opt/partdb-backup
|
||||
echo "${RELEASE}" >/opt/${APP}_version.txt
|
||||
msg_ok "Updated $APP to v${RELEASE}"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start apache2
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -r "/opt/v${RELEASE}.zip"
|
||||
rm -r /opt/partdb-backup
|
||||
msg_ok "Cleaned"
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "No update required. ${APP} is already at v${RELEASE}"
|
||||
|
||||
@@ -67,6 +67,7 @@ function update_script() {
|
||||
$STD php artisan migrate --seed --force
|
||||
chown -R www-data:www-data /opt/pelican-panel
|
||||
chmod -R 755 /opt/pelican-panel/storage /opt/pelican-panel/bootstrap/cache/
|
||||
rm -rf "/opt/pelican-panel/panel.tar.gz"
|
||||
echo "${RELEASE}" >/opt/${APP}_version.txt
|
||||
msg_ok "Updated $APP to v${RELEASE}"
|
||||
|
||||
@@ -74,10 +75,6 @@ function update_script() {
|
||||
$STD php artisan queue:restart
|
||||
$STD php artisan up
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -rf "/opt/pelican-panel/panel.tar.gz"
|
||||
msg_ok "Cleaned"
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "No update required. ${APP} is already at v${RELEASE}"
|
||||
|
||||
@@ -70,6 +70,7 @@ EOF
|
||||
$STD php artisan migrate --seed --force --no-interaction
|
||||
chown -R www-data:www-data /opt/pterodactyl-panel/*
|
||||
chmod -R 755 /opt/pterodactyl-panel/storage /opt/pterodactyl-panel/bootstrap/cache/
|
||||
rm -rf "/opt/pterodactyl-panel/panel.tar.gz"
|
||||
echo "${RELEASE}" >/opt/${APP}_version.txt
|
||||
msg_ok "Updated $APP to v${RELEASE}"
|
||||
|
||||
@@ -77,10 +78,6 @@ EOF
|
||||
$STD php artisan queue:restart
|
||||
$STD php artisan up
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -rf "/opt/pterodactyl-panel/panel.tar.gz"
|
||||
msg_ok "Cleaned"
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "No update required. ${APP} is already at v${RELEASE}"
|
||||
|
||||
@@ -43,14 +43,11 @@ function update_script() {
|
||||
$STD apt remove --purge -y dotnet-sdk-8.0
|
||||
$STD apt install -y dotnet-sdk-9.0
|
||||
fi
|
||||
rm -rf /opt/rdtc-backup
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start rdtc
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning Up"
|
||||
rm -rf /opt/rdtc-backup
|
||||
msg_ok "Cleaned"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
|
||||
@@ -54,6 +54,7 @@ function update_script() {
|
||||
cd /tmp
|
||||
curl -fsSL https://dl.min.io/server/minio/release/linux-amd64/minio.deb -o minio.deb
|
||||
$STD dpkg -i minio.deb
|
||||
rm -f /tmp/minio.deb
|
||||
msg_ok "Updated Minio"
|
||||
|
||||
msg_info "Updating Browserless (Patience)"
|
||||
@@ -75,16 +76,12 @@ function update_script() {
|
||||
$STD npm run build:function
|
||||
$STD npm prune production
|
||||
mv /opt/browserless.env /opt/browserless/.env
|
||||
rm -f "$brwsr_tmp"
|
||||
msg_ok "Updated Browserless"
|
||||
|
||||
msg_info "Restarting services"
|
||||
systemctl start minio Reactive-Resume browserless
|
||||
msg_ok "Restarted services"
|
||||
|
||||
msg_info "Cleaning Up"
|
||||
rm -f /tmp/minio.deb
|
||||
rm -f "$brwsr_tmp"
|
||||
msg_ok "Cleanup Completed"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
|
||||
@@ -41,15 +41,12 @@ function update_script() {
|
||||
$STD npm install
|
||||
cp -f /opt/index.html /opt/revealjs
|
||||
sed -i '25s/localhost/0.0.0.0/g' /opt/revealjs/gulpfile.js
|
||||
rm -f /opt/index.html
|
||||
msg_ok "Updated $APP"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start revealjs
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning Up"
|
||||
rm -f /opt/index.html
|
||||
msg_ok "Cleanup Completed"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
|
||||
@@ -61,15 +61,12 @@ function update_script() {
|
||||
$STD pip install -r requirements.txt
|
||||
mv /opt/config.ini.bak /opt/soularr/config.ini
|
||||
mv /opt/run.sh.bak /opt/soularr/run.sh
|
||||
rm -rf /tmp/main.zip
|
||||
msg_ok "Updated soularr"
|
||||
|
||||
msg_info "Starting soularr timer"
|
||||
systemctl start soularr.timer
|
||||
msg_ok "Started soularr timer"
|
||||
|
||||
msg_info "Cleaning Up"
|
||||
rm -rf /tmp/main.zip
|
||||
msg_ok "Cleanup Completed"
|
||||
exit
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ function update_script() {
|
||||
sed -i '/index index.php;/i \ client_max_body_size 100M;' /etc/nginx/conf.d/snipeit.conf
|
||||
fi
|
||||
|
||||
if check_for_gh_release "snipe-it" "snipe/snipe-it"; then
|
||||
if check_for_gh_release "snipe-it" "grokability/snipe-it"; then
|
||||
msg_info "Stopping Services"
|
||||
systemctl stop nginx
|
||||
msg_ok "Services Stopped"
|
||||
@@ -40,7 +40,7 @@ function update_script() {
|
||||
mv /opt/snipe-it /opt/snipe-it-backup
|
||||
msg_ok "Backup created"
|
||||
|
||||
fetch_and_deploy_gh_release "snipe-it" "snipe/snipe-it" "tarball"
|
||||
fetch_and_deploy_gh_release "snipe-it" "grokability/snipe-it" "tarball"
|
||||
[[ "$(php -v 2>/dev/null)" == PHP\ 8.2* ]] && PHP_VERSION="8.3" PHP_MODULE="common,ctype,ldap,fileinfo,iconv,mysql,soap,xsl" PHP_FPM="YES" setup_php
|
||||
sed -i 's/php8.2/php8.3/g' /etc/nginx/conf.d/snipeit.conf
|
||||
setup_composer
|
||||
|
||||
@@ -43,17 +43,13 @@ function update_script() {
|
||||
cd spoolman
|
||||
$STD pip3 install -r requirements.txt
|
||||
curl -fsSL "https://raw.githubusercontent.com/Donkie/Spoolman/master/.env.example" -o ".env"
|
||||
rm -rf /opt/spoolman.zip
|
||||
echo "${RELEASE}" >/opt/${APP}_version.txt
|
||||
msg_ok "Updated ${APP} to ${RELEASE}"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start spoolman
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -rf /opt/spoolman.zip
|
||||
msg_ok "Cleaned"
|
||||
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "No update required. ${APP} is already at ${RELEASE}"
|
||||
|
||||
@@ -64,16 +64,13 @@ EOF
|
||||
cd /opt/tandoor
|
||||
$STD /opt/tandoor/.venv/bin/python manage.py migrate
|
||||
$STD /opt/tandoor/.venv/bin/python manage.py collectstatic --no-input
|
||||
rm -rf /opt/tandoor.bak
|
||||
msg_ok "Updated Trandoor"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start tandoor
|
||||
systemctl reload nginx
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning Up"
|
||||
rm -rf /opt/tandoor.bak
|
||||
msg_ok "Cleanup Completed"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
|
||||
@@ -45,16 +45,13 @@ function update_script() {
|
||||
export NODE_OPTIONS=--openssl-legacy-provider
|
||||
$STD npm i
|
||||
$STD yarn build
|
||||
rm -r "/opt/v${RELEASE}.tar.gz"
|
||||
echo "${RELEASE}" >/opt/${APP}_version.txt
|
||||
msg_ok "Updated TasmoCompiler"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start tasmocompiler
|
||||
msg_ok "Started Service"
|
||||
|
||||
echo "${RELEASE}" >/opt/${APP}_version.txt
|
||||
msg_info "Cleaning up"
|
||||
rm -r "/opt/v${RELEASE}.tar.gz"
|
||||
msg_ok "Cleaned"
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "No update required. ${APP} is already at v${RELEASE}"
|
||||
|
||||
@@ -37,11 +37,8 @@ function update_script() {
|
||||
$STD unzip Tdarr_Updater.zip
|
||||
chmod +x Tdarr_Updater
|
||||
$STD ./Tdarr_Updater
|
||||
msg_ok "Updated Tdarr"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -rf /opt/tdarr/Tdarr_Updater.zip
|
||||
msg_ok "Cleaned up"
|
||||
msg_ok "Updated Tdarr"
|
||||
msg_ok "Updated successfully!"
|
||||
exit
|
||||
}
|
||||
|
||||
@@ -38,11 +38,8 @@ function update_script() {
|
||||
msg_info "Updating Technitium DNS"
|
||||
curl -fsSL "https://download.technitium.com/dns/DnsServerPortable.tar.gz" -o /opt/DnsServerPortable.tar.gz
|
||||
$STD tar zxvf /opt/DnsServerPortable.tar.gz -C /opt/technitium/dns/
|
||||
msg_ok "Updated Technitium DNS"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -f /opt/DnsServerPortable.tar.gz
|
||||
msg_ok "Cleaned up"
|
||||
msg_ok "Updated Technitium DNS"
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "No update required. Technitium DNS is already at v${RELEASE}."
|
||||
|
||||
@@ -41,15 +41,12 @@ function update_script() {
|
||||
|
||||
msg_info "Restoring data"
|
||||
cp -R /opt/teddycloud_bak/certs /opt/teddycloud_bak/config /opt/teddycloud_bak/data /opt/teddycloud
|
||||
rm -rf /opt/teddycloud_bak
|
||||
msg_ok "Data restored"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start teddycloud
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -rf /opt/teddycloud_bak
|
||||
msg_ok "Cleaned up"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
|
||||
15
ct/tianji.sh
15
ct/tianji.sh
@@ -43,7 +43,7 @@ function update_script() {
|
||||
|
||||
fetch_and_deploy_gh_release "tianji" "msgbyte/tianji"
|
||||
|
||||
msg_info "Updating ${APP}"
|
||||
msg_info "Updating Tianji"
|
||||
cd /opt/tianji
|
||||
export NODE_OPTIONS="--max_old_space_size=4096"
|
||||
$STD pnpm install --filter @tianji/client... --config.dedupe-peer-dependents=false --frozen-lockfile
|
||||
@@ -55,7 +55,11 @@ function update_script() {
|
||||
mv /opt/.env /opt/tianji/src/server/.env
|
||||
cd src/server
|
||||
$STD pnpm db:migrate:apply
|
||||
msg_ok "Updated ${APP}"
|
||||
rm -rf /opt/tianji_bak
|
||||
rm -rf /opt/tianji/src/client
|
||||
rm -rf /opt/tianji/website
|
||||
rm -rf /opt/tianji/reporter
|
||||
msg_ok "Updated Tianji"
|
||||
|
||||
msg_info "Updating AppRise"
|
||||
$STD uv pip install apprise cryptography --system
|
||||
@@ -64,13 +68,6 @@ function update_script() {
|
||||
msg_info "Starting Service"
|
||||
systemctl start tianji
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -rf /opt/tianji_bak
|
||||
rm -rf /opt/tianji/src/client
|
||||
rm -rf /opt/tianji/website
|
||||
rm -rf /opt/tianji/reporter
|
||||
msg_ok "Cleaned"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
|
||||
@@ -50,18 +50,12 @@ function update_script() {
|
||||
mv /opt/traccar.xml /opt/traccar/conf
|
||||
[[ -d /opt/data ]] && mv /opt/data /opt/traccar
|
||||
[[ -d /opt/media ]] && mv /opt/media /opt/traccar
|
||||
[ -f README.txt ] || [ -f traccar.run ] && rm -f README.txt traccar.run
|
||||
msg_ok "Data restored"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start traccar
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
[ -f README.txt ] || [ -f traccar.run ] && rm -f README.txt traccar.run
|
||||
$STD apt -y autoremove
|
||||
$STD apt -y autoclean
|
||||
$STD apt -y clean
|
||||
msg_ok "Cleaned up"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
|
||||
@@ -55,14 +55,8 @@ function update_script() {
|
||||
msg_info "Restoring Database"
|
||||
mkdir -p "$(dirname "${DB_RESTORE_PATH}")"
|
||||
cp -r /opt/trilium_backup/$(basename "${DB_PATH}") "${DB_RESTORE_PATH}"
|
||||
msg_ok "Restored Database"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -rf /opt/trilium_backup
|
||||
$STD apt -y autoremove
|
||||
$STD apt -y autoclean
|
||||
$STD apt -y clean
|
||||
msg_ok "Cleaned"
|
||||
msg_ok "Restored Database"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start trilium
|
||||
|
||||
@@ -43,12 +43,6 @@ function update_script() {
|
||||
msg_info "Starting Service"
|
||||
systemctl start uhf-server
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
$STD apt -y autoremove
|
||||
$STD apt -y autoclean
|
||||
$STD apt -y clean
|
||||
msg_ok "Cleaned"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
|
||||
54
ct/upgopher.sh
Normal file
54
ct/upgopher.sh
Normal file
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env bash
|
||||
source <(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
|
||||
# Copyright (c) 2021-2025 community-scripts ORG
|
||||
# Author: Eduard González (wanetty)
|
||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
|
||||
# Source: https://github.com/wanetty/upgopher
|
||||
|
||||
APP="Upgopher"
|
||||
var_tags="${var_tags:-file-sharing}"
|
||||
var_cpu="${var_cpu:-1}"
|
||||
var_ram="${var_ram:-512}"
|
||||
var_disk="${var_disk:-4}"
|
||||
var_os="${var_os:-debian}"
|
||||
var_version="${var_version:-13}"
|
||||
var_unprivileged="${var_unprivileged:-1}"
|
||||
|
||||
header_info "$APP"
|
||||
variables
|
||||
color
|
||||
catch_errors
|
||||
|
||||
function update_script() {
|
||||
header_info
|
||||
check_container_storage
|
||||
check_container_resources
|
||||
if [[ ! -d /opt/upgopher ]]; then
|
||||
msg_error "No ${APP} Installation Found!"
|
||||
exit
|
||||
fi
|
||||
|
||||
if check_for_gh_release "upgopher" "wanetty/upgopher"; then
|
||||
msg_info "Stopping Service"
|
||||
systemctl stop upgopher
|
||||
msg_ok "Stopped Service"
|
||||
|
||||
fetch_and_deploy_gh_release "upgopher" "wanetty/upgopher" "prebuild" "latest" "/opt/upgopher" "upgopher_*_linux_amd64.tar.gz"
|
||||
chmod +x /opt/upgopher/upgopher
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start upgopher
|
||||
msg_ok "Started Service"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
}
|
||||
|
||||
start
|
||||
build_container
|
||||
description
|
||||
|
||||
msg_ok "Completed Successfully!\n"
|
||||
echo -e "${CREATING}${GN}${APP} setup has been successfully initialized!${CL}"
|
||||
echo -e "${INFO}${YW} Access it using the following URL:${CL}"
|
||||
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:9090${CL}"
|
||||
@@ -57,11 +57,8 @@ function update_script() {
|
||||
else
|
||||
cp target/release/vaultwarden /opt/vaultwarden/bin/
|
||||
fi
|
||||
msg_ok "Updated VaultWarden"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
cd ~ && rm -rf vaultwarden
|
||||
msg_ok "Cleaned"
|
||||
msg_ok "Updated VaultWarden"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start vaultwarden
|
||||
@@ -77,11 +74,8 @@ function update_script() {
|
||||
msg_info "Updating Web-Vault to $WVRELEASE"
|
||||
$STD curl -fsSLO https://github.com/dani-garcia/bw_web_builds/releases/download/"$WVRELEASE"/bw_web_"$WVRELEASE".tar.gz
|
||||
$STD tar -zxf bw_web_"$WVRELEASE".tar.gz -C /opt/vaultwarden/
|
||||
msg_ok "Updated Web-Vault"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm bw_web_"$WVRELEASE".tar.gz
|
||||
msg_ok "Cleaned"
|
||||
msg_ok "Updated Web-Vault"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start vaultwarden
|
||||
|
||||
@@ -39,16 +39,13 @@ function update_script() {
|
||||
curl -fsSL "https://dl.vikunja.io/vikunja/$RELEASE/vikunja-$RELEASE-amd64.deb" -o $(basename "https://dl.vikunja.io/vikunja/$RELEASE/vikunja-$RELEASE-amd64.deb")
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
$STD dpkg -i vikunja-"$RELEASE"-amd64.deb
|
||||
rm -rf /opt/vikunja-"$RELEASE"-amd64.deb
|
||||
echo "${RELEASE}" >/opt/${APP}_version.txt
|
||||
msg_ok "Updated ${APP}"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start vikunja
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning Up"
|
||||
rm -rf /opt/vikunja-"$RELEASE"-amd64.deb
|
||||
msg_ok "Cleaned"
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "No update required. ${APP} is already at ${RELEASE}"
|
||||
|
||||
@@ -73,16 +73,13 @@ EOF
|
||||
cp -f wastebin* /opt/wastebin/
|
||||
chmod +x /opt/wastebin/wastebin
|
||||
chmod +x /opt/wastebin/wastebin-ctl
|
||||
rm -f "$temp_file"
|
||||
echo "${RELEASE}" >/opt/${APP}_version.txt
|
||||
msg_ok "Updated Wastebin"
|
||||
|
||||
msg_info "Starting Wastebin"
|
||||
systemctl start wastebin
|
||||
msg_ok "Started Wastebin"
|
||||
|
||||
msg_info "Cleaning Up"
|
||||
rm -f "$temp_file"
|
||||
msg_ok "Cleanup Completed"
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "No update required. ${APP} is already at v${RELEASE}"
|
||||
|
||||
@@ -37,10 +37,7 @@ function update_script() {
|
||||
fetch_and_deploy_gh_release "watchyourlan" "aceberg/WatchYourLAN" "binary"
|
||||
cp -R config.yaml /data/config.yaml
|
||||
sed -i 's|/etc/watchyourlan/config.yaml|/data/config.yaml|' /lib/systemd/system/watchyourlan.service
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm ~/config.yaml
|
||||
msg_ok "Cleaned up"
|
||||
|
||||
msg_info "Starting service"
|
||||
systemctl enable -q --now watchyourlan
|
||||
|
||||
@@ -45,16 +45,13 @@ function update_script() {
|
||||
$STD python3 manage.py collectstatic --no-input
|
||||
$STD yarn install
|
||||
$STD yarn build:css:sass
|
||||
rm -rf "$temp_file"
|
||||
echo "${RELEASE}" >/opt/${APP}_version.txt
|
||||
msg_ok "Updated $APP to v${RELEASE}"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start wger
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning Up"
|
||||
rm -rf "$temp_file"
|
||||
msg_ok "Cleanup Completed"
|
||||
msg_ok "Updated successfully!"
|
||||
else
|
||||
msg_ok "No update required. ${APP} is already at v${RELEASE}"
|
||||
|
||||
@@ -54,15 +54,12 @@ function update_script() {
|
||||
msg_info "Restoring Data"
|
||||
cp -R /opt/wikijs-backup/* /opt/wikijs
|
||||
$SQLITE_INSTALL && $STD npm rebuild sqlite3
|
||||
rm -rf /opt/wikijs-backup
|
||||
msg_ok "Restored Data"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start wikijs
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning Up"
|
||||
rm -rf /opt/wikijs-backup
|
||||
msg_ok "Cleanup Completed"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
|
||||
@@ -56,15 +56,12 @@ function update_script() {
|
||||
if ! grep -q 'frozen' /opt/wizarr/start.sh; then
|
||||
sed -i 's/run/& --frozen/' /opt/wizarr/start.sh
|
||||
fi
|
||||
rm -rf "$BACKUP_FILE"
|
||||
msg_ok "Updated Wizarr"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start wizarr
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning Up"
|
||||
rm -rf "$BACKUP_FILE"
|
||||
msg_ok "Cleanup Completed"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
|
||||
@@ -59,6 +59,7 @@ function update_script() {
|
||||
xargs -I{} echo "https://repo.zabbix.com/zabbix/{}/release/debian/pool/main/z/zabbix-release/zabbix-release_latest+debian13_all.deb")" \
|
||||
-o /tmp/zabbix-release_latest+debian13_all.deb
|
||||
$STD dpkg -i zabbix-release_latest+debian13_all.deb
|
||||
rm -rf /tmp/zabbix-release_latest+debian13_all.deb
|
||||
$STD apt update
|
||||
|
||||
$STD apt install --only-upgrade zabbix-server-pgsql zabbix-frontend-php php8.4-pgsql
|
||||
@@ -88,13 +89,6 @@ function update_script() {
|
||||
systemctl start "$AGENT_SERVICE"
|
||||
systemctl restart apache2
|
||||
msg_ok "Started Services"
|
||||
|
||||
msg_info "Cleaning Up"
|
||||
rm -rf /tmp/zabbix-release_latest+debian13_all.deb
|
||||
$STD apt -y autoremove
|
||||
$STD apt -y autoclean
|
||||
$STD apt -y clean
|
||||
msg_ok "Cleaned"
|
||||
msg_ok "Updated successfully!"
|
||||
exit
|
||||
}
|
||||
|
||||
@@ -50,15 +50,12 @@ function update_script() {
|
||||
grep -q "^packageImportMethod" ./pnpm-workspace.yaml || echo "packageImportMethod: hardlink" >>./pnpm-workspace.yaml
|
||||
$STD pnpm install --frozen-lockfile
|
||||
$STD pnpm build
|
||||
rm -rf /opt/z2m_backup
|
||||
msg_ok "Updated Zigbee2MQTT"
|
||||
|
||||
msg_info "Starting Service"
|
||||
systemctl start zigbee2mqtt
|
||||
msg_ok "Started Service"
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -rf /opt/z2m_backup
|
||||
msg_ok "Cleaned up"
|
||||
msg_ok "Updated successfully!"
|
||||
fi
|
||||
exit
|
||||
|
||||
934
frontend/bun.lock
generated
934
frontend/bun.lock
generated
File diff suppressed because it is too large
Load Diff
10
frontend/package.json
generated
10
frontend/package.json
generated
@@ -23,6 +23,7 @@
|
||||
"@radix-ui/react-label": "^2.1.2",
|
||||
"@radix-ui/react-navigation-menu": "^1.2.5",
|
||||
"@radix-ui/react-popover": "^1.1.6",
|
||||
"@radix-ui/react-scroll-area": "^1.2.10",
|
||||
"@radix-ui/react-select": "^2.1.6",
|
||||
"@radix-ui/react-separator": "^1.1.2",
|
||||
"@radix-ui/react-slot": "^1.1.2",
|
||||
@@ -36,27 +37,24 @@
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"date-fns": "^4.1.0",
|
||||
|
||||
"framer-motion": "^11.18.2",
|
||||
"fuse.js": "^7.1.0",
|
||||
"lucide-react": "^0.542.0",
|
||||
"lucide-react": "^0.554.0",
|
||||
"mini-svg-data-uri": "^1.4.4",
|
||||
"motion": "^12.23.12",
|
||||
"next": "15.5.2",
|
||||
"next-themes": "^0.4.4",
|
||||
"nuqs": "^2.4.1",
|
||||
"pocketbase": "^0.21.5",
|
||||
"prettier-plugin-organize-imports": "^4.1.0",
|
||||
"react": "19.0.0",
|
||||
"react-chartjs-2": "^5.3.0",
|
||||
"react-code-blocks": "^0.1.6",
|
||||
"react-datepicker": "^7.6.0",
|
||||
"react-day-picker": "^9.4.3",
|
||||
"react-dom": "19.0.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-simple-typewriter": "^5.0.1",
|
||||
"react-use-measure": "^2.1.7",
|
||||
"recharts": "2.15.4",
|
||||
"sharp": "^0.33.5",
|
||||
"simple-icons": "^13.21.0",
|
||||
"sonner": "^1.7.4",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"zod": "^3.24.2"
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"ram": 2048,
|
||||
"hdd": 4,
|
||||
"os": "Debian",
|
||||
"version": "12"
|
||||
"version": "13"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
43
frontend/public/json/opnsense-vm.json
Normal file
43
frontend/public/json/opnsense-vm.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "OPNsense",
|
||||
"slug": "opnsense-vm",
|
||||
"categories": [
|
||||
4,
|
||||
2
|
||||
],
|
||||
"date_created": "2025-11-23",
|
||||
"type": "vm",
|
||||
"updateable": true,
|
||||
"privileged": false,
|
||||
"interface_port": 443,
|
||||
"documentation": "https://docs.opnsense.org/",
|
||||
"website": "https://opnsense.org/",
|
||||
"logo": "https://cdn.jsdelivr.net/gh/selfhst/icons/webp/opnsense.webp",
|
||||
"config_path": "",
|
||||
"description": "OPNsense is an open-source firewall and routing platform based on FreeBSD. It provides advanced security features, including intrusion detection, VPN support, traffic shaping, and web filtering, with an intuitive web interface for easy management. Known for its reliability and regular updates, OPNsense is a popular choice for both businesses and home networks.",
|
||||
"disable": true,
|
||||
"disable_description": "This script has been temporarily disabled due to installation failures. The OPNsense bootstrap process was not completing successfully, resulting in a plain FreeBSD VM instead of a functional OPNsense installation. The issue is being investigated and the script will be re-enabled once resolved. For more details, see: https://github.com/community-scripts/ProxmoxVE/issues/6183",
|
||||
"install_methods": [
|
||||
{
|
||||
"type": "default",
|
||||
"script": "vm/opnsense-vm.sh",
|
||||
"resources": {
|
||||
"cpu": 4,
|
||||
"ram": 8192,
|
||||
"hdd": 10,
|
||||
"os": "FreeBSD",
|
||||
"version": "latest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"default_credentials": {
|
||||
"username": "root",
|
||||
"password": "opnsense"
|
||||
},
|
||||
"notes": [
|
||||
{
|
||||
"text": "It will fail with default settings if there is no vmbr0 and vmbr1 on your node. Use advanced settings in this case.",
|
||||
"type": "warning"
|
||||
}
|
||||
]
|
||||
}
|
||||
44
frontend/public/json/upgopher.json
Normal file
44
frontend/public/json/upgopher.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "Upgopher",
|
||||
"slug": "upgopher",
|
||||
"categories": [
|
||||
11
|
||||
],
|
||||
"date_created": "2025-11-22",
|
||||
"type": "ct",
|
||||
"updateable": true,
|
||||
"privileged": false,
|
||||
"interface_port": 9090,
|
||||
"documentation": "https://github.com/wanetty/upgopher#readme",
|
||||
"config_path": "",
|
||||
"website": "https://github.com/wanetty/upgopher",
|
||||
"logo": "https://raw.githubusercontent.com/wanetty/upgopher/main/static/logopher.webp",
|
||||
"description": "A simple Go web server for file upload, download, and browsing. Cross-platform alternative to Python-based file servers with no library dependencies. Features file upload via web interface, directory navigation, URL copying to clipboard, optional basic authentication, HTTPS support, and hidden files toggle.",
|
||||
"install_methods": [
|
||||
{
|
||||
"type": "default",
|
||||
"script": "ct/upgopher.sh",
|
||||
"resources": {
|
||||
"cpu": 1,
|
||||
"ram": 512,
|
||||
"hdd": 4,
|
||||
"os": "Debian",
|
||||
"version": "13"
|
||||
}
|
||||
}
|
||||
],
|
||||
"default_credentials": {
|
||||
"username": null,
|
||||
"password": null
|
||||
},
|
||||
"notes": [
|
||||
{
|
||||
"text": "Default uploads directory: `/opt/upgopher/uploads`",
|
||||
"type": "info"
|
||||
},
|
||||
{
|
||||
"text": "Set application startup options by editing `/etc/systemd/system/upgopher.service`. Read documentation for available options.",
|
||||
"type": "info"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,8 +1,203 @@
|
||||
[
|
||||
{
|
||||
"name": "rcourtman/Pulse",
|
||||
"version": "v4.32.3",
|
||||
"date": "2025-11-21T11:16:25Z"
|
||||
"version": "v4.32.5",
|
||||
"date": "2025-11-23T22:53:33Z"
|
||||
},
|
||||
{
|
||||
"name": "raydak-labs/configarr",
|
||||
"version": "v1.18.0",
|
||||
"date": "2025-11-23T17:42:53Z"
|
||||
},
|
||||
{
|
||||
"name": "wavelog/wavelog",
|
||||
"version": "2.2",
|
||||
"date": "2025-11-23T17:32:38Z"
|
||||
},
|
||||
{
|
||||
"name": "verdaccio/verdaccio",
|
||||
"version": "v6.2.2",
|
||||
"date": "2025-11-23T16:54:07Z"
|
||||
},
|
||||
{
|
||||
"name": "crafty-controller/crafty-4",
|
||||
"version": "v4.6.1",
|
||||
"date": "2025-11-23T16:42:50Z"
|
||||
},
|
||||
{
|
||||
"name": "openhab/openhab-core",
|
||||
"version": "5.0.3",
|
||||
"date": "2025-11-23T14:22:13Z"
|
||||
},
|
||||
{
|
||||
"name": "steveiliop56/tinyauth",
|
||||
"version": "v4.1.0",
|
||||
"date": "2025-11-23T12:13:34Z"
|
||||
},
|
||||
{
|
||||
"name": "readeck/readeck",
|
||||
"version": "0.21.2",
|
||||
"date": "2025-11-23T10:56:43Z"
|
||||
},
|
||||
{
|
||||
"name": "morpheus65535/bazarr",
|
||||
"version": "v1.5.3",
|
||||
"date": "2025-09-20T12:12:33Z"
|
||||
},
|
||||
{
|
||||
"name": "Jackett/Jackett",
|
||||
"version": "v0.24.340",
|
||||
"date": "2025-11-23T06:00:18Z"
|
||||
},
|
||||
{
|
||||
"name": "tobychui/zoraxy",
|
||||
"version": "v3.3.0-rc1",
|
||||
"date": "2025-11-23T05:42:35Z"
|
||||
},
|
||||
{
|
||||
"name": "hyperion-project/hyperion.ng",
|
||||
"version": "2.1.1",
|
||||
"date": "2025-06-14T17:45:06Z"
|
||||
},
|
||||
{
|
||||
"name": "BerriAI/litellm",
|
||||
"version": "v1.80.5.rc.2",
|
||||
"date": "2025-11-23T00:31:24Z"
|
||||
},
|
||||
{
|
||||
"name": "jeedom/core",
|
||||
"version": "4.4.20",
|
||||
"date": "2025-11-23T00:27:04Z"
|
||||
},
|
||||
{
|
||||
"name": "paperless-ngx/paperless-ngx",
|
||||
"version": "v2.20.0",
|
||||
"date": "2025-11-22T21:56:00Z"
|
||||
},
|
||||
{
|
||||
"name": "OliveTin/OliveTin",
|
||||
"version": "2025.11.25",
|
||||
"date": "2025-11-22T21:00:52Z"
|
||||
},
|
||||
{
|
||||
"name": "Cleanuparr/Cleanuparr",
|
||||
"version": "v2.4.6",
|
||||
"date": "2025-11-22T20:32:09Z"
|
||||
},
|
||||
{
|
||||
"name": "go-gitea/gitea",
|
||||
"version": "v1.25.2",
|
||||
"date": "2025-11-22T19:37:02Z"
|
||||
},
|
||||
{
|
||||
"name": "msgbyte/tianji",
|
||||
"version": "v1.30.13",
|
||||
"date": "2025-11-22T18:22:32Z"
|
||||
},
|
||||
{
|
||||
"name": "nzbgetcom/nzbget",
|
||||
"version": "v25.4",
|
||||
"date": "2025-10-09T10:27:01Z"
|
||||
},
|
||||
{
|
||||
"name": "bunkerity/bunkerweb",
|
||||
"version": "v1.6.6",
|
||||
"date": "2025-11-22T16:10:29Z"
|
||||
},
|
||||
{
|
||||
"name": "TechnitiumSoftware/DnsServer",
|
||||
"version": "v14.2.0",
|
||||
"date": "2025-11-22T12:54:08Z"
|
||||
},
|
||||
{
|
||||
"name": "keycloak/keycloak",
|
||||
"version": "26.4.5",
|
||||
"date": "2025-11-12T15:24:23Z"
|
||||
},
|
||||
{
|
||||
"name": "pocketbase/pocketbase",
|
||||
"version": "v0.34.0",
|
||||
"date": "2025-11-22T09:18:14Z"
|
||||
},
|
||||
{
|
||||
"name": "laurent22/joplin",
|
||||
"version": "server-v3.4.4",
|
||||
"date": "2025-09-25T13:19:26Z"
|
||||
},
|
||||
{
|
||||
"name": "zwave-js/zwave-js-ui",
|
||||
"version": "v11.8.0",
|
||||
"date": "2025-11-22T07:54:42Z"
|
||||
},
|
||||
{
|
||||
"name": "inventree/InvenTree",
|
||||
"version": "1.1.5",
|
||||
"date": "2025-11-22T07:02:52Z"
|
||||
},
|
||||
{
|
||||
"name": "theonedev/onedev",
|
||||
"version": "v13.1.0",
|
||||
"date": "2025-11-22T04:29:25Z"
|
||||
},
|
||||
{
|
||||
"name": "TwiN/gatus",
|
||||
"version": "v5.33.0",
|
||||
"date": "2025-11-21T22:54:49Z"
|
||||
},
|
||||
{
|
||||
"name": "homarr-labs/homarr",
|
||||
"version": "v1.44.0",
|
||||
"date": "2025-11-21T19:17:09Z"
|
||||
},
|
||||
{
|
||||
"name": "gelbphoenix/autocaliweb",
|
||||
"version": "v0.11.0",
|
||||
"date": "2025-11-21T18:42:15Z"
|
||||
},
|
||||
{
|
||||
"name": "rclone/rclone",
|
||||
"version": "v1.72.0",
|
||||
"date": "2025-11-21T18:20:58Z"
|
||||
},
|
||||
{
|
||||
"name": "mattermost/mattermost",
|
||||
"version": "v10.11.8",
|
||||
"date": "2025-11-21T17:06:07Z"
|
||||
},
|
||||
{
|
||||
"name": "home-assistant/core",
|
||||
"version": "2025.11.3",
|
||||
"date": "2025-11-21T17:03:22Z"
|
||||
},
|
||||
{
|
||||
"name": "Bubka/2FAuth",
|
||||
"version": "v5.6.1",
|
||||
"date": "2025-11-21T16:51:21Z"
|
||||
},
|
||||
{
|
||||
"name": "BookStackApp/BookStack",
|
||||
"version": "v25.11.3",
|
||||
"date": "2025-11-21T14:06:50Z"
|
||||
},
|
||||
{
|
||||
"name": "pommee/goaway",
|
||||
"version": "v0.62.19",
|
||||
"date": "2025-11-21T13:24:09Z"
|
||||
},
|
||||
{
|
||||
"name": "fuma-nama/fumadocs",
|
||||
"version": "fumadocs-ui@16.1.0",
|
||||
"date": "2025-11-21T13:02:57Z"
|
||||
},
|
||||
{
|
||||
"name": "forgejo/forgejo",
|
||||
"version": "v13.0.3",
|
||||
"date": "2025-11-21T12:43:04Z"
|
||||
},
|
||||
{
|
||||
"name": "chrisbenincasa/tunarr",
|
||||
"version": "v0.23.0-alpha.24",
|
||||
"date": "2025-11-21T12:16:39Z"
|
||||
},
|
||||
{
|
||||
"name": "seriousm4x/UpSnap",
|
||||
@@ -29,65 +224,40 @@
|
||||
"version": "v1.11.0",
|
||||
"date": "2025-11-21T08:09:03Z"
|
||||
},
|
||||
{
|
||||
"name": "mattermost/mattermost",
|
||||
"version": "v10.11.7",
|
||||
"date": "2025-11-17T08:40:53Z"
|
||||
},
|
||||
{
|
||||
"name": "MariaDB/server",
|
||||
"version": "mariadb-12.1.2",
|
||||
"date": "2025-11-18T15:16:21Z"
|
||||
},
|
||||
{
|
||||
"name": "morpheus65535/bazarr",
|
||||
"version": "v1.5.3",
|
||||
"date": "2025-09-20T12:12:33Z"
|
||||
},
|
||||
{
|
||||
"name": "Jackett/Jackett",
|
||||
"version": "v0.24.338",
|
||||
"date": "2025-11-21T05:55:33Z"
|
||||
},
|
||||
{
|
||||
"name": "bluenviron/mediamtx",
|
||||
"version": "v1.15.4",
|
||||
"date": "2025-11-21T01:21:03Z"
|
||||
},
|
||||
{
|
||||
"name": "jeedom/core",
|
||||
"version": "4.4.20",
|
||||
"date": "2025-11-21T00:27:06Z"
|
||||
},
|
||||
{
|
||||
"name": "TasmoAdmin/TasmoAdmin",
|
||||
"version": "v4.3.2",
|
||||
"date": "2025-10-18T12:11:00Z"
|
||||
},
|
||||
{
|
||||
"name": "ollama/ollama",
|
||||
"version": "v0.13.1-rc0",
|
||||
"date": "2025-11-20T21:30:35Z"
|
||||
},
|
||||
{
|
||||
"name": "project-zot/zot",
|
||||
"version": "v2.1.11",
|
||||
"date": "2025-11-20T20:14:44Z"
|
||||
},
|
||||
{
|
||||
"name": "readeck/readeck",
|
||||
"version": "0.21.0",
|
||||
"date": "2025-11-20T18:35:59Z"
|
||||
},
|
||||
{
|
||||
"name": "saltstack/salt",
|
||||
"version": "v3007.9",
|
||||
"date": "2025-11-20T17:58:32Z"
|
||||
},
|
||||
{
|
||||
"name": "keycloak/keycloak",
|
||||
"version": "26.4.5",
|
||||
"date": "2025-11-12T15:24:23Z"
|
||||
},
|
||||
{
|
||||
"name": "msgbyte/tianji",
|
||||
"version": "v1.30.12",
|
||||
"date": "2025-11-20T16:13:19Z"
|
||||
"name": "neo4j/neo4j",
|
||||
"version": "5.26.17",
|
||||
"date": "2025-11-20T16:09:28Z"
|
||||
},
|
||||
{
|
||||
"name": "meilisearch/meilisearch",
|
||||
@@ -124,11 +294,6 @@
|
||||
"version": "v2.1.1",
|
||||
"date": "2025-11-20T10:54:07Z"
|
||||
},
|
||||
{
|
||||
"name": "fuma-nama/fumadocs",
|
||||
"version": "fumadocs-ui@16.0.15",
|
||||
"date": "2025-11-20T10:52:04Z"
|
||||
},
|
||||
{
|
||||
"name": "Athou/commafeed",
|
||||
"version": "5.12.0",
|
||||
@@ -149,16 +314,6 @@
|
||||
"version": "v1.56.0",
|
||||
"date": "2025-11-20T02:28:35Z"
|
||||
},
|
||||
{
|
||||
"name": "BerriAI/litellm",
|
||||
"version": "v1.80.0.dev6",
|
||||
"date": "2025-11-20T02:03:21Z"
|
||||
},
|
||||
{
|
||||
"name": "steveiliop56/tinyauth",
|
||||
"version": "v4.0.1",
|
||||
"date": "2025-10-15T16:53:55Z"
|
||||
},
|
||||
{
|
||||
"name": "coder/code-server",
|
||||
"version": "v4.106.2",
|
||||
@@ -199,11 +354,6 @@
|
||||
"version": "0.51.3",
|
||||
"date": "2025-11-19T15:45:28Z"
|
||||
},
|
||||
{
|
||||
"name": "BookStackApp/BookStack",
|
||||
"version": "v25.11.2",
|
||||
"date": "2025-11-19T15:26:29Z"
|
||||
},
|
||||
{
|
||||
"name": "grafana/grafana",
|
||||
"version": "v12.3.0",
|
||||
@@ -224,11 +374,6 @@
|
||||
"version": "2025.5",
|
||||
"date": "2025-11-19T14:48:47Z"
|
||||
},
|
||||
{
|
||||
"name": "ollama/ollama",
|
||||
"version": "v0.13.0",
|
||||
"date": "2025-11-19T14:16:07Z"
|
||||
},
|
||||
{
|
||||
"name": "AdguardTeam/AdGuardHome",
|
||||
"version": "v0.107.69",
|
||||
@@ -249,26 +394,11 @@
|
||||
"version": "v1.143.0rc1",
|
||||
"date": "2025-11-19T00:09:31Z"
|
||||
},
|
||||
{
|
||||
"name": "hyperion-project/hyperion.ng",
|
||||
"version": "2.1.1",
|
||||
"date": "2025-06-14T17:45:06Z"
|
||||
},
|
||||
{
|
||||
"name": "gtsteffaniak/filebrowser",
|
||||
"version": "v1.0.1-stable",
|
||||
"date": "2025-11-10T16:51:44Z"
|
||||
},
|
||||
{
|
||||
"name": "bunkerity/bunkerweb",
|
||||
"version": "testing",
|
||||
"date": "2025-11-17T16:24:26Z"
|
||||
},
|
||||
{
|
||||
"name": "chrisbenincasa/tunarr",
|
||||
"version": "v0.23.0-alpha.23",
|
||||
"date": "2025-11-18T21:36:15Z"
|
||||
},
|
||||
{
|
||||
"name": "umami-software/umami",
|
||||
"version": "v3.0.1",
|
||||
@@ -309,11 +439,6 @@
|
||||
"version": "v0.28.2",
|
||||
"date": "2025-11-18T05:51:46Z"
|
||||
},
|
||||
{
|
||||
"name": "theonedev/onedev",
|
||||
"version": "v13.1.0",
|
||||
"date": "2025-11-18T00:06:49Z"
|
||||
},
|
||||
{
|
||||
"name": "ipfs/kubo",
|
||||
"version": "v0.38.2",
|
||||
@@ -369,11 +494,6 @@
|
||||
"version": "v0.21.0",
|
||||
"date": "2025-08-23T18:33:53Z"
|
||||
},
|
||||
{
|
||||
"name": "TwiN/gatus",
|
||||
"version": "v5.32.0",
|
||||
"date": "2025-11-16T21:08:56Z"
|
||||
},
|
||||
{
|
||||
"name": "binwiederhier/ntfy",
|
||||
"version": "v2.15.0",
|
||||
@@ -404,26 +524,11 @@
|
||||
"version": "v25.11.1",
|
||||
"date": "2025-11-16T13:04:21Z"
|
||||
},
|
||||
{
|
||||
"name": "TechnitiumSoftware/DnsServer",
|
||||
"version": "v14.1.0",
|
||||
"date": "2025-11-16T11:32:10Z"
|
||||
},
|
||||
{
|
||||
"name": "FlowiseAI/Flowise",
|
||||
"version": "flowise@3.0.11",
|
||||
"date": "2025-11-16T01:29:06Z"
|
||||
},
|
||||
{
|
||||
"name": "OliveTin/OliveTin",
|
||||
"version": "3000.4.0",
|
||||
"date": "2025-11-16T01:02:49Z"
|
||||
},
|
||||
{
|
||||
"name": "paperless-ngx/paperless-ngx",
|
||||
"version": "v2.19.6",
|
||||
"date": "2025-11-15T22:56:45Z"
|
||||
},
|
||||
{
|
||||
"name": "karakeep-app/karakeep",
|
||||
"version": "android/v1.8.2-2",
|
||||
@@ -439,16 +544,6 @@
|
||||
"version": "v0.16.1",
|
||||
"date": "2025-11-14T22:50:06Z"
|
||||
},
|
||||
{
|
||||
"name": "home-assistant/core",
|
||||
"version": "2025.11.2",
|
||||
"date": "2025-11-14T22:10:50Z"
|
||||
},
|
||||
{
|
||||
"name": "homarr-labs/homarr",
|
||||
"version": "v1.43.3",
|
||||
"date": "2025-11-14T19:16:17Z"
|
||||
},
|
||||
{
|
||||
"name": "mealie-recipes/mealie",
|
||||
"version": "v3.5.0",
|
||||
@@ -464,16 +559,6 @@
|
||||
"version": "v4.7.0",
|
||||
"date": "2025-11-14T09:45:13Z"
|
||||
},
|
||||
{
|
||||
"name": "nzbgetcom/nzbget",
|
||||
"version": "v25.4",
|
||||
"date": "2025-10-09T10:27:01Z"
|
||||
},
|
||||
{
|
||||
"name": "verdaccio/verdaccio",
|
||||
"version": "generator-verdaccio-plugin@6.0.0-next-8.25",
|
||||
"date": "2025-11-14T08:14:55Z"
|
||||
},
|
||||
{
|
||||
"name": "runtipi/runtipi",
|
||||
"version": "v4.6.5",
|
||||
@@ -489,16 +574,6 @@
|
||||
"version": "v1.7.7",
|
||||
"date": "2025-11-13T21:28:44Z"
|
||||
},
|
||||
{
|
||||
"name": "pommee/goaway",
|
||||
"version": "v0.62.18",
|
||||
"date": "2025-11-13T19:49:21Z"
|
||||
},
|
||||
{
|
||||
"name": "pocketbase/pocketbase",
|
||||
"version": "v0.33.0",
|
||||
"date": "2025-11-13T14:09:14Z"
|
||||
},
|
||||
{
|
||||
"name": "PrivateBin/PrivateBin",
|
||||
"version": "1.7.9",
|
||||
@@ -519,11 +594,6 @@
|
||||
"version": "v1.0.25",
|
||||
"date": "2025-11-12T16:57:54Z"
|
||||
},
|
||||
{
|
||||
"name": "zwave-js/zwave-js-ui",
|
||||
"version": "v11.7.0",
|
||||
"date": "2025-11-12T14:09:36Z"
|
||||
},
|
||||
{
|
||||
"name": "cockpit-project/cockpit",
|
||||
"version": "351",
|
||||
@@ -609,11 +679,6 @@
|
||||
"version": "v1.0.0-beta19",
|
||||
"date": "2025-11-09T17:26:30Z"
|
||||
},
|
||||
{
|
||||
"name": "Cleanuparr/Cleanuparr",
|
||||
"version": "v2.4.5",
|
||||
"date": "2025-11-09T17:14:01Z"
|
||||
},
|
||||
{
|
||||
"name": "authelia/authelia",
|
||||
"version": "v4.39.14",
|
||||
@@ -624,16 +689,6 @@
|
||||
"version": "3.5.1",
|
||||
"date": "2025-11-09T05:09:28Z"
|
||||
},
|
||||
{
|
||||
"name": "inventree/InvenTree",
|
||||
"version": "1.1.3",
|
||||
"date": "2025-11-09T00:28:21Z"
|
||||
},
|
||||
{
|
||||
"name": "raydak-labs/configarr",
|
||||
"version": "v1.17.2",
|
||||
"date": "2025-11-08T22:47:58Z"
|
||||
},
|
||||
{
|
||||
"name": "hargata/lubelog",
|
||||
"version": "v1.5.4",
|
||||
@@ -704,11 +759,6 @@
|
||||
"version": "v0.9.1",
|
||||
"date": "2025-11-06T02:26:53Z"
|
||||
},
|
||||
{
|
||||
"name": "neo4j/neo4j",
|
||||
"version": "5.26.16",
|
||||
"date": "2025-11-05T20:41:40Z"
|
||||
},
|
||||
{
|
||||
"name": "leiweibau/Pi.Alert",
|
||||
"version": "v2025-11-05",
|
||||
@@ -754,11 +804,6 @@
|
||||
"version": "v4.0.16.2944",
|
||||
"date": "2025-11-05T01:56:48Z"
|
||||
},
|
||||
{
|
||||
"name": "go-gitea/gitea",
|
||||
"version": "v1.25.1",
|
||||
"date": "2025-11-04T20:01:09Z"
|
||||
},
|
||||
{
|
||||
"name": "jhuckaby/Cronicle",
|
||||
"version": "v0.9.100",
|
||||
@@ -809,11 +854,6 @@
|
||||
"version": "v1.9.1",
|
||||
"date": "2025-11-02T21:14:50Z"
|
||||
},
|
||||
{
|
||||
"name": "tobychui/zoraxy",
|
||||
"version": "v3.2.9",
|
||||
"date": "2025-11-02T05:48:39Z"
|
||||
},
|
||||
{
|
||||
"name": "9001/copyparty",
|
||||
"version": "v1.19.20",
|
||||
@@ -864,11 +904,6 @@
|
||||
"version": "v1.11.1",
|
||||
"date": "2025-10-29T22:09:26Z"
|
||||
},
|
||||
{
|
||||
"name": "laurent22/joplin",
|
||||
"version": "server-v3.4.4",
|
||||
"date": "2025-09-25T13:19:26Z"
|
||||
},
|
||||
{
|
||||
"name": "apache/cassandra",
|
||||
"version": "cassandra-5.0.6",
|
||||
@@ -889,21 +924,11 @@
|
||||
"version": "v3.0.0-beta.2",
|
||||
"date": "2025-10-28T10:16:29Z"
|
||||
},
|
||||
{
|
||||
"name": "openhab/openhab-core",
|
||||
"version": "5.1.0.M2",
|
||||
"date": "2025-10-26T14:38:02Z"
|
||||
},
|
||||
{
|
||||
"name": "ErsatzTV/ErsatzTV",
|
||||
"version": "v25.8.0",
|
||||
"date": "2025-10-26T14:23:37Z"
|
||||
},
|
||||
{
|
||||
"name": "forgejo/forgejo",
|
||||
"version": "v13.0.2",
|
||||
"date": "2025-10-26T06:33:05Z"
|
||||
},
|
||||
{
|
||||
"name": "usememos/memos",
|
||||
"version": "v0.25.2",
|
||||
@@ -939,11 +964,6 @@
|
||||
"version": "v3.2.5-beta",
|
||||
"date": "2025-10-21T16:49:14Z"
|
||||
},
|
||||
{
|
||||
"name": "rclone/rclone",
|
||||
"version": "v1.71.2",
|
||||
"date": "2025-10-20T15:25:52Z"
|
||||
},
|
||||
{
|
||||
"name": "Part-DB/Part-DB-server",
|
||||
"version": "v2.2.1",
|
||||
@@ -984,16 +1004,6 @@
|
||||
"version": "v0.14.1",
|
||||
"date": "2024-08-29T22:32:51Z"
|
||||
},
|
||||
{
|
||||
"name": "wavelog/wavelog",
|
||||
"version": "2.1.2",
|
||||
"date": "2025-10-15T06:51:32Z"
|
||||
},
|
||||
{
|
||||
"name": "crafty-controller/crafty-4",
|
||||
"version": "v4.5.5",
|
||||
"date": "2025-10-14T18:48:36Z"
|
||||
},
|
||||
{
|
||||
"name": "rogerfar/rdt-client",
|
||||
"version": "v2.0.119",
|
||||
@@ -1009,11 +1019,6 @@
|
||||
"version": "v5.0.85",
|
||||
"date": "2025-10-12T19:55:18Z"
|
||||
},
|
||||
{
|
||||
"name": "gelbphoenix/autocaliweb",
|
||||
"version": "v0.10.4",
|
||||
"date": "2025-10-11T19:53:39Z"
|
||||
},
|
||||
{
|
||||
"name": "projectsend/projectsend",
|
||||
"version": "r1945",
|
||||
@@ -1314,11 +1319,6 @@
|
||||
"version": "v1.11.11",
|
||||
"date": "2025-06-18T18:04:50Z"
|
||||
},
|
||||
{
|
||||
"name": "Bubka/2FAuth",
|
||||
"version": "v5.6.0",
|
||||
"date": "2025-06-18T12:19:54Z"
|
||||
},
|
||||
{
|
||||
"name": "TriliumNext/Notes",
|
||||
"version": "v0.95.0",
|
||||
|
||||
@@ -1,9 +1,67 @@
|
||||
"use client";
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import "react-datepicker/dist/react-datepicker.css";
|
||||
import {
|
||||
ArrowUpDown,
|
||||
Box,
|
||||
CheckCircle2,
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
List,
|
||||
Loader2,
|
||||
Trophy,
|
||||
XCircle,
|
||||
} from "lucide-react";
|
||||
import {
|
||||
Bar,
|
||||
BarChart,
|
||||
CartesianGrid,
|
||||
Cell,
|
||||
LabelList,
|
||||
XAxis,
|
||||
} from "recharts";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
|
||||
import ApplicationChart from "../../components/application-chart";
|
||||
import type { ChartConfig } from "@/components/ui/chart";
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import {
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
} from "@/components/ui/chart";
|
||||
import { formattedBadge } from "@/components/command-menu";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
type DataModel = {
|
||||
id: number;
|
||||
@@ -30,42 +88,76 @@ type SummaryData = {
|
||||
nsapp_count: Record<string, number>;
|
||||
};
|
||||
|
||||
const DataFetcher: React.FC = () => {
|
||||
// Chart colors optimized for both light and dark modes
|
||||
// Medium-toned colors that are visible and not too flashy in both themes
|
||||
const CHART_COLORS = [
|
||||
"#5B8DEF", // blue - medium tone
|
||||
"#4ECDC4", // teal - medium tone
|
||||
"#FF8C42", // orange - medium tone
|
||||
"#A78BFA", // purple - medium tone
|
||||
"#F472B6", // pink - medium tone
|
||||
"#38BDF8", // cyan - medium tone
|
||||
"#4ADE80", // green - medium tone
|
||||
"#FBBF24", // yellow - medium tone
|
||||
"#818CF8", // indigo - medium tone
|
||||
"#FB7185", // rose - medium tone
|
||||
"#2DD4BF", // turquoise - medium tone
|
||||
"#C084FC", // violet - medium tone
|
||||
"#60A5FA", // sky blue - medium tone
|
||||
"#84CC16", // lime - medium tone
|
||||
"#F59E0B", // amber - medium tone
|
||||
"#A855F7", // purple - medium tone
|
||||
"#10B981", // emerald - medium tone
|
||||
"#EAB308", // gold - medium tone
|
||||
"#3B82F6", // royal blue - medium tone
|
||||
"#EF4444", // red - medium tone
|
||||
];
|
||||
|
||||
const chartConfigApps = {
|
||||
count: {
|
||||
label: "Installations",
|
||||
color: "hsl(var(--chart-1))",
|
||||
},
|
||||
} satisfies ChartConfig;
|
||||
|
||||
export default function DataPage() {
|
||||
const [data, setData] = useState<DataModel[]>([]);
|
||||
const [summary, setSummary] = useState<SummaryData | null>(null);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [itemsPerPage, setItemsPerPage] = useState(25);
|
||||
const [sortConfig, setSortConfig] = useState<{ key: string; direction: "ascending" | "descending" } | null>(null);
|
||||
const [sortConfig, setSortConfig] = useState<{
|
||||
key: string;
|
||||
direction: "ascending" | "descending";
|
||||
} | null>(null);
|
||||
|
||||
const nf = new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 });
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSummary = async () => {
|
||||
try {
|
||||
const response = await fetch("https://api.htl-braunau.at/data/summary");
|
||||
if (!response.ok)
|
||||
throw new Error(`Failed to fetch summary: ${response.statusText}`);
|
||||
const result: SummaryData = await response.json();
|
||||
setSummary(result);
|
||||
}
|
||||
catch (err) {
|
||||
setError((err as Error).message);
|
||||
}
|
||||
};
|
||||
|
||||
fetchSummary();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchPaginatedData = async () => {
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await fetch(`https://api.htl-braunau.at/data/paginated?page=${currentPage}&limit=${itemsPerPage === 0 ? "" : itemsPerPage}`);
|
||||
if (!response.ok)
|
||||
throw new Error(`Failed to fetch data: ${response.statusText}`);
|
||||
const result: DataModel[] = await response.json();
|
||||
setData(result);
|
||||
const [summaryRes, dataRes] = await Promise.all([
|
||||
fetch("https://api.htl-braunau.at/data/summary"),
|
||||
fetch(
|
||||
`https://api.htl-braunau.at/data/paginated?page=${currentPage}&limit=${itemsPerPage === 0 ? "" : itemsPerPage
|
||||
}`,
|
||||
),
|
||||
]);
|
||||
|
||||
if (!summaryRes.ok) {
|
||||
throw new Error(`Failed to fetch summary: ${summaryRes.statusText}`);
|
||||
}
|
||||
if (!dataRes.ok) {
|
||||
throw new Error(`Failed to fetch data: ${dataRes.statusText}`);
|
||||
}
|
||||
|
||||
const summaryData: SummaryData = await summaryRes.json();
|
||||
const pageData: DataModel[] = await dataRes.json();
|
||||
|
||||
setSummary(summaryData);
|
||||
setData(pageData);
|
||||
}
|
||||
catch (err) {
|
||||
setError((err as Error).message);
|
||||
@@ -75,13 +167,13 @@ const DataFetcher: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
fetchPaginatedData();
|
||||
fetchData();
|
||||
}, [currentPage, itemsPerPage]);
|
||||
|
||||
const sortedData = React.useMemo(() => {
|
||||
const sortedData = useMemo(() => {
|
||||
if (!sortConfig)
|
||||
return data;
|
||||
const sorted = [...data].sort((a, b) => {
|
||||
return [...data].sort((a, b) => {
|
||||
if (a[sortConfig.key] < b[sortConfig.key]) {
|
||||
return sortConfig.direction === "ascending" ? -1 : 1;
|
||||
}
|
||||
@@ -90,23 +182,15 @@ const DataFetcher: React.FC = () => {
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
return sorted;
|
||||
}, [data, sortConfig]);
|
||||
|
||||
if (loading)
|
||||
return <p>Loading...</p>;
|
||||
if (error) {
|
||||
return (
|
||||
<p>
|
||||
Error:
|
||||
{error}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
const requestSort = (key: string) => {
|
||||
let direction: "ascending" | "descending" = "ascending";
|
||||
if (sortConfig && sortConfig.key === key && sortConfig.direction === "ascending") {
|
||||
if (
|
||||
sortConfig
|
||||
&& sortConfig.key === key
|
||||
&& sortConfig.direction === "ascending"
|
||||
) {
|
||||
direction = "descending";
|
||||
}
|
||||
setSortConfig({ key, direction });
|
||||
@@ -114,135 +198,447 @@ const DataFetcher: React.FC = () => {
|
||||
|
||||
const formatDate = (dateString: string): string => {
|
||||
const date = new Date(dateString);
|
||||
const year = date.getFullYear();
|
||||
const month = date.getMonth() + 1;
|
||||
const day = date.getDate();
|
||||
const hours = String(date.getHours()).padStart(2, "0");
|
||||
const minutes = String(date.getMinutes()).padStart(2, "0");
|
||||
const timezoneOffset = dateString.slice(-6);
|
||||
return `${day}.${month}.${year} ${hours}:${minutes} ${timezoneOffset} GMT`;
|
||||
return new Intl.DateTimeFormat("en-US", {
|
||||
dateStyle: "medium",
|
||||
timeStyle: "short",
|
||||
}).format(date);
|
||||
};
|
||||
|
||||
const getTypeBadge = (type: string) => {
|
||||
if (type === "lxc")
|
||||
return formattedBadge("ct");
|
||||
if (type === "vm")
|
||||
return formattedBadge("vm");
|
||||
return null;
|
||||
};
|
||||
|
||||
// Stats calculations
|
||||
const successCount = summary?.status_count.done ?? 0;
|
||||
const failureCount = summary?.status_count.failed ?? 0;
|
||||
const totalCount = summary?.total_entries ?? 0;
|
||||
const successRate = totalCount > 0 ? (successCount / totalCount) * 100 : 0;
|
||||
|
||||
const allApps = useMemo(() => {
|
||||
if (!summary?.nsapp_count)
|
||||
return [];
|
||||
return Object.entries(summary.nsapp_count).sort(([, a], [, b]) => b - a);
|
||||
}, [summary]);
|
||||
|
||||
const topApps = useMemo(() => {
|
||||
return allApps.slice(0, 15);
|
||||
}, [allApps]);
|
||||
|
||||
const mostPopularApp = topApps[0];
|
||||
|
||||
// Chart Data
|
||||
const appsChartData = topApps.map(([name, count], index) => ({
|
||||
app: name,
|
||||
count,
|
||||
fill: CHART_COLORS[index % CHART_COLORS.length],
|
||||
}));
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="p-6 mt-20">
|
||||
<h1 className="text-2xl font-bold mb-4 text-center">Created LXCs</h1>
|
||||
<ApplicationChart data={summary} />
|
||||
<p className="text-lg font-bold mt-4"> </p>
|
||||
<div className="mb-4 flex justify-between items-center">
|
||||
<p className="text-lg font-bold">
|
||||
{nf.format(
|
||||
summary?.total_entries ?? 0,
|
||||
)}
|
||||
{" "}
|
||||
results found
|
||||
</p>
|
||||
<p className="text-lg font">
|
||||
Status Legend: 🔄 installing
|
||||
{" "}
|
||||
{nf.format(summary?.status_count.installing ?? 0)}
|
||||
{" "}
|
||||
| ✔️ completed
|
||||
{" "}
|
||||
{nf.format(summary?.status_count.done ?? 0)}
|
||||
{" "}
|
||||
| ❌ failed
|
||||
{" "}
|
||||
{nf.format(summary?.status_count.failed ?? 0)}
|
||||
{" "}
|
||||
| ❓ unknown
|
||||
<div className="p-6 text-center text-red-500">
|
||||
<p>
|
||||
Error loading data:
|
||||
{error}
|
||||
</p>
|
||||
</div>
|
||||
<div className="overflow-x-auto">
|
||||
<div className="overflow-y-auto lg:overflow-y-visible">
|
||||
<table className="min-w-full table-auto border-collapse">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort("status")}>Status</th>
|
||||
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort("type")}>Type</th>
|
||||
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort("nsapp")}>Application</th>
|
||||
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort("os_type")}>OS</th>
|
||||
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort("os_version")}>OS Version</th>
|
||||
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort("disk_size")}>Disk Size</th>
|
||||
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort("core_count")}>Core Count</th>
|
||||
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort("ram_size")}>RAM Size</th>
|
||||
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort("method")}>Method</th>
|
||||
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort("pve_version")}>PVE Version</th>
|
||||
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort("error")}>Error Message</th>
|
||||
<th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort("created_at")}>Created At</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{sortedData.map((item, index) => (
|
||||
<tr key={index}>
|
||||
<td className="px-4 py-2 border-b">
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mb-3">
|
||||
<div className="mt-20 flex sm:px-4 xl:px-0">
|
||||
<div className="mx-4 w-full sm:mx-0 space-y-8">
|
||||
{/* Header */}
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold tracking-tight">Analytics</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Overview of container installations and system statistics.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Widgets */}
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Total Created</CardTitle>
|
||||
<Box className="h-4 w-4 text-muted-foreground" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{nf.format(totalCount)}</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Total LXC/VM entries found
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Success Rate</CardTitle>
|
||||
<CheckCircle2 className="h-4 w-4 text-green-500" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">
|
||||
{successRate.toFixed(1)}
|
||||
%
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{nf.format(successCount)}
|
||||
{" "}
|
||||
successful installations
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Failures</CardTitle>
|
||||
<XCircle className="h-4 w-4 text-red-500" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">{nf.format(failureCount)}</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Installations encountered errors
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">Most Popular</CardTitle>
|
||||
<Trophy className="h-4 w-4 text-yellow-500" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="truncate text-2xl font-bold">
|
||||
{mostPopularApp ? mostPopularApp[0] : "N/A"}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{mostPopularApp ? nf.format(mostPopularApp[1]) : 0}
|
||||
{" "}
|
||||
installations
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Graphs */}
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<div className="space-y-1.5">
|
||||
<CardTitle>Top Applications</CardTitle>
|
||||
<CardDescription>
|
||||
The most frequently installed applications.
|
||||
</CardDescription>
|
||||
</div>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline" size="sm" className="ml-auto">
|
||||
<List className="mr-2 h-4 w-4" />
|
||||
View All
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="max-h-[80vh] sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Application Statistics</DialogTitle>
|
||||
<DialogDescription>
|
||||
Installation counts for all
|
||||
{" "}
|
||||
{allApps.length}
|
||||
{" "}
|
||||
applications.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<ScrollArea className="h-[60vh] w-full rounded-md border p-4">
|
||||
<div className="space-y-4">
|
||||
{allApps.map(([name, count], index) => (
|
||||
<div
|
||||
key={name}
|
||||
className="flex items-center justify-between text-sm"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="w-8 font-mono text-muted-foreground">
|
||||
{index + 1}
|
||||
.
|
||||
</span>
|
||||
<span className="font-medium">{name}</span>
|
||||
</div>
|
||||
<span className="font-mono">{nf.format(count)}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</CardHeader>
|
||||
<CardContent className="pl-2">
|
||||
<div className="h-[300px] w-full">
|
||||
{loading
|
||||
? (
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<ChartContainer config={chartConfigApps} className="h-full w-full">
|
||||
<BarChart
|
||||
accessibilityLayer
|
||||
data={appsChartData}
|
||||
margin={{
|
||||
top: 20,
|
||||
}}
|
||||
>
|
||||
<CartesianGrid vertical={false} />
|
||||
<XAxis
|
||||
dataKey="app"
|
||||
tickLine={false}
|
||||
tickMargin={10}
|
||||
axisLine={false}
|
||||
tickFormatter={value => (value.length > 8 ? `${value.slice(0, 8)}...` : value)}
|
||||
/>
|
||||
<ChartTooltip
|
||||
cursor={false}
|
||||
content={<ChartTooltipContent nameKey="app" />}
|
||||
/>
|
||||
<Bar dataKey="count" radius={8}>
|
||||
{appsChartData.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.fill} />
|
||||
))}
|
||||
<LabelList
|
||||
position="top"
|
||||
offset={12}
|
||||
className="fill-foreground"
|
||||
fontSize={12}
|
||||
/>
|
||||
</Bar>
|
||||
</BarChart>
|
||||
</ChartContainer>
|
||||
)}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Data Table */}
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between">
|
||||
<div>
|
||||
<CardTitle>Installation Log</CardTitle>
|
||||
<CardDescription>
|
||||
Detailed records of all container creation attempts.
|
||||
</CardDescription>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Select
|
||||
value={String(itemsPerPage)}
|
||||
onValueChange={val => setItemsPerPage(Number(val))}
|
||||
>
|
||||
<SelectTrigger className="w-[80px]">
|
||||
<SelectValue placeholder="Limit" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="10">10</SelectItem>
|
||||
<SelectItem value="25">25</SelectItem>
|
||||
<SelectItem value="50">50</SelectItem>
|
||||
<SelectItem value="100">100</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead
|
||||
className="w-[100px] cursor-pointer"
|
||||
onClick={() => requestSort("status")}
|
||||
>
|
||||
Status
|
||||
{sortConfig?.key === "status" && (
|
||||
<ArrowUpDown className="ml-2 inline h-4 w-4" />
|
||||
)}
|
||||
</TableHead>
|
||||
<TableHead
|
||||
className="cursor-pointer"
|
||||
onClick={() => requestSort("type")}
|
||||
>
|
||||
Type
|
||||
{sortConfig?.key === "type" && (
|
||||
<ArrowUpDown className="ml-2 inline h-4 w-4" />
|
||||
)}
|
||||
</TableHead>
|
||||
<TableHead
|
||||
className="cursor-pointer"
|
||||
onClick={() => requestSort("nsapp")}
|
||||
>
|
||||
Application
|
||||
{sortConfig?.key === "nsapp" && (
|
||||
<ArrowUpDown className="ml-2 inline h-4 w-4" />
|
||||
)}
|
||||
</TableHead>
|
||||
<TableHead
|
||||
className="hidden cursor-pointer md:table-cell"
|
||||
onClick={() => requestSort("os_type")}
|
||||
>
|
||||
OS
|
||||
{sortConfig?.key === "os_type" && (
|
||||
<ArrowUpDown className="ml-2 inline h-4 w-4" />
|
||||
)}
|
||||
</TableHead>
|
||||
<TableHead
|
||||
className="hidden cursor-pointer md:table-cell"
|
||||
onClick={() => requestSort("disk_size")}
|
||||
>
|
||||
Disk Size
|
||||
{sortConfig?.key === "disk_size" && (
|
||||
<ArrowUpDown className="ml-2 inline h-4 w-4" />
|
||||
)}
|
||||
</TableHead>
|
||||
<TableHead
|
||||
className="hidden cursor-pointer lg:table-cell"
|
||||
onClick={() => requestSort("core_count")}
|
||||
>
|
||||
Core Count
|
||||
{sortConfig?.key === "core_count" && (
|
||||
<ArrowUpDown className="ml-2 inline h-4 w-4" />
|
||||
)}
|
||||
</TableHead>
|
||||
<TableHead
|
||||
className="hidden cursor-pointer lg:table-cell"
|
||||
onClick={() => requestSort("ram_size")}
|
||||
>
|
||||
RAM Size
|
||||
{sortConfig?.key === "ram_size" && (
|
||||
<ArrowUpDown className="ml-2 inline h-4 w-4" />
|
||||
)}
|
||||
</TableHead>
|
||||
<TableHead
|
||||
className="cursor-pointer text-right"
|
||||
onClick={() => requestSort("created_at")}
|
||||
>
|
||||
Created At
|
||||
{sortConfig?.key === "created_at" && (
|
||||
<ArrowUpDown className="ml-2 inline h-4 w-4" />
|
||||
)}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{loading
|
||||
? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} className="h-24 text-center">
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
{" "}
|
||||
Loading data...
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
: sortedData.length > 0
|
||||
? (
|
||||
sortedData.map((item, idx) => (
|
||||
<TableRow key={`${item.id}-${idx}`}>
|
||||
<TableCell>
|
||||
{item.status === "done"
|
||||
? (
|
||||
"✔️"
|
||||
<Badge className="text-green-500/75 border-green-500/75">
|
||||
Success
|
||||
</Badge>
|
||||
)
|
||||
: item.status === "failed"
|
||||
? (
|
||||
"❌"
|
||||
<Badge className="text-red-500/75 border-red-500/75">
|
||||
Failed
|
||||
</Badge>
|
||||
)
|
||||
: item.status === "installing"
|
||||
? (
|
||||
"🔄"
|
||||
<Badge className="text-blue-500/75 border-blue-500/75">
|
||||
Installing
|
||||
</Badge>
|
||||
)
|
||||
: (
|
||||
item.status
|
||||
<Badge variant="outline">
|
||||
{item.status}
|
||||
</Badge>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-4 py-2 border-b">
|
||||
{item.type === "lxc"
|
||||
? (
|
||||
"📦"
|
||||
)
|
||||
: item.type === "vm"
|
||||
? (
|
||||
"🖥️"
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{getTypeBadge(item.type) || (
|
||||
<Badge variant="outline">
|
||||
{item.type}
|
||||
</Badge>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="font-medium">
|
||||
{item.nsapp}
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
{item.os_type}
|
||||
{" "}
|
||||
{item.os_version}
|
||||
</TableCell>
|
||||
<TableCell className="hidden md:table-cell">
|
||||
{item.disk_size}
|
||||
MB
|
||||
</TableCell>
|
||||
<TableCell className="hidden lg:table-cell">
|
||||
{item.core_count}
|
||||
</TableCell>
|
||||
<TableCell className="hidden lg:table-cell">
|
||||
{item.ram_size}
|
||||
MB
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{formatDate(item.created_at)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
)
|
||||
: (
|
||||
item.type
|
||||
<TableRow>
|
||||
<TableCell colSpan={8} className="h-24 text-center">
|
||||
No results found.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</td>
|
||||
<td className="px-4 py-2 border-b">{item.nsapp}</td>
|
||||
<td className="px-4 py-2 border-b">{item.os_type}</td>
|
||||
<td className="px-4 py-2 border-b">{item.os_version}</td>
|
||||
<td className="px-4 py-2 border-b">{item.disk_size}</td>
|
||||
<td className="px-4 py-2 border-b">{item.core_count}</td>
|
||||
<td className="px-4 py-2 border-b">{item.ram_size}</td>
|
||||
<td className="px-4 py-2 border-b">{item.method}</td>
|
||||
<td className="px-4 py-2 border-b">{item.pve_version}</td>
|
||||
<td className="px-4 py-2 border-b">{item.error}</td>
|
||||
<td className="px-4 py-2 border-b">{formatDate(item.created_at)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4 flex justify-between items-center">
|
||||
<button onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))} disabled={currentPage === 1} className="p-2 border">Previous</button>
|
||||
<span>
|
||||
Page
|
||||
{currentPage}
|
||||
</span>
|
||||
<button onClick={() => setCurrentPage(prev => prev + 1)} className="p-2 border">Next</button>
|
||||
<select
|
||||
value={itemsPerPage}
|
||||
onChange={e => setItemsPerPage(Number(e.target.value))}
|
||||
className="p-2 border"
|
||||
|
||||
<div className="flex items-center justify-end space-x-2 py-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))}
|
||||
disabled={currentPage === 1 || loading}
|
||||
>
|
||||
<option value={10}>10</option>
|
||||
<option value={20}>20</option>
|
||||
<option value={50}>50</option>
|
||||
<option value={100}>100</option>
|
||||
<option value={250}>250</option>
|
||||
<option value={500}>500</option>
|
||||
<option value={5000}>5000</option>
|
||||
</select>
|
||||
<ChevronLeft className="mr-2 h-4 w-4" />
|
||||
Previous
|
||||
</Button>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Page
|
||||
{" "}
|
||||
{currentPage}
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setCurrentPage(prev => prev + 1)}
|
||||
disabled={loading || sortedData.length < itemsPerPage}
|
||||
>
|
||||
Next
|
||||
<ChevronRight className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DataFetcher;
|
||||
}
|
||||
|
||||
@@ -35,12 +35,22 @@ export const ScriptSchema = z.object({
|
||||
logo: z.string().url().nullable(),
|
||||
config_path: z.string(),
|
||||
description: z.string().min(1, "Description is required"),
|
||||
disable: z.boolean().optional(),
|
||||
disable_description: z.string().optional(),
|
||||
install_methods: z.array(InstallMethodSchema).min(1, "At least one install method is required"),
|
||||
default_credentials: z.object({
|
||||
username: z.string().nullable(),
|
||||
password: z.string().nullable(),
|
||||
}),
|
||||
notes: z.array(NoteSchema),
|
||||
}).refine((data) => {
|
||||
if (data.disable === true && !data.disable_description) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}, {
|
||||
message: "disable_description is required when disable is true",
|
||||
path: ["disable_description"],
|
||||
});
|
||||
|
||||
export type Script = z.infer<typeof ScriptSchema>;
|
||||
|
||||
@@ -42,6 +42,8 @@ const initialScript: Script = {
|
||||
website: null,
|
||||
logo: null,
|
||||
description: "",
|
||||
disable: undefined,
|
||||
disable_description: undefined,
|
||||
install_methods: [],
|
||||
default_credentials: {
|
||||
username: null,
|
||||
@@ -261,7 +263,25 @@ export default function JSONGenerator() {
|
||||
<Switch checked={script.privileged} onCheckedChange={checked => updateScript("privileged", checked)} />
|
||||
<label>Privileged</label>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch checked={script.disable || false} onCheckedChange={checked => updateScript("disable", checked)} />
|
||||
<label>Disabled</label>
|
||||
</div>
|
||||
</div>
|
||||
{script.disable && (
|
||||
<div>
|
||||
<Label>
|
||||
Disable Description
|
||||
{" "}
|
||||
<span className="text-red-500">*</span>
|
||||
</Label>
|
||||
<Textarea
|
||||
placeholder="Explain why this script is disabled..."
|
||||
value={script.disable_description || ""}
|
||||
onChange={e => updateScript("disable_description", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Input
|
||||
placeholder="Interface Port"
|
||||
type="number"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { basePath } from "@/config/site-config";
|
||||
|
||||
export default function NotFoundPage() {
|
||||
return (
|
||||
@@ -12,7 +13,17 @@ export default function NotFoundPage() {
|
||||
Oops, the page you are looking for could not be found.
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={() => window.history.back()} variant="secondary">
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (window.history.length > 1) {
|
||||
window.history.back();
|
||||
}
|
||||
else {
|
||||
window.location.href = `/${basePath}`;
|
||||
}
|
||||
}}
|
||||
variant="secondary"
|
||||
>
|
||||
Go Back
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -123,7 +123,7 @@ export default function ScriptAccordion({
|
||||
className={`flex cursor-pointer items-center justify-between gap-1 px-1 py-1 text-muted-foreground hover:rounded-lg hover:bg-accent/60 hover:dark:bg-accent/20 ${selectedScript === script.slug
|
||||
? "rounded-lg bg-accent font-semibold dark:bg-accent/30 dark:text-white"
|
||||
: ""
|
||||
}`}
|
||||
} ${script.disable ? "opacity-60" : ""}`}
|
||||
onClick={() => {
|
||||
handleSelected(script.slug);
|
||||
setSelectedCategory(category.name);
|
||||
@@ -143,7 +143,9 @@ export default function ScriptAccordion({
|
||||
alt={script.name}
|
||||
className="mr-1 w-4 h-4 rounded-full"
|
||||
/>
|
||||
<span className="flex items-center gap-2">{script.name}</span>
|
||||
<span className="flex items-center gap-2">
|
||||
{script.name}
|
||||
</span>
|
||||
</div>
|
||||
{formattedBadge(script.type)}
|
||||
</Link>
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useVersions } from "@/hooks/use-versions";
|
||||
import { basePath } from "@/config/site-config";
|
||||
import { extractDate } from "@/lib/time";
|
||||
|
||||
import DisableDescription from "./script-items/disable-description";
|
||||
import { getDisplayValueFromType } from "./script-info-blocks";
|
||||
import DefaultPassword from "./script-items/default-password";
|
||||
import InstallCommand from "./script-items/install-command";
|
||||
@@ -146,9 +147,15 @@ export function ScriptItem({ item, setSelectedScript }: ScriptItemProps) {
|
||||
<ScriptHeader item={item} />
|
||||
</Suspense>
|
||||
|
||||
<Description item={item} />
|
||||
<Alerts item={item} />
|
||||
{item.disable && item.disable_description && (
|
||||
<DisableDescription item={item} />
|
||||
) }
|
||||
|
||||
{!item.disable && (
|
||||
<>
|
||||
<Description item={item} />
|
||||
|
||||
<Alerts item={item} />
|
||||
<div className="mt-4 rounded-lg border shadow-sm">
|
||||
<div className="flex gap-3 px-4 py-2 bg-accent/25">
|
||||
<h2 className="text-lg font-semibold">
|
||||
@@ -177,6 +184,8 @@ export function ScriptItem({ item, setSelectedScript }: ScriptItemProps) {
|
||||
</div>
|
||||
|
||||
<DefaultPassword item={item} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { AlertCircle } from "lucide-react";
|
||||
|
||||
import type { Script } from "@/lib/types";
|
||||
|
||||
import TextParseLinks from "@/components/text-parse-links";
|
||||
import { AlertColors } from "@/config/site-config";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export default function DisableDescription({ item }: { item: Script }) {
|
||||
return (
|
||||
<div className="mt-4 flex flex-col shadow-sm gap-2">
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-start gap-3 rounded-lg border p-4 text-sm",
|
||||
AlertColors.warning,
|
||||
)}
|
||||
>
|
||||
<AlertCircle className="h-5 min-h-5 w-5 min-w-5 mt-0.5" />
|
||||
<div className="flex flex-col gap-2">
|
||||
<h3 className="font-semibold text-base">Script Disabled</h3>
|
||||
<p>{TextParseLinks(item.disable_description!)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { ArcElement, Chart as ChartJS, Tooltip as ChartTooltip, Legend } from "chart.js";
|
||||
import ChartDataLabels from "chartjs-plugin-datalabels";
|
||||
import { BarChart3, PieChart } from "lucide-react";
|
||||
import React, { useState } from "react";
|
||||
import { Pie } from "react-chartjs-2";
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/tooltip";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
ChartJS.register(ArcElement, ChartTooltip, Legend, ChartDataLabels);
|
||||
|
||||
type SummaryData = {
|
||||
nsapp_count: Record<string, number>;
|
||||
};
|
||||
|
||||
type ApplicationChartProps = {
|
||||
data: SummaryData | null;
|
||||
};
|
||||
|
||||
const ITEMS_PER_PAGE = 20;
|
||||
const CHART_COLORS = [
|
||||
"#ff6384",
|
||||
"#36a2eb",
|
||||
"#ffce56",
|
||||
"#4bc0c0",
|
||||
"#9966ff",
|
||||
"#ff9f40",
|
||||
"#4dc9f6",
|
||||
"#f67019",
|
||||
"#537bc4",
|
||||
"#acc236",
|
||||
"#166a8f",
|
||||
"#00a950",
|
||||
"#58595b",
|
||||
"#8549ba",
|
||||
];
|
||||
|
||||
export default function ApplicationChart({ data }: ApplicationChartProps) {
|
||||
const [isChartOpen, setIsChartOpen] = useState(false);
|
||||
const [isTableOpen, setIsTableOpen] = useState(false);
|
||||
const [chartStartIndex, setChartStartIndex] = useState(0);
|
||||
const [tableLimit, setTableLimit] = useState(ITEMS_PER_PAGE);
|
||||
|
||||
if (!data)
|
||||
return null;
|
||||
|
||||
const sortedApps = Object.entries(data.nsapp_count)
|
||||
.sort(([, a], [, b]) => b - a);
|
||||
|
||||
const chartApps = sortedApps.slice(
|
||||
chartStartIndex,
|
||||
chartStartIndex + ITEMS_PER_PAGE,
|
||||
);
|
||||
|
||||
const chartData = {
|
||||
labels: chartApps.map(([name]) => name),
|
||||
datasets: [
|
||||
{
|
||||
data: chartApps.map(([, count]) => count),
|
||||
backgroundColor: CHART_COLORS,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const chartOptions = {
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
datalabels: {
|
||||
color: "white",
|
||||
font: { weight: "bold" as const },
|
||||
formatter: (value: number, context: any) => {
|
||||
const label = context.chart.data.labels?.[context.dataIndex];
|
||||
return `${label}\n(${value})`;
|
||||
},
|
||||
},
|
||||
},
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-6 flex justify-center gap-4">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => setIsChartOpen(true)}
|
||||
>
|
||||
<PieChart className="h-5 w-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Open Chart View</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
onClick={() => setIsTableOpen(true)}
|
||||
>
|
||||
<BarChart3 className="h-5 w-5" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Open Table View</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
|
||||
<Dialog open={isChartOpen} onOpenChange={setIsChartOpen}>
|
||||
<DialogContent className="max-w-3xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Applications Distribution</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="h-[60vh] w-full">
|
||||
<Pie data={chartData} options={chartOptions} />
|
||||
</div>
|
||||
<div className="flex justify-center gap-4">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setChartStartIndex(Math.max(0, chartStartIndex - ITEMS_PER_PAGE))}
|
||||
disabled={chartStartIndex === 0}
|
||||
>
|
||||
Previous
|
||||
{" "}
|
||||
{ITEMS_PER_PAGE}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setChartStartIndex(chartStartIndex + ITEMS_PER_PAGE)}
|
||||
disabled={chartStartIndex + ITEMS_PER_PAGE >= sortedApps.length}
|
||||
>
|
||||
Next
|
||||
{" "}
|
||||
{ITEMS_PER_PAGE}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog open={isTableOpen} onOpenChange={setIsTableOpen}>
|
||||
<DialogContent className="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Applications Count</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="max-h-[60vh] overflow-y-auto">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Application</TableHead>
|
||||
<TableHead className="text-right">Count</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{sortedApps.slice(0, tableLimit).map(([name, count]) => (
|
||||
<TableRow key={name}>
|
||||
<TableCell>{name}</TableCell>
|
||||
<TableCell className="text-right">{count}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
{tableLimit < sortedApps.length && (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full"
|
||||
onClick={() => setTableLimit(prev => prev + ITEMS_PER_PAGE)}
|
||||
>
|
||||
Load More
|
||||
</Button>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
52
frontend/src/components/text-parse-links.tsx
Normal file
52
frontend/src/components/text-parse-links.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { ClipboardIcon, ExternalLink } from "lucide-react";
|
||||
import { Fragment } from "react";
|
||||
|
||||
import handleCopy from "./handle-copy";
|
||||
|
||||
const URL_PATTERN = /(https?:\/\/[^\s,]+)/;
|
||||
const CODE_PATTERN = /`([^`]*)`/;
|
||||
|
||||
export default function TextParseLinks(text: string) {
|
||||
const codeParts = text.split(CODE_PATTERN);
|
||||
|
||||
return codeParts.map((part: string, codeIndex: number) => {
|
||||
if (codeIndex % 2 === 1) {
|
||||
return (
|
||||
<span
|
||||
key={`code-${codeIndex}`}
|
||||
className="bg-secondary py-1 px-2 rounded-lg inline-flex items-center gap-2"
|
||||
>
|
||||
{part}
|
||||
<ClipboardIcon
|
||||
className="size-3 cursor-pointer"
|
||||
onClick={() => handleCopy("command", part)}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
const urlParts = part.split(URL_PATTERN);
|
||||
|
||||
return (
|
||||
<Fragment key={`text-${codeIndex}`}>
|
||||
{urlParts.map((urlPart: string, urlIndex: number) => {
|
||||
if (urlIndex % 2 === 1) {
|
||||
return (
|
||||
<a
|
||||
key={`url-${codeIndex}-${urlIndex}`}
|
||||
href={urlPart}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center gap-1 text-blue-600 dark:text-blue-400 hover:underline font-medium transition-colors"
|
||||
>
|
||||
{urlPart}
|
||||
<ExternalLink className="size-3" />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return <Fragment key={`plain-${codeIndex}-${urlIndex}`}>{urlPart}</Fragment>;
|
||||
})}
|
||||
</Fragment>
|
||||
);
|
||||
});
|
||||
}
|
||||
369
frontend/src/components/ui/chart.tsx
Normal file
369
frontend/src/components/ui/chart.tsx
Normal file
@@ -0,0 +1,369 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as RechartsPrimitive from "recharts"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
// Format: { THEME_NAME: CSS_SELECTOR }
|
||||
const THEMES = { light: "", dark: ".dark" } as const
|
||||
|
||||
export type ChartConfig = {
|
||||
[k in string]: {
|
||||
label?: React.ReactNode
|
||||
icon?: React.ComponentType
|
||||
} & (
|
||||
| { color?: string; theme?: never }
|
||||
| { color?: never; theme: Record<keyof typeof THEMES, string> }
|
||||
)
|
||||
}
|
||||
|
||||
type ChartContextProps = {
|
||||
config: ChartConfig
|
||||
}
|
||||
|
||||
const ChartContext = React.createContext<ChartContextProps | null>(null)
|
||||
|
||||
function useChart() {
|
||||
const context = React.useContext(ChartContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useChart must be used within a <ChartContainer />")
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
const ChartContainer = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.ComponentProps<"div"> & {
|
||||
config: ChartConfig
|
||||
children: React.ComponentProps<
|
||||
typeof RechartsPrimitive.ResponsiveContainer
|
||||
>["children"]
|
||||
}
|
||||
>(({ id, className, children, config, ...props }, ref) => {
|
||||
const uniqueId = React.useId()
|
||||
const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
|
||||
|
||||
return (
|
||||
<ChartContext.Provider value={{ config }}>
|
||||
<div
|
||||
data-chart={chartId}
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-none [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChartStyle id={chartId} config={config} />
|
||||
<RechartsPrimitive.ResponsiveContainer>
|
||||
{children}
|
||||
</RechartsPrimitive.ResponsiveContainer>
|
||||
</div>
|
||||
</ChartContext.Provider>
|
||||
)
|
||||
})
|
||||
ChartContainer.displayName = "Chart"
|
||||
|
||||
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
||||
const colorConfig = Object.entries(config).filter(
|
||||
([, config]) => config.theme || config.color
|
||||
)
|
||||
|
||||
if (!colorConfig.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<style
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: Object.entries(THEMES)
|
||||
.map(
|
||||
([theme, prefix]) => `
|
||||
${prefix} [data-chart=${id}] {
|
||||
${colorConfig
|
||||
.map(([key, itemConfig]) => {
|
||||
const color =
|
||||
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
|
||||
itemConfig.color
|
||||
return color ? ` --color-${key}: ${color};` : null
|
||||
})
|
||||
.join("\n")}
|
||||
}
|
||||
`
|
||||
)
|
||||
.join("\n"),
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const ChartTooltip = RechartsPrimitive.Tooltip
|
||||
|
||||
const ChartTooltipContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
|
||||
React.ComponentProps<"div"> & {
|
||||
hideLabel?: boolean
|
||||
hideIndicator?: boolean
|
||||
indicator?: "line" | "dot" | "dashed"
|
||||
nameKey?: string
|
||||
labelKey?: string
|
||||
}
|
||||
>(
|
||||
(
|
||||
{
|
||||
active,
|
||||
payload,
|
||||
className,
|
||||
indicator = "dot",
|
||||
hideLabel = false,
|
||||
hideIndicator = false,
|
||||
label,
|
||||
labelFormatter,
|
||||
labelClassName,
|
||||
formatter,
|
||||
color,
|
||||
nameKey,
|
||||
labelKey,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const { config } = useChart()
|
||||
|
||||
const tooltipLabel = React.useMemo(() => {
|
||||
if (hideLabel || !payload?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const [item] = payload
|
||||
const key = `${labelKey || item?.dataKey || item?.name || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
const value =
|
||||
!labelKey && typeof label === "string"
|
||||
? config[label as keyof typeof config]?.label || label
|
||||
: itemConfig?.label
|
||||
|
||||
if (labelFormatter) {
|
||||
return (
|
||||
<div className={cn("font-medium", labelClassName)}>
|
||||
{labelFormatter(value, payload)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <div className={cn("font-medium", labelClassName)}>{value}</div>
|
||||
}, [
|
||||
label,
|
||||
labelFormatter,
|
||||
payload,
|
||||
hideLabel,
|
||||
labelClassName,
|
||||
config,
|
||||
labelKey,
|
||||
])
|
||||
|
||||
if (!active || !payload?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const nestLabel = payload.length === 1 && indicator !== "dot"
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{!nestLabel ? tooltipLabel : null}
|
||||
<div className="grid gap-1.5">
|
||||
{payload
|
||||
.filter((item) => item.type !== "none")
|
||||
.map((item, index) => {
|
||||
const key = `${nameKey || item.name || item.dataKey || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
const indicatorColor = color || item.payload.fill || item.color
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.dataKey}
|
||||
className={cn(
|
||||
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
|
||||
indicator === "dot" && "items-center"
|
||||
)}
|
||||
>
|
||||
{formatter && item?.value !== undefined && item.name ? (
|
||||
formatter(item.value, item.name, item, index, item.payload)
|
||||
) : (
|
||||
<>
|
||||
{itemConfig?.icon ? (
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
!hideIndicator && (
|
||||
<div
|
||||
className={cn(
|
||||
"shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
|
||||
{
|
||||
"h-2.5 w-2.5": indicator === "dot",
|
||||
"w-1": indicator === "line",
|
||||
"w-0 border-[1.5px] border-dashed bg-transparent":
|
||||
indicator === "dashed",
|
||||
"my-0.5": nestLabel && indicator === "dashed",
|
||||
}
|
||||
)}
|
||||
style={
|
||||
{
|
||||
"--color-bg": indicatorColor,
|
||||
"--color-border": indicatorColor,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-1 justify-between leading-none",
|
||||
nestLabel ? "items-end" : "items-center"
|
||||
)}
|
||||
>
|
||||
<div className="grid gap-1.5">
|
||||
{nestLabel ? tooltipLabel : null}
|
||||
<span className="text-muted-foreground">
|
||||
{itemConfig?.label || item.name}
|
||||
</span>
|
||||
</div>
|
||||
{item.value && (
|
||||
<span className="font-mono font-medium tabular-nums text-foreground">
|
||||
{item.value.toLocaleString()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
ChartTooltipContent.displayName = "ChartTooltip"
|
||||
|
||||
const ChartLegend = RechartsPrimitive.Legend
|
||||
|
||||
const ChartLegendContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.ComponentProps<"div"> &
|
||||
Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
|
||||
hideIcon?: boolean
|
||||
nameKey?: string
|
||||
}
|
||||
>(
|
||||
(
|
||||
{ className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
|
||||
ref
|
||||
) => {
|
||||
const { config } = useChart()
|
||||
|
||||
if (!payload?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex items-center justify-center gap-4",
|
||||
verticalAlign === "top" ? "pb-3" : "pt-3",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{payload
|
||||
.filter((item) => item.type !== "none")
|
||||
.map((item) => {
|
||||
const key = `${nameKey || item.dataKey || "value"}`
|
||||
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.value}
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{itemConfig?.icon && !hideIcon ? (
|
||||
<itemConfig.icon />
|
||||
) : (
|
||||
<div
|
||||
className="h-2 w-2 shrink-0 rounded-[2px]"
|
||||
style={{
|
||||
backgroundColor: item.color,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{itemConfig?.label}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
ChartLegendContent.displayName = "ChartLegend"
|
||||
|
||||
// Helper to extract item config from a payload.
|
||||
function getPayloadConfigFromPayload(
|
||||
config: ChartConfig,
|
||||
payload: unknown,
|
||||
key: string
|
||||
) {
|
||||
if (typeof payload !== "object" || payload === null) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const payloadPayload =
|
||||
"payload" in payload &&
|
||||
typeof payload.payload === "object" &&
|
||||
payload.payload !== null
|
||||
? payload.payload
|
||||
: undefined
|
||||
|
||||
let configLabelKey: string = key
|
||||
|
||||
if (
|
||||
key in payload &&
|
||||
typeof payload[key as keyof typeof payload] === "string"
|
||||
) {
|
||||
configLabelKey = payload[key as keyof typeof payload] as string
|
||||
} else if (
|
||||
payloadPayload &&
|
||||
key in payloadPayload &&
|
||||
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
|
||||
) {
|
||||
configLabelKey = payloadPayload[
|
||||
key as keyof typeof payloadPayload
|
||||
] as string
|
||||
}
|
||||
|
||||
return configLabelKey in config
|
||||
? config[configLabelKey]
|
||||
: config[key as keyof typeof config]
|
||||
}
|
||||
|
||||
export {
|
||||
ChartContainer,
|
||||
ChartTooltip,
|
||||
ChartTooltipContent,
|
||||
ChartLegend,
|
||||
ChartLegendContent,
|
||||
ChartStyle,
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useInView, useMotionValue, useSpring } from "framer-motion";
|
||||
import { useInView, useMotionValue, useSpring } from "motion/react";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
48
frontend/src/components/ui/scroll-area.tsx
Normal file
48
frontend/src/components/ui/scroll-area.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const ScrollArea = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("relative overflow-hidden", className)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollBar />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
))
|
||||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
|
||||
|
||||
const ScrollBar = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
>(({ className, orientation = "vertical", ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||
ref={ref}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"flex touch-none select-none transition-colors",
|
||||
orientation === "vertical" &&
|
||||
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
||||
orientation === "horizontal" &&
|
||||
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
))
|
||||
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
|
||||
|
||||
export { ScrollArea, ScrollBar }
|
||||
@@ -5,7 +5,7 @@ import React from "react";
|
||||
import type { OperatingSystem } from "@/lib/types";
|
||||
|
||||
// eslint-disable-next-line node/no-process-env
|
||||
export const basePath = process.env.BASE_PATH || "";
|
||||
export const basePath = process.env.BASE_PATH || "ProxmoxVE";
|
||||
|
||||
export const navbarLinks = [
|
||||
{
|
||||
|
||||
@@ -14,6 +14,8 @@ export type Script = {
|
||||
logo: string | null;
|
||||
config_path: string;
|
||||
description: string;
|
||||
disable?: boolean;
|
||||
disable_description?: string;
|
||||
install_methods: {
|
||||
type: "default" | "alpine";
|
||||
script: string;
|
||||
|
||||
@@ -97,9 +97,4 @@ msg_ok "Configured Service"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
|
||||
msg_info "Cleaning up"
|
||||
$STD apt -y autoremove
|
||||
$STD apt -y autoclean
|
||||
$STD apt -y clean
|
||||
msg_ok "Cleaned"
|
||||
cleanup_lxc
|
||||
|
||||
@@ -89,9 +89,4 @@ msg_ok "Created Service"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
|
||||
msg_info "Cleaning up"
|
||||
$STD apt -y autoremove
|
||||
$STD apt -y autoclean
|
||||
$STD apt -y clean
|
||||
msg_ok "Cleaned"
|
||||
cleanup_lxc
|
||||
|
||||
@@ -41,9 +41,4 @@ msg_ok "Created Service"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
|
||||
msg_info "Cleaning up"
|
||||
$STD apt -y autoremove
|
||||
$STD apt -y autoclean
|
||||
$STD apt -y clean
|
||||
msg_ok "Cleaned"
|
||||
cleanup_lxc
|
||||
|
||||
@@ -146,9 +146,4 @@ msg_ok "Created Service"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
|
||||
msg_info "Cleaning up"
|
||||
$STD apt -y autoremove
|
||||
$STD apt -y autoclean
|
||||
$STD apt -y clean
|
||||
msg_ok "Cleaned"
|
||||
cleanup_lxc
|
||||
|
||||
@@ -30,6 +30,7 @@ curl -fsSL "$RELEASE" -o $(basename "$RELEASE")
|
||||
$STD unzip Agent_Linux64*.zip
|
||||
chmod +x ./Agent
|
||||
echo $RELEASE >~/.agentdvr
|
||||
rm -rf Agent_Linux64*.zip
|
||||
msg_ok "Installed AgentDVR"
|
||||
|
||||
msg_info "Creating Service"
|
||||
@@ -53,10 +54,4 @@ msg_ok "Created Service"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -rf Agent_Linux64*.zip
|
||||
$STD apt -y autoremove
|
||||
$STD apt -y autoclean
|
||||
$STD apt -y clean
|
||||
msg_ok "Cleaned"
|
||||
cleanup_lxc
|
||||
|
||||
@@ -34,8 +34,4 @@ msg_ok "Installed Apache Cassandra"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
|
||||
msg_info "Cleaning up"
|
||||
$STD apt-get -y autoremove
|
||||
$STD apt-get -y autoclean
|
||||
msg_ok "Cleaned"
|
||||
cleanup_lxc
|
||||
|
||||
@@ -36,8 +36,4 @@ msg_ok "Installed Apache CouchDB."
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
|
||||
msg_info "Cleaning up"
|
||||
$STD apt-get -y autoremove
|
||||
$STD apt-get -y autoclean
|
||||
msg_ok "Cleaned"
|
||||
cleanup_lxc
|
||||
|
||||
@@ -71,6 +71,8 @@ mv ~/mysql-connector-j-9.3.0/mysql-connector-j-9.3.0.jar /etc/guacamole/lib/
|
||||
curl -fsSL "https://downloads.apache.org/guacamole/${RELEASE_SERVER}/binary/guacamole-auth-jdbc-${RELEASE_SERVER}.tar.gz" -o "/root/guacamole-auth-jdbc-${RELEASE_SERVER}.tar.gz"
|
||||
$STD tar -xf ~/guacamole-auth-jdbc-$RELEASE_SERVER.tar.gz
|
||||
mv ~/guacamole-auth-jdbc-$RELEASE_SERVER/mysql/guacamole-auth-jdbc-mysql-$RELEASE_SERVER.jar /etc/guacamole/extensions/
|
||||
rm -rf ~/mysql-connector-j-9.3.0{,.tar.gz}
|
||||
rm -rf ~/guacamole-auth-jdbc-$RELEASE_SERVER{,.tar.gz}
|
||||
msg_ok "Setup Apache Guacamole"
|
||||
|
||||
msg_info "Setup Database"
|
||||
@@ -146,10 +148,4 @@ msg_ok "Setup Service"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -rf ~/mysql-connector-j-9.3.0{,.tar.gz}
|
||||
rm -rf ~/guacamole-auth-jdbc-$RELEASE_SERVER{,.tar.gz}
|
||||
$STD apt -y autoremove
|
||||
$STD apt -y autoclean
|
||||
msg_ok "Cleaned"
|
||||
cleanup_lxc
|
||||
|
||||
@@ -69,8 +69,4 @@ msg_ok "Created Service"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
|
||||
msg_info "Cleaning up"
|
||||
$STD apt-get -y autoremove
|
||||
$STD apt-get -y autoclean
|
||||
msg_ok "Cleaned"
|
||||
cleanup_lxc
|
||||
|
||||
@@ -119,7 +119,7 @@ curl -fsSL "$TOMCAT_URL" -o "/tmp/tomcat.tar.gz"
|
||||
mkdir -p /opt/tomcat-$TOMCAT_VERSION
|
||||
tar --strip-components=1 -xzf /tmp/tomcat.tar.gz -C /opt/tomcat-$TOMCAT_VERSION
|
||||
chown -R root:root /opt/tomcat-$TOMCAT_VERSION
|
||||
|
||||
rm -f /tmp/tomcat.tar.gz
|
||||
cat <<EOF >/etc/systemd/system/tomcat.service
|
||||
[Unit]
|
||||
Description=Apache Tomcat Web Application Container
|
||||
@@ -147,9 +147,4 @@ msg_ok "Tomcat $LATEST_VERSION installed and started"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -f /tmp/tomcat.tar.gz
|
||||
$STD apt-get -y autoremove
|
||||
$STD apt-get -y autoclean
|
||||
msg_ok "Cleaned"
|
||||
cleanup_lxc
|
||||
|
||||
@@ -24,8 +24,4 @@ msg_ok "Installed Apt-Cacher NG"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
|
||||
msg_info "Cleaning up"
|
||||
$STD apt-get -y autoremove
|
||||
$STD apt-get -y autoclean
|
||||
msg_ok "Cleaned"
|
||||
cleanup_lxc
|
||||
|
||||
@@ -91,9 +91,4 @@ msg_ok "Created Service"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
|
||||
msg_info "Cleaning up"
|
||||
$STD apt -y autoremove
|
||||
$STD apt -y autoclean
|
||||
$STD apt -y clean
|
||||
msg_ok "Cleaned"
|
||||
cleanup_lxc
|
||||
|
||||
@@ -82,8 +82,4 @@ msg_ok "Created Service"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
|
||||
msg_info "Cleaning up"
|
||||
$STD apt-get -y autoremove
|
||||
$STD apt-get -y autoclean
|
||||
msg_ok "Cleaned"
|
||||
cleanup_lxc
|
||||
|
||||
@@ -24,6 +24,7 @@ if [[ ${prompt,,} =~ ^(y|yes)$ ]]; then
|
||||
systemctl disable -q --now nginx
|
||||
curl -fsSL "$(curl -fsSL https://api.github.com/repos/mayswind/ariang/releases/latest | grep download | grep AllInOne.zip | cut -d\" -f4)" -o $(basename "$(curl -fsSL https://api.github.com/repos/mayswind/ariang/releases/latest | grep download | grep AllInOne.zip | cut -d\" -f4)")
|
||||
$STD unzip AriaNg-*-AllInOne.zip -d /var/www
|
||||
rm AriaNg-*-AllInOne.zip
|
||||
rm /etc/nginx/sites-enabled/*
|
||||
cat <<EOF >/etc/nginx/conf.d/ariang.conf
|
||||
server {
|
||||
@@ -84,9 +85,4 @@ msg_ok "Created Service"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm AriaNg-*-AllInOne.zip
|
||||
$STD apt-get -y autoremove
|
||||
$STD apt-get -y autoclean
|
||||
msg_ok "Cleaned"
|
||||
cleanup_lxc
|
||||
|
||||
@@ -88,6 +88,7 @@ curl -fsSL "$DOWNLOAD_URL" -o "$temp_file"
|
||||
mkdir -p /opt/asterisk
|
||||
tar zxf "$temp_file" --strip-components=1 -C /opt/asterisk
|
||||
cd /opt/asterisk
|
||||
rm -f "$temp_file"
|
||||
msg_ok "Downloaded Asterisk ($RELEASE)"
|
||||
|
||||
msg_info "Installing Asterisk"
|
||||
@@ -105,10 +106,5 @@ msg_ok "Installed Asterisk"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
cleanup_lxc
|
||||
|
||||
msg_info "Cleaning up"
|
||||
rm -f "$temp_file"
|
||||
$STD apt -y autoremove
|
||||
$STD apt -y autoclean
|
||||
$STD apt -y clean
|
||||
msg_ok "Cleaned"
|
||||
|
||||
@@ -28,8 +28,4 @@ msg_ok "Setup audiobookshelf"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
|
||||
msg_info "Cleaning up"
|
||||
$STD apt-get -y autoremove
|
||||
$STD apt-get -y autoclean
|
||||
msg_ok "Cleaned"
|
||||
cleanup_lxc
|
||||
|
||||
@@ -66,8 +66,4 @@ msg_ok "Authelia Setup completed"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
|
||||
msg_info "Cleaning up"
|
||||
$STD apt-get -y autoremove
|
||||
$STD apt-get -y autoclean
|
||||
msg_ok "Cleaned"
|
||||
cleanup_lxc
|
||||
|
||||
@@ -46,8 +46,4 @@ msg_ok "Created Service"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
|
||||
msg_info "Cleaning up"
|
||||
$STD apt-get -y autoremove
|
||||
$STD apt-get -y autoclean
|
||||
msg_ok "Cleaned"
|
||||
cleanup_lxc
|
||||
|
||||
@@ -323,8 +323,4 @@ msg_ok "Created scripts and service files"
|
||||
|
||||
motd_ssh
|
||||
customize
|
||||
|
||||
msg_info "Cleaning up"
|
||||
$STD apt-get -y autoremove
|
||||
$STD apt-get -y autoclean
|
||||
msg_ok "Cleaned"
|
||||
cleanup_lxc
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user