mirror of
				https://github.com/community-scripts/ProxmoxVE.git
				synced 2025-11-04 02:12:49 +00:00 
			
		
		
		
	Compare commits
	
		
			23 Commits
		
	
	
		
			2025-01-30
			...
			2025-02-02
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					05ec444e2d | ||
| 
						 | 
					19911f5b81 | ||
| 
						 | 
					57b37b1e16 | ||
| 
						 | 
					91a3fd10e0 | ||
| 
						 | 
					6f0ebac209 | ||
| 
						 | 
					f40e6e5453 | ||
| 
						 | 
					8a07124457 | ||
| 
						 | 
					41c32af0d6 | ||
| 
						 | 
					3cb4196fee | ||
| 
						 | 
					a33108cd4f | ||
| 
						 | 
					a0d7c5db0e | ||
| 
						 | 
					baef2a0cb9 | ||
| 
						 | 
					f9b84bf5ee | ||
| 
						 | 
					ccab9d1be5 | ||
| 
						 | 
					58a2ece7b7 | ||
| 
						 | 
					aa16f936c8 | ||
| 
						 | 
					c8829beddd | ||
| 
						 | 
					3adc22d837 | ||
| 
						 | 
					71b1288220 | ||
| 
						 | 
					3c58303a9f | ||
| 
						 | 
					b8edf0dd68 | ||
| 
						 | 
					2fa3116c9c | ||
| 
						 | 
					d416ff9cfa | 
							
								
								
									
										5
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
    "files.associations": {
 | 
			
		||||
        "*.func": "shellscript"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										47
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -17,6 +17,53 @@ All LXC instances created using this repository come pre-installed with Midnight
 | 
			
		||||
Do not break established syntax in this file, as it is automatically updated by a Github Workflow
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## 2025-02-02
 | 
			
		||||
 | 
			
		||||
### Changed
 | 
			
		||||
 | 
			
		||||
### 🚀 Updated Scripts
 | 
			
		||||
 | 
			
		||||
- Prometheus PVE Exporter: Add `--default-timeout=300` to pip install commands [@andygrunwald](https://github.com/andygrunwald) ([#1950](https://github.com/community-scripts/ProxmoxVE/pull/1950))
 | 
			
		||||
- fix z2m update function to 2.1.0 [@MickLesk](https://github.com/MickLesk) ([#1938](https://github.com/community-scripts/ProxmoxVE/pull/1938))
 | 
			
		||||
 | 
			
		||||
### 🧰 Maintenance
 | 
			
		||||
 | 
			
		||||
- VSCode: Add Shellscript Syntax highlighting for *.func files [@andygrunwald](https://github.com/andygrunwald) ([#1948](https://github.com/community-scripts/ProxmoxVE/pull/1948))
 | 
			
		||||
 | 
			
		||||
## 2025-02-01
 | 
			
		||||
 | 
			
		||||
### Changed
 | 
			
		||||
 | 
			
		||||
### 💥 Breaking Changes
 | 
			
		||||
 | 
			
		||||
- [DCMA] Delete scripts 5etools and pf2etools - Copyright abuse [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1922](https://github.com/community-scripts/ProxmoxVE/pull/1922))
 | 
			
		||||
 | 
			
		||||
### ✨ New Scripts
 | 
			
		||||
 | 
			
		||||
- New script: Baïkal [@bvdberg01](https://github.com/bvdberg01) ([#1913](https://github.com/community-scripts/ProxmoxVE/pull/1913))
 | 
			
		||||
 | 
			
		||||
### 🚀 Updated Scripts
 | 
			
		||||
 | 
			
		||||
- Bug fix: Paymenter [@opastorello](https://github.com/opastorello) ([#1917](https://github.com/community-scripts/ProxmoxVE/pull/1917))
 | 
			
		||||
 | 
			
		||||
## 2025-01-31
 | 
			
		||||
 | 
			
		||||
### Changed
 | 
			
		||||
 | 
			
		||||
### ✨ New Scripts
 | 
			
		||||
 | 
			
		||||
- New Script: Paymenter [@opastorello](https://github.com/opastorello) ([#1827](https://github.com/community-scripts/ProxmoxVE/pull/1827))
 | 
			
		||||
 | 
			
		||||
### 🚀 Updated Scripts
 | 
			
		||||
 | 
			
		||||
- [Fix] Alpine-IT-Tools, add missing ssh package for root ssh access [@CrazyWolf13](https://github.com/CrazyWolf13) ([#1891](https://github.com/community-scripts/ProxmoxVE/pull/1891))
 | 
			
		||||
- [Fix] Change Download of Trilium after there change the tag/release logic [@MickLesk](https://github.com/MickLesk) ([#1892](https://github.com/community-scripts/ProxmoxVE/pull/1892))
 | 
			
		||||
 | 
			
		||||
### 🌐 Website
 | 
			
		||||
 | 
			
		||||
- [Website] Enhance DataFetcher with better UI components and add reactive data fetching intervals [@BramSuurdje](https://github.com/BramSuurdje) ([#1902](https://github.com/community-scripts/ProxmoxVE/pull/1902))
 | 
			
		||||
- [Website] Update /data/page.tsx [@michelroegl-brunner](https://github.com/michelroegl-brunner) ([#1900](https://github.com/community-scripts/ProxmoxVE/pull/1900))
 | 
			
		||||
 | 
			
		||||
## 2025-01-30
 | 
			
		||||
 | 
			
		||||
### Changed
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										113
									
								
								ct/5etools.sh
									
									
									
									
									
								
							
							
						
						
									
										113
									
								
								ct/5etools.sh
									
									
									
									
									
								
							@@ -1,113 +0,0 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
source <(curl -s https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
 | 
			
		||||
# Copyright (c) 2021-2025 community-scripts ORG
 | 
			
		||||
# Author: TheRealVira
 | 
			
		||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
 | 
			
		||||
# Source: https://5e.tools/
 | 
			
		||||
 | 
			
		||||
# App Default Values
 | 
			
		||||
APP="5etools"
 | 
			
		||||
var_tags="wiki"
 | 
			
		||||
var_cpu="1"
 | 
			
		||||
var_ram="512"
 | 
			
		||||
var_disk="13"
 | 
			
		||||
var_os="debian"
 | 
			
		||||
var_version="12"
 | 
			
		||||
var_unprivileged="1"
 | 
			
		||||
 | 
			
		||||
# App Output & Base Settings
 | 
			
		||||
header_info "$APP"
 | 
			
		||||
base_settings
 | 
			
		||||
 | 
			
		||||
# Core
 | 
			
		||||
variables
 | 
			
		||||
color
 | 
			
		||||
catch_errors
 | 
			
		||||
 | 
			
		||||
function update_script() {
 | 
			
		||||
    header_info
 | 
			
		||||
    check_container_storage
 | 
			
		||||
    check_container_resources
 | 
			
		||||
 | 
			
		||||
    # Check if installation is present | -f for file, -d for folder
 | 
			
		||||
    if [[ ! -d "/opt/${APP}" ]]; then
 | 
			
		||||
        msg_error "No ${APP} Installation Found!"
 | 
			
		||||
        exit
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    RELEASE=$(curl -s https://api.github.com/repos/5etools-mirror-3/5etools-src/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }')
 | 
			
		||||
    if [[ "${RELEASE}" != "$(cat /opt/${APP}_version.txt)" ]] || [[ ! -f "/opt/${APP}_version.txt" ]]; then
 | 
			
		||||
        # Crawling the new version and checking whether an update is required
 | 
			
		||||
        msg_info "Updating System"
 | 
			
		||||
        apt-get update &>/dev/null
 | 
			
		||||
        apt-get -y upgrade &>/dev/null
 | 
			
		||||
        msg_ok "Updated System"
 | 
			
		||||
 | 
			
		||||
        # Execute Update
 | 
			
		||||
        msg_info "Updating base 5etools"
 | 
			
		||||
        cd /opt
 | 
			
		||||
        wget -q "https://github.com/5etools-mirror-3/5etools-src/archive/refs/tags/${RELEASE}.zip"
 | 
			
		||||
        unzip -q "${RELEASE}.zip"
 | 
			
		||||
        mv "/opt/${APP}/img" "/opt/img-backup"
 | 
			
		||||
        rm -rf "/opt/${APP}"
 | 
			
		||||
        mv "${APP}-src-${RELEASE:1}" "/opt/${APP}"
 | 
			
		||||
        mv "/opt/img-backup" "/opt/${APP}/img"
 | 
			
		||||
        cd /opt/5etools
 | 
			
		||||
        $STD npm install
 | 
			
		||||
        $STD npm run build
 | 
			
		||||
        cd ~
 | 
			
		||||
        echo "${RELEASE}" >"/opt/${APP}_version.txt"
 | 
			
		||||
        chown -R www-data: "/opt/${APP}"
 | 
			
		||||
        chmod -R 755 "/opt/${APP}"
 | 
			
		||||
        msg_ok "Updated base 5etools"
 | 
			
		||||
        # Cleaning up
 | 
			
		||||
        msg_info "Cleaning Up"
 | 
			
		||||
        rm -rf /opt/${RELEASE}.zip
 | 
			
		||||
        $STD apt-get -y autoremove
 | 
			
		||||
        $STD apt-get -y autoclean
 | 
			
		||||
        msg_ok "Cleanup Completed"
 | 
			
		||||
    else
 | 
			
		||||
        msg_ok "No update required. Base ${APP} is already at ${RELEASE}"
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    IMG_RELEASE=$(curl -s https://api.github.com/repos/5etools-mirror-2/5etools-img/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }')
 | 
			
		||||
    if [[ "${IMG_RELEASE}" != "$(cat /opt/${APP}_IMG_version.txt)" ]] || [[ ! -f "/opt/${APP}_IMG_version.txt" ]]; then
 | 
			
		||||
        # Crawling the new version and checking whether an update is required
 | 
			
		||||
        msg_info "Updating System"
 | 
			
		||||
        apt-get update &>/dev/null
 | 
			
		||||
        apt-get -y upgrade &>/dev/null
 | 
			
		||||
        msg_ok "Updated System"
 | 
			
		||||
 | 
			
		||||
        # Execute Update
 | 
			
		||||
        msg_info "Updating 5etools images"
 | 
			
		||||
        curl -sSL "https://github.com/5etools-mirror-2/5etools-img/archive/refs/tags/${IMG_RELEASE}.zip" > "${IMG_RELEASE}.zip"
 | 
			
		||||
        unzip -q "${IMG_RELEASE}.zip"
 | 
			
		||||
        rm -rf "/opt/${APP}/img"
 | 
			
		||||
        mv "${APP}-img-${IMG_RELEASE:1}" "/opt/${APP}/img"
 | 
			
		||||
        echo "${IMG_RELEASE}" >"/opt/${APP}_IMG_version.txt"
 | 
			
		||||
        chown -R www-data: "/opt/${APP}"
 | 
			
		||||
        chmod -R 755 "/opt/${APP}"
 | 
			
		||||
 | 
			
		||||
        msg_ok "Updating 5etools images"
 | 
			
		||||
 | 
			
		||||
        # Cleaning up
 | 
			
		||||
        msg_info "Cleaning Up"
 | 
			
		||||
        rm -rf /opt/${RELEASE}.zip
 | 
			
		||||
        rm -rf ${IMG_RELEASE}.zip
 | 
			
		||||
        $STD apt-get -y autoremove
 | 
			
		||||
        $STD apt-get -y autoclean
 | 
			
		||||
        msg_ok "Cleanup Completed"
 | 
			
		||||
    else
 | 
			
		||||
        msg_ok "No update required. ${APP} images are already at ${IMG_RELEASE}"
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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}${CL}"
 | 
			
		||||
							
								
								
									
										75
									
								
								ct/baikal.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								ct/baikal.sh
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,75 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
source <(curl -s https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
 | 
			
		||||
# Copyright (c) 2021-2025 community-scripts ORG
 | 
			
		||||
# Author: bvdberg01
 | 
			
		||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
 | 
			
		||||
# Source: https://sabre.io/baikal/
 | 
			
		||||
 | 
			
		||||
# App Default Values
 | 
			
		||||
APP="Baikal"
 | 
			
		||||
var_tags="Dav"
 | 
			
		||||
var_cpu="1"
 | 
			
		||||
var_ram="512"
 | 
			
		||||
var_disk="4"
 | 
			
		||||
var_os="debian"
 | 
			
		||||
var_version="12"
 | 
			
		||||
var_unprivileged="1"
 | 
			
		||||
 | 
			
		||||
# App Output & Base Settings
 | 
			
		||||
header_info "$APP"
 | 
			
		||||
base_settings
 | 
			
		||||
 | 
			
		||||
# Core
 | 
			
		||||
variables
 | 
			
		||||
color
 | 
			
		||||
catch_errors
 | 
			
		||||
 | 
			
		||||
function update_script() {
 | 
			
		||||
  header_info
 | 
			
		||||
  check_container_storage
 | 
			
		||||
  check_container_resources
 | 
			
		||||
  if [[ ! -d /opt/baikal ]]; then
 | 
			
		||||
    msg_error "No ${APP} Installation Found!"
 | 
			
		||||
    exit
 | 
			
		||||
  fi
 | 
			
		||||
  RELEASE=$(curl -s https://api.github.com/repos/sabre-io/Baikal/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }')
 | 
			
		||||
  if [[ ! -f /opt/${APP}_version.txt ]] || [[ "${RELEASE}" != "$(cat /opt/${APP}_version.txt)" ]]; then
 | 
			
		||||
    msg_info "Stopping Service"
 | 
			
		||||
    systemctl stop apache2
 | 
			
		||||
    msg_ok "Stopped Service"
 | 
			
		||||
 | 
			
		||||
    msg_info "Updating ${APP} to v${RELEASE}"
 | 
			
		||||
    cd /opt
 | 
			
		||||
    wget -q "https://github.com/sabre-io/baikal/releases/download/${RELEASE}/baikal-${RELEASE}.zip"
 | 
			
		||||
    mv /opt/baikal /opt/baikal-backup
 | 
			
		||||
    unzip -o -q "baikal-${RELEASE}.zip"
 | 
			
		||||
    cp -r /opt/baikal-backup/config/baikal.yaml /opt/baikal/config/
 | 
			
		||||
    cp -r /opt/baikal-backup/Specific/ /opt/baikal/
 | 
			
		||||
    chown -R www-data:www-data /opt/baikal/
 | 
			
		||||
    chmod -R 755 /opt/baikal/
 | 
			
		||||
    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 -rf "/opt/baikal-${RELEASE}.zip"
 | 
			
		||||
    rm -rf /opt/baikal-backup
 | 
			
		||||
    msg_ok "Cleaned"
 | 
			
		||||
    msg_ok "Updated Successfully"
 | 
			
		||||
  else
 | 
			
		||||
    msg_ok "No update required. ${APP} is already at v${RELEASE}"
 | 
			
		||||
  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}${CL}"
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
    ______     __              __    
 | 
			
		||||
   / ____/__  / /_____  ____  / /____
 | 
			
		||||
  /___ \/ _ \/ __/ __ \/ __ \/ / ___/
 | 
			
		||||
 ____/ /  __/ /_/ /_/ / /_/ / (__  ) 
 | 
			
		||||
/_____/\___/\__/\____/\____/_/____/  
 | 
			
		||||
                                     
 | 
			
		||||
							
								
								
									
										6
									
								
								ct/headers/baikal
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								ct/headers/baikal
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
    ____        _ __         __
 | 
			
		||||
   / __ )____ _(_) /______ _/ /
 | 
			
		||||
  / __  / __ `/ / //_/ __ `/ / 
 | 
			
		||||
 / /_/ / /_/ / / ,< / /_/ / /  
 | 
			
		||||
/_____/\__,_/_/_/|_|\__,_/_/   
 | 
			
		||||
                               
 | 
			
		||||
							
								
								
									
										6
									
								
								ct/headers/paymenter
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								ct/headers/paymenter
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
    ____                                   __           
 | 
			
		||||
   / __ \____ ___  ______ ___  ___  ____  / /____  _____
 | 
			
		||||
  / /_/ / __ `/ / / / __ `__ \/ _ \/ __ \/ __/ _ \/ ___/
 | 
			
		||||
 / ____/ /_/ / /_/ / / / / / /  __/ / / / /_/  __/ /    
 | 
			
		||||
/_/    \__,_/\__, /_/ /_/ /_/\___/_/ /_/\__/\___/_/     
 | 
			
		||||
            /____/                                      
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
    ____  _______      ______            __    
 | 
			
		||||
   / __ \/ __/__ \ ___/_  __/___  ____  / /____
 | 
			
		||||
  / /_/ / /_ __/ // _ \/ / / __ \/ __ \/ / ___/
 | 
			
		||||
 / ____/ __// __//  __/ / / /_/ / /_/ / (__  ) 
 | 
			
		||||
/_/   /_/  /____/\___/_/  \____/\____/_/____/  
 | 
			
		||||
                                               
 | 
			
		||||
							
								
								
									
										56
									
								
								ct/paymenter.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								ct/paymenter.sh
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
source <(curl -s https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
 | 
			
		||||
 | 
			
		||||
# Copyright (c) 2021-2025 community-scripts ORG
 | 
			
		||||
# Author: Nícolas Pastorello (opastorello)
 | 
			
		||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
 | 
			
		||||
 | 
			
		||||
# App Default Values
 | 
			
		||||
APP="Paymenter"
 | 
			
		||||
var_tags="hosting;ecommerce;marketplace;"
 | 
			
		||||
var_cpu="2"
 | 
			
		||||
var_ram="1024"
 | 
			
		||||
var_disk="5"
 | 
			
		||||
var_os="debian"
 | 
			
		||||
var_version="12"
 | 
			
		||||
var_unprivileged="1"
 | 
			
		||||
 | 
			
		||||
# App Output & Base Settings
 | 
			
		||||
header_info "$APP"
 | 
			
		||||
base_settings
 | 
			
		||||
 | 
			
		||||
# Core
 | 
			
		||||
variables
 | 
			
		||||
color
 | 
			
		||||
catch_errors
 | 
			
		||||
 | 
			
		||||
function update_script() {
 | 
			
		||||
  header_info
 | 
			
		||||
  check_container_storage
 | 
			
		||||
  check_container_resources
 | 
			
		||||
 | 
			
		||||
  if [[ ! -d /opt/paymenter ]]; then
 | 
			
		||||
    msg_error "No ${APP} Installation Found!"
 | 
			
		||||
    exit
 | 
			
		||||
  fi
 | 
			
		||||
  RELEASE=$(curl -s https://api.github.com/repos/paymenter/paymenter/releases/latest | grep '"tag_name"' | sed -E 's/.*"tag_name": "([^"]+)".*/\1/')
 | 
			
		||||
  if [[ ! -f /opt/${APP}_version.txt ]] || [[ "${RELEASE}" != "$(cat /opt/${APP}_version.txt)" ]]; then
 | 
			
		||||
    msg_info "Updating ${APP} to ${RELEASE}"
 | 
			
		||||
    echo "${RELEASE}" >/opt/${APP}_version.txt
 | 
			
		||||
    cd /opt/paymenter
 | 
			
		||||
    php artisan p:upgrade --no-interaction &>/dev/null
 | 
			
		||||
    msg_ok "Updated Successfully"
 | 
			
		||||
  else
 | 
			
		||||
    msg_ok "No update required. ${APP} is already at ${RELEASE}."
 | 
			
		||||
  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}:80${CL}"
 | 
			
		||||
@@ -1,81 +0,0 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
source <(curl -s https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/misc/build.func)
 | 
			
		||||
# Copyright (c) 2021-2025 community-scripts ORG
 | 
			
		||||
# Author: TheRealVira
 | 
			
		||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
 | 
			
		||||
# Source: https://pf2etools.com/
 | 
			
		||||
 | 
			
		||||
# App Default Values
 | 
			
		||||
APP="Pf2eTools"
 | 
			
		||||
var_tags="wiki"
 | 
			
		||||
var_cpu="1"
 | 
			
		||||
var_ram="512"
 | 
			
		||||
var_disk="6"
 | 
			
		||||
var_os="debian"
 | 
			
		||||
var_version="12"
 | 
			
		||||
var_unprivileged="1"
 | 
			
		||||
 | 
			
		||||
# App Output & Base Settings
 | 
			
		||||
header_info "$APP"
 | 
			
		||||
base_settings
 | 
			
		||||
 | 
			
		||||
# Core
 | 
			
		||||
variables
 | 
			
		||||
color
 | 
			
		||||
catch_errors
 | 
			
		||||
 | 
			
		||||
function update_script() {
 | 
			
		||||
    header_info
 | 
			
		||||
    check_container_storage
 | 
			
		||||
    check_container_resources
 | 
			
		||||
 | 
			
		||||
    # Check if installation is present | -f for file, -d for folder
 | 
			
		||||
    if [[ ! -d "/opt/${APP}" ]]; then
 | 
			
		||||
        msg_error "No ${APP} Installation Found!"
 | 
			
		||||
        exit
 | 
			
		||||
    fi
 | 
			
		||||
 | 
			
		||||
    RELEASE=$(curl -s https://api.github.com/repos/Pf2eToolsOrg/Pf2eTools/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }')
 | 
			
		||||
    if [[ "${RELEASE}" != "$(cat /opt/${APP}_version.txt)" ]] || [[ ! -f "/opt/${APP}_version.txt" ]]; then
 | 
			
		||||
        # Crawling the new version and checking whether an update is required
 | 
			
		||||
        msg_info "Updating System"
 | 
			
		||||
        apt-get update &>/dev/null
 | 
			
		||||
        apt-get -y upgrade &>/dev/null
 | 
			
		||||
        msg_ok "Updated System"
 | 
			
		||||
 | 
			
		||||
        # Execute Update
 | 
			
		||||
        msg_info "Updating ${APP}"
 | 
			
		||||
        cd /opt
 | 
			
		||||
        wget -q "https://github.com/Pf2eToolsOrg/Pf2eTools/archive/refs/tags/${RELEASE}.zip"
 | 
			
		||||
        unzip -q ${RELEASE}.zip
 | 
			
		||||
        rm -rf "/opt/${APP}"
 | 
			
		||||
        mv ${APP}-${RELEASE:1} /opt/${APP}
 | 
			
		||||
        cd /opt/Pf2eTools
 | 
			
		||||
        $STD npm install
 | 
			
		||||
        $STD npm run build
 | 
			
		||||
        echo "${RELEASE}" >"/opt/${APP}_version.txt"
 | 
			
		||||
        msg_ok "Updated ${APP}"
 | 
			
		||||
 | 
			
		||||
        chown -R www-data: "/opt/${APP}"
 | 
			
		||||
        chmod -R 755 "/opt/${APP}"
 | 
			
		||||
 | 
			
		||||
        # Cleaning up
 | 
			
		||||
        msg_info "Cleaning Up"
 | 
			
		||||
        rm -rf /opt/${RELEASE}.zip
 | 
			
		||||
        $STD apt-get -y autoremove
 | 
			
		||||
        $STD apt-get -y autoclean
 | 
			
		||||
        msg_ok "Cleanup Completed"
 | 
			
		||||
    else
 | 
			
		||||
        msg_ok "No update required. ${APP} is already at ${RELEASE}"
 | 
			
		||||
    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}${CL}"
 | 
			
		||||
@@ -37,7 +37,7 @@ function update_script() {
 | 
			
		||||
    msg_ok "Stopped ${APP}"
 | 
			
		||||
 | 
			
		||||
    msg_info "Updating ${APP}"
 | 
			
		||||
    pip install prometheus-pve-exporter --upgrade --root-user-action=ignore &>/dev/null
 | 
			
		||||
    pip install prometheus-pve-exporter --default-timeout=300 --upgrade --root-user-action=ignore &>/dev/null
 | 
			
		||||
    msg_ok "Updated ${APP}"
 | 
			
		||||
 | 
			
		||||
    msg_info "Starting ${APP}"
 | 
			
		||||
 
 | 
			
		||||
@@ -35,22 +35,22 @@ function update_script() {
 | 
			
		||||
    RELEASE=$(curl -s https://api.github.com/repos/TriliumNext/Notes/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }')
 | 
			
		||||
 | 
			
		||||
    msg_info "Stopping ${APP}"
 | 
			
		||||
    systemctl stop trilium.service
 | 
			
		||||
    systemctl stop trilium
 | 
			
		||||
    sleep 1
 | 
			
		||||
    msg_ok "Stopped ${APP}"
 | 
			
		||||
 | 
			
		||||
    msg_info "Updating to ${RELEASE}"
 | 
			
		||||
    wget -q https://github.com/TriliumNext/Notes/releases/download/${RELEASE}/TriliumNextNotes-${RELEASE}-server-linux-x64.tar.xz
 | 
			
		||||
    tar -xf TriliumNextNotes-${RELEASE}-server-linux-x64.tar.xz
 | 
			
		||||
    wget -q https://github.com/TriliumNext/Notes/releases/download/${RELEASE}/TriliumNextNotes-linux-x64-${RELEASE}.tar.xz
 | 
			
		||||
    tar -xf TriliumNextNotes-linux-x64-${RELEASE}.tar.xz
 | 
			
		||||
    cp -r trilium-linux-x64-server/* /opt/trilium/
 | 
			
		||||
    msg_ok "Updated to ${RELEASE}"
 | 
			
		||||
 | 
			
		||||
    msg_info "Cleaning up"
 | 
			
		||||
    rm -rf TriliumNextNotes-${RELEASE}-server-linux-x64.tar.xz trilium-linux-x64-server
 | 
			
		||||
    rm -rf TriliumNextNotes-linux-x64-${RELEASE}.tar.xz trilium-linux-x64-server
 | 
			
		||||
    msg_ok "Cleaned"
 | 
			
		||||
 | 
			
		||||
    msg_info "Starting ${APP}"
 | 
			
		||||
    systemctl start trilium.service
 | 
			
		||||
    systemctl start trilium
 | 
			
		||||
    sleep 1
 | 
			
		||||
    msg_ok "Started ${APP}"
 | 
			
		||||
    msg_ok "Updated Successfully"
 | 
			
		||||
@@ -64,4 +64,4 @@ 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}:8080${CL}"
 | 
			
		||||
echo -e "${TAB}${GATEWAY}${BGN}http://${IP}:8080${CL}"
 | 
			
		||||
 
 | 
			
		||||
@@ -39,24 +39,33 @@ function update_script() {
 | 
			
		||||
    msg_ok "Stopped Service"
 | 
			
		||||
 | 
			
		||||
    msg_info "Creating Backup"
 | 
			
		||||
    mkdir -p /opt/z2m_backup
 | 
			
		||||
    tar -czf /opt/z2m_backup/${APP}_backup_$(date +%Y%m%d%H%M%S).tar.gz -C /opt zigbee2mqtt &>/dev/null
 | 
			
		||||
    mv /opt/zigbee2mqtt/data /opt/z2m_backup
 | 
			
		||||
      rm -rf /opt/${APP}_backup*.tar.gz
 | 
			
		||||
      mkdir -p /opt/z2m_backup
 | 
			
		||||
      tar -czf /opt/z2m_backup/${APP}_backup_$(date +%Y%m%d%H%M%S).tar.gz -C /opt zigbee2mqtt &>/dev/null
 | 
			
		||||
      mv /opt/zigbee2mqtt/data /opt/z2m_backup
 | 
			
		||||
    msg_ok "Backup Created"
 | 
			
		||||
 | 
			
		||||
    msg_info "Updating ${APP} to v${RELEASE}"
 | 
			
		||||
    cd /opt
 | 
			
		||||
    wget -q "https://github.com/Koenkk/zigbee2mqtt/archive/refs/tags/${RELEASE}.zip"
 | 
			
		||||
    unzip -q ${RELEASE}.zip
 | 
			
		||||
    mv zigbee2mqtt-${RELEASE} /opt/zigbee2mqtt
 | 
			
		||||
    rm -rf /opt/zigbee2mqtt/data
 | 
			
		||||
    mv /opt/z2m_backup/data /opt/zigbee2mqtt
 | 
			
		||||
    cd /opt/zigbee2mqtt 
 | 
			
		||||
    pnpm install --frozen-lockfile &>/dev/null
 | 
			
		||||
    pnpm build &>/dev/null
 | 
			
		||||
      cd /opt
 | 
			
		||||
      wget -q "https://github.com/Koenkk/zigbee2mqtt/archive/refs/tags/${RELEASE}.zip"
 | 
			
		||||
      unzip -q ${RELEASE}.zip
 | 
			
		||||
      rm -rf /opt/zigbee2mqtt
 | 
			
		||||
      mv zigbee2mqtt-${RELEASE} /opt/zigbee2mqtt
 | 
			
		||||
      rm -rf /opt/zigbee2mqtt/data
 | 
			
		||||
      mv /opt/z2m_backup/data /opt/zigbee2mqtt
 | 
			
		||||
      cd /opt/zigbee2mqtt 
 | 
			
		||||
      pnpm install --frozen-lockfile &>/dev/null
 | 
			
		||||
      pnpm build &>/dev/null
 | 
			
		||||
    msg_ok "Updated Zigbee2MQTT"
 | 
			
		||||
 | 
			
		||||
    msg_info "Starting Service"
 | 
			
		||||
    systemctl start zigbee2mqtt
 | 
			
		||||
      systemctl start zigbee2mqtt
 | 
			
		||||
    msg_ok "Started Service"
 | 
			
		||||
 | 
			
		||||
    msg_info "Cleaning up"
 | 
			
		||||
      rm -rf /opt/z2m_backup
 | 
			
		||||
      rm -rf /opt/${RELEASE}.zip
 | 
			
		||||
    msg_ok "Cleaned up"
 | 
			
		||||
    echo "${RELEASE}" >/opt/${APP}_version.txt
 | 
			
		||||
  else
 | 
			
		||||
    msg_ok "No update required. ${APP} is already at v${RELEASE}."
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,33 @@
 | 
			
		||||
"use client";
 | 
			
		||||
 | 
			
		||||
import React, { useEffect, useState } from "react";
 | 
			
		||||
import DatePicker from 'react-datepicker';
 | 
			
		||||
import 'react-datepicker/dist/react-datepicker.css';
 | 
			
		||||
import { string } from "zod";
 | 
			
		||||
import ApplicationChart from "../../components/ApplicationChart";
 | 
			
		||||
import ApplicationChart from "@/components/ApplicationChart";
 | 
			
		||||
import { Button } from "@/components/ui/button";
 | 
			
		||||
import { Calendar } from "@/components/ui/calendar";
 | 
			
		||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
 | 
			
		||||
import { Input } from "@/components/ui/input";
 | 
			
		||||
import {
 | 
			
		||||
  Popover,
 | 
			
		||||
  PopoverContent,
 | 
			
		||||
  PopoverTrigger,
 | 
			
		||||
} from "@/components/ui/popover";
 | 
			
		||||
import {
 | 
			
		||||
  Select,
 | 
			
		||||
  SelectContent,
 | 
			
		||||
  SelectItem,
 | 
			
		||||
  SelectTrigger,
 | 
			
		||||
  SelectValue,
 | 
			
		||||
} from "@/components/ui/select";
 | 
			
		||||
import {
 | 
			
		||||
  Table,
 | 
			
		||||
  TableBody,
 | 
			
		||||
  TableCell,
 | 
			
		||||
  TableHead,
 | 
			
		||||
  TableHeader,
 | 
			
		||||
  TableRow,
 | 
			
		||||
} from "@/components/ui/table";
 | 
			
		||||
import { format } from "date-fns";
 | 
			
		||||
import { Calendar as CalendarIcon } from "lucide-react";
 | 
			
		||||
import React, { useCallback, useEffect, useState } from "react";
 | 
			
		||||
 | 
			
		||||
interface DataModel {
 | 
			
		||||
  id: number;
 | 
			
		||||
@@ -35,27 +58,47 @@ const DataFetcher: React.FC = () => {
 | 
			
		||||
  const [startDate, setStartDate] = useState<Date | null>(null);
 | 
			
		||||
  const [endDate, setEndDate] = useState<Date | null>(null);
 | 
			
		||||
  const [sortConfig, setSortConfig] = useState<{ key: keyof DataModel | null, direction: 'ascending' | 'descending' }>({ key: 'id', direction: 'descending' });
 | 
			
		||||
  const [itemsPerPage, setItemsPerPage] = useState(5);
 | 
			
		||||
  const [itemsPerPage, setItemsPerPage] = useState(25);
 | 
			
		||||
  const [currentPage, setCurrentPage] = useState(1);
 | 
			
		||||
  const [showChart, setShowChart] = useState<boolean>(false);
 | 
			
		||||
  const [interval, setIntervalTime] = useState<number>(10); // Default interval 10 seconds
 | 
			
		||||
  const [reloadInterval, setReloadInterval] = useState<NodeJS.Timeout | null>(null);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    const fetchData = async () => {
 | 
			
		||||
      try {
 | 
			
		||||
        const response = await fetch("https://api.htl-braunau.at/data/json");
 | 
			
		||||
        if (!response.ok) throw new Error("Failed to fetch data: ${response.statusText}");
 | 
			
		||||
        const result: DataModel[] = await response.json();
 | 
			
		||||
        setData(result);
 | 
			
		||||
      } catch (err) {
 | 
			
		||||
        setError((err as Error).message);
 | 
			
		||||
      } finally {
 | 
			
		||||
        setLoading(false);
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    fetchData();
 | 
			
		||||
  const fetchData = useCallback(async () => {
 | 
			
		||||
    try {
 | 
			
		||||
      const response = await fetch("https://api.htl-braunau.at/data/json");
 | 
			
		||||
      if (!response.ok) throw new Error(`Failed to fetch data: ${response.statusText}`);
 | 
			
		||||
      const result: DataModel[] = await response.json();
 | 
			
		||||
      setData(result);
 | 
			
		||||
      setLoading(false);
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      setError((err as Error).message);
 | 
			
		||||
      setLoading(false);
 | 
			
		||||
    }
 | 
			
		||||
  }, []);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    fetchData();
 | 
			
		||||
    const storedInterval = localStorage.getItem('reloadInterval');
 | 
			
		||||
    if (storedInterval) {
 | 
			
		||||
      setIntervalTime(Number(storedInterval));
 | 
			
		||||
    }
 | 
			
		||||
  }, [fetchData]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    let intervalId: NodeJS.Timeout | null = null;
 | 
			
		||||
    
 | 
			
		||||
    if (interval > 0) {
 | 
			
		||||
      intervalId = setInterval(fetchData, Math.max(interval, 10) * 1000);
 | 
			
		||||
      localStorage.setItem('reloadInterval', interval.toString());
 | 
			
		||||
    } else {
 | 
			
		||||
      localStorage.removeItem('reloadInterval');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return () => {
 | 
			
		||||
      if (intervalId) clearInterval(intervalId);
 | 
			
		||||
    };
 | 
			
		||||
  }, [interval, fetchData]);
 | 
			
		||||
 | 
			
		||||
  const filteredData = data.filter(item => {
 | 
			
		||||
    const matchesSearchQuery = Object.values(item).some(value =>
 | 
			
		||||
@@ -110,139 +153,194 @@ const DataFetcher: React.FC = () => {
 | 
			
		||||
    return `${day}.${month}.${year} ${hours}:${minutes} ${timezoneOffset} GMT`;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleItemsPerPageChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
 | 
			
		||||
    setItemsPerPage(Number(event.target.value));
 | 
			
		||||
    setCurrentPage(1);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const paginatedData = sortedData.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage);
 | 
			
		||||
 | 
			
		||||
  if (loading) return <p>Loading...</p>;
 | 
			
		||||
  if (error) return <p>Error: {error}</p>;
 | 
			
		||||
  const statusCounts = data.reduce((acc, item) => {
 | 
			
		||||
    const status = item.status;
 | 
			
		||||
    acc[status] = (acc[status] || 0) + 1;
 | 
			
		||||
    return acc;
 | 
			
		||||
  }, {} as Record<string, number>);
 | 
			
		||||
 | 
			
		||||
  if (loading) return <div className="flex justify-center items-center h-screen">Loading...</div>;
 | 
			
		||||
  if (error) return <div className="flex justify-center items-center h-screen text-red-500">Error: {error}</div>;
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="p-6 mt-20">
 | 
			
		||||
      <h1 className="text-2xl font-bold mb-4 text-center">Created LXCs</h1>
 | 
			
		||||
      <div className="mb-4 flex space-x-4">
 | 
			
		||||
        <div>
 | 
			
		||||
          <input
 | 
			
		||||
            type="text"
 | 
			
		||||
            placeholder="Search..."
 | 
			
		||||
            value={searchQuery}
 | 
			
		||||
            onChange={e => setSearchQuery(e.target.value)}
 | 
			
		||||
            className="p-2 border"
 | 
			
		||||
          />
 | 
			
		||||
          <label className="text-sm text-gray-600 mt-1 block">Search by keyword</label>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div>
 | 
			
		||||
          <DatePicker
 | 
			
		||||
            selected={startDate}
 | 
			
		||||
            onChange={date => setStartDate(date)}
 | 
			
		||||
            selectsStart
 | 
			
		||||
            startDate={startDate}
 | 
			
		||||
            endDate={endDate}
 | 
			
		||||
            placeholderText="Start date"
 | 
			
		||||
            className="p-2 border"
 | 
			
		||||
          />
 | 
			
		||||
          <label className="text-sm text-gray-600 mt-1 block">Set a start date</label>
 | 
			
		||||
        </div>
 | 
			
		||||
    <div className="container mx-auto p-6 pt-20 space-y-6">
 | 
			
		||||
      <h1 className="text-3xl font-bold text-center">Created LXCs</h1>
 | 
			
		||||
      
 | 
			
		||||
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
 | 
			
		||||
        <Card>
 | 
			
		||||
          <CardHeader className="pb-2">
 | 
			
		||||
            <CardTitle className="text-sm font-medium">Search</CardTitle>
 | 
			
		||||
          </CardHeader>
 | 
			
		||||
          <CardContent>
 | 
			
		||||
            <Input
 | 
			
		||||
              placeholder="Search..."
 | 
			
		||||
              value={searchQuery}
 | 
			
		||||
              onChange={e => setSearchQuery(e.target.value)}
 | 
			
		||||
            />
 | 
			
		||||
          </CardContent>
 | 
			
		||||
        </Card>
 | 
			
		||||
 | 
			
		||||
        <div>
 | 
			
		||||
          <DatePicker
 | 
			
		||||
            selected={endDate}
 | 
			
		||||
            onChange={date => setEndDate(date)}
 | 
			
		||||
            selectsEnd
 | 
			
		||||
            startDate={startDate}
 | 
			
		||||
            endDate={endDate}
 | 
			
		||||
            placeholderText="End date"
 | 
			
		||||
            className="p-2 border"
 | 
			
		||||
          />
 | 
			
		||||
          <label className="text-sm text-gray-600 mt-1 block">Set a end date</label>
 | 
			
		||||
        </div>
 | 
			
		||||
        <Card>
 | 
			
		||||
          <CardHeader className="pb-2">
 | 
			
		||||
            <CardTitle className="text-sm font-medium">Start Date</CardTitle>
 | 
			
		||||
          </CardHeader>
 | 
			
		||||
          <CardContent>
 | 
			
		||||
            <Popover>
 | 
			
		||||
              <PopoverTrigger asChild>
 | 
			
		||||
                <Button variant="outline" className="w-full justify-start text-left font-normal">
 | 
			
		||||
                  <CalendarIcon className="mr-2 h-4 w-4" />
 | 
			
		||||
                  {startDate ? format(startDate, "PPP") : "Pick a date"}
 | 
			
		||||
                </Button>
 | 
			
		||||
              </PopoverTrigger>
 | 
			
		||||
              <PopoverContent className="w-auto p-0">
 | 
			
		||||
                <Calendar
 | 
			
		||||
                  mode="single"
 | 
			
		||||
                  selected={startDate || undefined}
 | 
			
		||||
                  onSelect={(date: Date | undefined) => setStartDate(date || null)}
 | 
			
		||||
                  initialFocus
 | 
			
		||||
                />
 | 
			
		||||
              </PopoverContent>
 | 
			
		||||
            </Popover>
 | 
			
		||||
          </CardContent>
 | 
			
		||||
        </Card>
 | 
			
		||||
 | 
			
		||||
        <Card>
 | 
			
		||||
          <CardHeader className="pb-2">
 | 
			
		||||
            <CardTitle className="text-sm font-medium">End Date</CardTitle>
 | 
			
		||||
          </CardHeader>
 | 
			
		||||
          <CardContent>
 | 
			
		||||
            <Popover>
 | 
			
		||||
              <PopoverTrigger asChild>
 | 
			
		||||
                <Button variant="outline" className="w-full justify-start text-left font-normal">
 | 
			
		||||
                  <CalendarIcon className="mr-2 h-4 w-4" />
 | 
			
		||||
                  {endDate ? format(endDate, "PPP") : "Pick a date"}
 | 
			
		||||
                </Button>
 | 
			
		||||
              </PopoverTrigger>
 | 
			
		||||
              <PopoverContent className="w-auto p-0">
 | 
			
		||||
                <Calendar
 | 
			
		||||
                  mode="single"
 | 
			
		||||
                  selected={endDate || undefined}
 | 
			
		||||
                  onSelect={(date: Date | undefined) => setEndDate(date || null)}
 | 
			
		||||
                  initialFocus
 | 
			
		||||
                />
 | 
			
		||||
              </PopoverContent>
 | 
			
		||||
            </Popover>
 | 
			
		||||
          </CardContent>
 | 
			
		||||
        </Card>
 | 
			
		||||
        
 | 
			
		||||
        <Card>
 | 
			
		||||
          <CardHeader className="pb-2">
 | 
			
		||||
            <CardTitle className="text-sm font-medium">Reload Interval</CardTitle>
 | 
			
		||||
          </CardHeader>
 | 
			
		||||
          <CardContent>
 | 
			
		||||
            <Input
 | 
			
		||||
              type="number"
 | 
			
		||||
              value={interval}
 | 
			
		||||
              onChange={e => setIntervalTime(Number(e.target.value))}
 | 
			
		||||
              placeholder="Interval (seconds)"
 | 
			
		||||
            />
 | 
			
		||||
          </CardContent>
 | 
			
		||||
        </Card>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <ApplicationChart data={filteredData} />
 | 
			
		||||
      <div className="mb-4 flex justify-between items-center">
 | 
			
		||||
        <p className="text-lg font-bold">{filteredData.length} results found</p>
 | 
			
		||||
        <select value={itemsPerPage} onChange={handleItemsPerPageChange} className="p-2 border">
 | 
			
		||||
          <option value={25}>25</option>
 | 
			
		||||
          <option value={50}>50</option>
 | 
			
		||||
          <option value={100}>100</option>
 | 
			
		||||
          <option value={200}>200</option>
 | 
			
		||||
        </select>
 | 
			
		||||
      </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('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('hn')}>Hostname</th>
 | 
			
		||||
                <th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('ssh')}>SSH</th>
 | 
			
		||||
                <th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('verbose')}>Verb</th>
 | 
			
		||||
                <th className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('tags')}>Tags</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('created_at')}>Created At</th>
 | 
			
		||||
              </tr>
 | 
			
		||||
            </thead>
 | 
			
		||||
            <tbody>
 | 
			
		||||
              {paginatedData.map((item, index) => (
 | 
			
		||||
                <tr key={index}>
 | 
			
		||||
                  <td className="px-4 py-2 border-b">
 | 
			
		||||
                    {item.status === "done" ? (
 | 
			
		||||
                      "✔️"
 | 
			
		||||
                    ) : item.status === "failed" ? (
 | 
			
		||||
                      "❌"
 | 
			
		||||
                    ) : item.status === "installing" ? (
 | 
			
		||||
                      "🔄"  
 | 
			
		||||
                    ) : (
 | 
			
		||||
                      item.status
 | 
			
		||||
                    )}
 | 
			
		||||
                  </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.hn}</td>
 | 
			
		||||
                  <td className="px-4 py-2 border-b">{item.ssh}</td>
 | 
			
		||||
                  <td className="px-4 py-2 border-b">{item.verbose}</td>
 | 
			
		||||
                  <td className="px-4 py-2 border-b">{item.tags.replace(/;/g, ' ')}</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">{formatDate(item.created_at)}</td>
 | 
			
		||||
                </tr>
 | 
			
		||||
              ))}
 | 
			
		||||
            </tbody>
 | 
			
		||||
          </table>
 | 
			
		||||
 | 
			
		||||
      <div className="flex justify-between items-center">
 | 
			
		||||
        <p className="text-lg font-medium">{filteredData.length} results found</p>
 | 
			
		||||
        <div className="flex gap-2 items-center">
 | 
			
		||||
          <span>🔄 Installing: {statusCounts.installing || 0}</span>
 | 
			
		||||
          <span>✔️ Completed: {statusCounts.done || 0}</span>
 | 
			
		||||
          <span>❌ Failed: {statusCounts.failed || 0}</span>
 | 
			
		||||
          <span>❓ Unknown: {statusCounts.unknown || 0}</span>
 | 
			
		||||
        </div>
 | 
			
		||||
        <Select value={itemsPerPage.toString()} onValueChange={(value) => setItemsPerPage(Number(value))}>
 | 
			
		||||
          <SelectTrigger className="w-[180px]">
 | 
			
		||||
            <SelectValue placeholder="Items per page" />
 | 
			
		||||
          </SelectTrigger>
 | 
			
		||||
          <SelectContent>
 | 
			
		||||
            {[25, 50, 100, 200].map(value => (
 | 
			
		||||
              <SelectItem key={value} value={value.toString()}>
 | 
			
		||||
                {value} items
 | 
			
		||||
              </SelectItem>
 | 
			
		||||
            ))}
 | 
			
		||||
          </SelectContent>
 | 
			
		||||
        </Select>
 | 
			
		||||
      </div>
 | 
			
		||||
      <div className="mt-4 flex justify-between items-center">
 | 
			
		||||
        <button
 | 
			
		||||
 | 
			
		||||
      <div className="rounded-md border">
 | 
			
		||||
        <Table>
 | 
			
		||||
          <TableHeader>
 | 
			
		||||
            <TableRow>
 | 
			
		||||
              <TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('status')}>Status</TableHead>
 | 
			
		||||
              <TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('nsapp')}>Application</TableHead>
 | 
			
		||||
              <TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('os_type')}>OS</TableHead>
 | 
			
		||||
              <TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('os_version')}>OS Version</TableHead>
 | 
			
		||||
              <TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('disk_size')}>Disk Size</TableHead>
 | 
			
		||||
              <TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('core_count')}>Core Count</TableHead>
 | 
			
		||||
              <TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('ram_size')}>RAM Size</TableHead>
 | 
			
		||||
              <TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('hn')}>Hostname</TableHead>
 | 
			
		||||
              <TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('ssh')}>SSH</TableHead>
 | 
			
		||||
              <TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('verbose')}>Verb</TableHead>
 | 
			
		||||
              <TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('tags')}>Tags</TableHead>
 | 
			
		||||
              <TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('method')}>Method</TableHead>
 | 
			
		||||
              <TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('pve_version')}>PVE Version</TableHead>
 | 
			
		||||
              <TableHead className="px-4 py-2 border-b cursor-pointer" onClick={() => requestSort('created_at')}>Created At</TableHead>
 | 
			
		||||
            </TableRow>
 | 
			
		||||
          </TableHeader>
 | 
			
		||||
          <TableBody>
 | 
			
		||||
            {paginatedData.map((item, index) => (
 | 
			
		||||
              <TableRow key={index}>
 | 
			
		||||
                <TableCell className="px-4 py-2 border-b">{item.status === "done" ? (
 | 
			
		||||
                  "✔️"
 | 
			
		||||
                ) : item.status === "failed" ? (
 | 
			
		||||
                  "❌"
 | 
			
		||||
                ) : item.status === "installing" ? (
 | 
			
		||||
                  "🔄"  
 | 
			
		||||
                ) : (
 | 
			
		||||
                  item.status
 | 
			
		||||
                )}</TableCell>
 | 
			
		||||
                <TableCell className="px-4 py-2 border-b">{item.nsapp}</TableCell>
 | 
			
		||||
                <TableCell className="px-4 py-2 border-b">{item.os_type}</TableCell>
 | 
			
		||||
                <TableCell className="px-4 py-2 border-b">{item.os_version}</TableCell>
 | 
			
		||||
                <TableCell className="px-4 py-2 border-b">{item.disk_size}</TableCell>
 | 
			
		||||
                <TableCell className="px-4 py-2 border-b">{item.core_count}</TableCell>
 | 
			
		||||
                <TableCell className="px-4 py-2 border-b">{item.ram_size}</TableCell>
 | 
			
		||||
                <TableCell className="px-4 py-2 border-b">{item.hn}</TableCell>
 | 
			
		||||
                <TableCell className="px-4 py-2 border-b">{item.ssh}</TableCell>
 | 
			
		||||
                <TableCell className="px-4 py-2 border-b">{item.verbose}</TableCell>
 | 
			
		||||
                <TableCell className="px-4 py-2 border-b">{item.tags.replace(/;/g, ' ')}</TableCell>
 | 
			
		||||
                <TableCell className="px-4 py-2 border-b">{item.method}</TableCell>
 | 
			
		||||
                <TableCell className="px-4 py-2 border-b">{item.pve_version}</TableCell>
 | 
			
		||||
                <TableCell className="px-4 py-2 border-b">{formatDate(item.created_at)}</TableCell>
 | 
			
		||||
              </TableRow>
 | 
			
		||||
            ))}
 | 
			
		||||
          </TableBody>
 | 
			
		||||
        </Table>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div className="flex items-center justify-center space-x-2">
 | 
			
		||||
        <Button
 | 
			
		||||
          variant="outline"
 | 
			
		||||
          onClick={() => setCurrentPage(prev => Math.max(prev - 1, 1))}
 | 
			
		||||
          disabled={currentPage === 1}
 | 
			
		||||
          className="p-2 border"
 | 
			
		||||
        >
 | 
			
		||||
          Previous
 | 
			
		||||
        </button>
 | 
			
		||||
        <span>Page {currentPage}</span>
 | 
			
		||||
        <button
 | 
			
		||||
        </Button>
 | 
			
		||||
        <span className="text-sm">
 | 
			
		||||
          Page {currentPage} of {Math.ceil(sortedData.length / itemsPerPage)}
 | 
			
		||||
        </span>
 | 
			
		||||
        <Button
 | 
			
		||||
          variant="outline"
 | 
			
		||||
          onClick={() => setCurrentPage(prev => (prev * itemsPerPage < sortedData.length ? prev + 1 : prev))}
 | 
			
		||||
          disabled={currentPage * itemsPerPage >= sortedData.length}
 | 
			
		||||
          className="p-2 border"
 | 
			
		||||
        >
 | 
			
		||||
          Next
 | 
			
		||||
        </button>
 | 
			
		||||
        </Button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default DataFetcher;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,132 +1,193 @@
 | 
			
		||||
"use client";
 | 
			
		||||
 | 
			
		||||
import { Button } from "@/components/ui/button";
 | 
			
		||||
import {
 | 
			
		||||
  Dialog,
 | 
			
		||||
  DialogContent,
 | 
			
		||||
  DialogHeader,
 | 
			
		||||
  DialogTitle,
 | 
			
		||||
} from "@/components/ui/dialog";
 | 
			
		||||
import {
 | 
			
		||||
  Table,
 | 
			
		||||
  TableBody,
 | 
			
		||||
  TableCell,
 | 
			
		||||
  TableHead,
 | 
			
		||||
  TableHeader,
 | 
			
		||||
  TableRow,
 | 
			
		||||
} from "@/components/ui/table";
 | 
			
		||||
import {
 | 
			
		||||
  Tooltip,
 | 
			
		||||
  TooltipContent,
 | 
			
		||||
  TooltipProvider,
 | 
			
		||||
  TooltipTrigger,
 | 
			
		||||
} from "@/components/ui/tooltip";
 | 
			
		||||
import { Chart as ChartJS, ArcElement, 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 { Chart as ChartJS, ArcElement, Tooltip, Legend } from "chart.js";
 | 
			
		||||
import ChartDataLabels from "chartjs-plugin-datalabels";
 | 
			
		||||
import Modal from "@/components/Modal"; 
 | 
			
		||||
 | 
			
		||||
ChartJS.register(ArcElement, Tooltip, Legend, ChartDataLabels);
 | 
			
		||||
ChartJS.register(ArcElement, ChartTooltip, Legend, ChartDataLabels);
 | 
			
		||||
 | 
			
		||||
interface ApplicationChartProps {
 | 
			
		||||
  data: { nsapp: string }[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ApplicationChart: React.FC<ApplicationChartProps> = ({ data }) => {
 | 
			
		||||
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(20);
 | 
			
		||||
  const [tableLimit, setTableLimit] = useState(ITEMS_PER_PAGE);
 | 
			
		||||
 | 
			
		||||
  const appCounts: Record<string, number> = {};
 | 
			
		||||
  data.forEach((item) => {
 | 
			
		||||
    appCounts[item.nsapp] = (appCounts[item.nsapp] || 0) + 1;
 | 
			
		||||
  });
 | 
			
		||||
  // Calculate application counts
 | 
			
		||||
  const appCounts = data.reduce((acc, item) => {
 | 
			
		||||
    acc[item.nsapp] = (acc[item.nsapp] || 0) + 1;
 | 
			
		||||
    return acc;
 | 
			
		||||
  }, {} as Record<string, number>);
 | 
			
		||||
 | 
			
		||||
  const sortedApps = Object.entries(appCounts).sort(([, a], [, b]) => b - a);
 | 
			
		||||
  const chartApps = sortedApps.slice(chartStartIndex, chartStartIndex + 20);
 | 
			
		||||
  const sortedApps = Object.entries(appCounts)
 | 
			
		||||
    .sort(([, a], [, b]) => b - a);
 | 
			
		||||
 | 
			
		||||
  const chartApps = sortedApps.slice(
 | 
			
		||||
    chartStartIndex,
 | 
			
		||||
    chartStartIndex + ITEMS_PER_PAGE
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const chartData = {
 | 
			
		||||
    labels: chartApps.map(([name]) => name),
 | 
			
		||||
    datasets: [
 | 
			
		||||
      {
 | 
			
		||||
        label: "Applications",
 | 
			
		||||
        data: chartApps.map(([, count]) => count),
 | 
			
		||||
        backgroundColor: [
 | 
			
		||||
          "#ff6384",
 | 
			
		||||
          "#36a2eb",
 | 
			
		||||
          "#ffce56",
 | 
			
		||||
          "#4bc0c0",
 | 
			
		||||
          "#9966ff",
 | 
			
		||||
          "#ff9f40",
 | 
			
		||||
        ],
 | 
			
		||||
        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 text-center">
 | 
			
		||||
      <button
 | 
			
		||||
        onClick={() => setIsChartOpen(true)}
 | 
			
		||||
        className="m-2 p-2 bg-blue-500 text-white rounded"
 | 
			
		||||
      >
 | 
			
		||||
        📊 Open Chart
 | 
			
		||||
      </button>
 | 
			
		||||
      <button
 | 
			
		||||
        onClick={() => setIsTableOpen(true)}
 | 
			
		||||
        className="m-2 p-2 bg-green-500 text-white rounded"
 | 
			
		||||
      >
 | 
			
		||||
        📋 Open Table
 | 
			
		||||
      </button>
 | 
			
		||||
    <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>
 | 
			
		||||
 | 
			
		||||
      <Modal isOpen={isChartOpen} onClose={() => setIsChartOpen(false)}>
 | 
			
		||||
        <h2 className="text-xl font-bold text-black dark:text-white mb-4">Top Applications (Chart)</h2>
 | 
			
		||||
        <div className="w-3/4 mx-auto">
 | 
			
		||||
          <Pie
 | 
			
		||||
            data={chartData}
 | 
			
		||||
            options={{
 | 
			
		||||
              plugins: {
 | 
			
		||||
                legend: { display: false },
 | 
			
		||||
                datalabels: {
 | 
			
		||||
                  color: "white",
 | 
			
		||||
                  font: { weight: "bold" },
 | 
			
		||||
                  formatter: (value, context) =>
 | 
			
		||||
                    context.chart.data.labels?.[context.dataIndex] || "",
 | 
			
		||||
                },
 | 
			
		||||
              },
 | 
			
		||||
            }}
 | 
			
		||||
          />
 | 
			
		||||
        </div>
 | 
			
		||||
        <div className="flex justify-center space-x-4 mt-4">
 | 
			
		||||
          <button
 | 
			
		||||
            onClick={() => setChartStartIndex(Math.max(0, chartStartIndex - 20))}
 | 
			
		||||
            disabled={chartStartIndex === 0}
 | 
			
		||||
            className="p-2 border rounded bg-blue-500 text-white"
 | 
			
		||||
          >
 | 
			
		||||
            ◀ Last 20
 | 
			
		||||
          </button>
 | 
			
		||||
          <button
 | 
			
		||||
            onClick={() => setChartStartIndex(chartStartIndex + 20)}
 | 
			
		||||
            disabled={chartStartIndex + 20 >= sortedApps.length}
 | 
			
		||||
            className="p-2 border rounded bg-blue-500 text-white"
 | 
			
		||||
          >
 | 
			
		||||
            Next 20 ▶
 | 
			
		||||
          </button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </Modal>
 | 
			
		||||
        <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>
 | 
			
		||||
 | 
			
		||||
      <Modal isOpen={isTableOpen} onClose={() => setIsTableOpen(false)}>
 | 
			
		||||
        <h2 className="text-xl font-bold text-black dark:text-white mb-4">Application Count Table</h2>
 | 
			
		||||
        <table className="w-full border-collapse border border-gray-600 dark:border-gray-500">
 | 
			
		||||
          <thead>
 | 
			
		||||
            <tr className="bg-gray-800 text-white">
 | 
			
		||||
              <th className="p-2 border">Application</th>
 | 
			
		||||
              <th className="p-2 border">Count</th>
 | 
			
		||||
            </tr>
 | 
			
		||||
          </thead>
 | 
			
		||||
          <tbody>
 | 
			
		||||
            {sortedApps.slice(0, tableLimit).map(([name, count]) => (
 | 
			
		||||
              <tr key={name} className="hover:bg-gray-200 dark:hover:bg-gray-700 text-black dark:text-white">
 | 
			
		||||
                <td className="p-2 border">{name}</td>
 | 
			
		||||
                <td className="p-2 border">{count}</td>
 | 
			
		||||
              </tr>
 | 
			
		||||
            ))}
 | 
			
		||||
          </tbody>
 | 
			
		||||
        </table>
 | 
			
		||||
      <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>
 | 
			
		||||
 | 
			
		||||
        {tableLimit < sortedApps.length && (
 | 
			
		||||
          <div className="text-center mt-4">
 | 
			
		||||
            <button
 | 
			
		||||
              onClick={() => setTableLimit(tableLimit + 20)}
 | 
			
		||||
              className="p-2 bg-green-500 text-white rounded"
 | 
			
		||||
      <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>
 | 
			
		||||
          </div>
 | 
			
		||||
        )}
 | 
			
		||||
      </Modal>
 | 
			
		||||
            </Button>
 | 
			
		||||
          )}
 | 
			
		||||
        </DialogContent>
 | 
			
		||||
      </Dialog>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default ApplicationChart;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										120
									
								
								frontend/src/components/ui/table.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								frontend/src/components/ui/table.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,120 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
 | 
			
		||||
const Table = React.forwardRef<
 | 
			
		||||
  HTMLTableElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLTableElement>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <div className="relative w-full overflow-auto">
 | 
			
		||||
    <table
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      className={cn("w-full caption-bottom text-sm", className)}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
))
 | 
			
		||||
Table.displayName = "Table"
 | 
			
		||||
 | 
			
		||||
const TableHeader = React.forwardRef<
 | 
			
		||||
  HTMLTableSectionElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLTableSectionElement>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
 | 
			
		||||
))
 | 
			
		||||
TableHeader.displayName = "TableHeader"
 | 
			
		||||
 | 
			
		||||
const TableBody = React.forwardRef<
 | 
			
		||||
  HTMLTableSectionElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLTableSectionElement>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <tbody
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("[&_tr:last-child]:border-0", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
TableBody.displayName = "TableBody"
 | 
			
		||||
 | 
			
		||||
const TableFooter = React.forwardRef<
 | 
			
		||||
  HTMLTableSectionElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLTableSectionElement>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <tfoot
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
TableFooter.displayName = "TableFooter"
 | 
			
		||||
 | 
			
		||||
const TableRow = React.forwardRef<
 | 
			
		||||
  HTMLTableRowElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLTableRowElement>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <tr
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
TableRow.displayName = "TableRow"
 | 
			
		||||
 | 
			
		||||
const TableHead = React.forwardRef<
 | 
			
		||||
  HTMLTableCellElement,
 | 
			
		||||
  React.ThHTMLAttributes<HTMLTableCellElement>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <th
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
TableHead.displayName = "TableHead"
 | 
			
		||||
 | 
			
		||||
const TableCell = React.forwardRef<
 | 
			
		||||
  HTMLTableCellElement,
 | 
			
		||||
  React.TdHTMLAttributes<HTMLTableCellElement>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <td
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn(
 | 
			
		||||
      "p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
 | 
			
		||||
      className
 | 
			
		||||
    )}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
TableCell.displayName = "TableCell"
 | 
			
		||||
 | 
			
		||||
const TableCaption = React.forwardRef<
 | 
			
		||||
  HTMLTableCaptionElement,
 | 
			
		||||
  React.HTMLAttributes<HTMLTableCaptionElement>
 | 
			
		||||
>(({ className, ...props }, ref) => (
 | 
			
		||||
  <caption
 | 
			
		||||
    ref={ref}
 | 
			
		||||
    className={cn("mt-4 text-sm text-muted-foreground", className)}
 | 
			
		||||
    {...props}
 | 
			
		||||
  />
 | 
			
		||||
))
 | 
			
		||||
TableCaption.displayName = "TableCaption"
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  Table,
 | 
			
		||||
  TableHeader,
 | 
			
		||||
  TableBody,
 | 
			
		||||
  TableFooter,
 | 
			
		||||
  TableHead,
 | 
			
		||||
  TableRow,
 | 
			
		||||
  TableCell,
 | 
			
		||||
  TableCaption,
 | 
			
		||||
}
 | 
			
		||||
@@ -1,83 +0,0 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
 | 
			
		||||
# Copyright (c) 2021-2025 community-scripts ORG
 | 
			
		||||
# Author: TheRealVira
 | 
			
		||||
# License: MIT
 | 
			
		||||
# Source: https://5e.tools/
 | 
			
		||||
 | 
			
		||||
# Import Functions und Setup
 | 
			
		||||
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
 | 
			
		||||
color
 | 
			
		||||
verb_ip6
 | 
			
		||||
catch_errors
 | 
			
		||||
setting_up_container
 | 
			
		||||
network_check
 | 
			
		||||
update_os
 | 
			
		||||
 | 
			
		||||
msg_info "Installing Dependencies"
 | 
			
		||||
$STD apt-get install -y \
 | 
			
		||||
  curl \
 | 
			
		||||
  mc \
 | 
			
		||||
  sudo \
 | 
			
		||||
  git \
 | 
			
		||||
  gpg \
 | 
			
		||||
  ca-certificates \
 | 
			
		||||
  apache2
 | 
			
		||||
msg_ok "Installed Dependencies"
 | 
			
		||||
 | 
			
		||||
msg_info "Setting up Node.js Repository"
 | 
			
		||||
mkdir -p /etc/apt/keyrings
 | 
			
		||||
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
 | 
			
		||||
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" >/etc/apt/sources.list.d/nodesource.list
 | 
			
		||||
msg_ok "Set up Node.js Repository"
 | 
			
		||||
 | 
			
		||||
msg_info "Installing Node.js"
 | 
			
		||||
$STD apt-get update
 | 
			
		||||
$STD apt-get install -y nodejs
 | 
			
		||||
msg_ok "Installed Node.js"
 | 
			
		||||
 | 
			
		||||
# Setup App
 | 
			
		||||
msg_info "Set up 5etools Base"
 | 
			
		||||
cd /opt
 | 
			
		||||
RELEASE=$(curl -s https://api.github.com/repos/5etools-mirror-3/5etools-src/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }')
 | 
			
		||||
wget -q "https://github.com/5etools-mirror-3/5etools-src/archive/refs/tags/${RELEASE}.zip"
 | 
			
		||||
unzip -q "${RELEASE}.zip"
 | 
			
		||||
mv "5etools-src-${RELEASE:1}" /opt/5etools
 | 
			
		||||
cd /opt/5etools
 | 
			
		||||
$STD npm install
 | 
			
		||||
$STD npm run build
 | 
			
		||||
echo "${RELEASE}" >"/opt/5etools_version.txt"
 | 
			
		||||
msg_ok "Set up 5etools Base"
 | 
			
		||||
 | 
			
		||||
msg_info "Set up 5etools Image"
 | 
			
		||||
cd /opt
 | 
			
		||||
IMG_RELEASE=$(curl -s https://api.github.com/repos/5etools-mirror-2/5etools-img/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }')
 | 
			
		||||
curl -sSL "https://github.com/5etools-mirror-2/5etools-img/archive/refs/tags/${IMG_RELEASE}.zip" > "${IMG_RELEASE}.zip"
 | 
			
		||||
unzip -q "${IMG_RELEASE}.zip"
 | 
			
		||||
mv "5etools-img-${IMG_RELEASE:1}" /opt/5etools/img
 | 
			
		||||
echo "${IMG_RELEASE}" >"/opt/5etools_IMG_version.txt"
 | 
			
		||||
msg_ok "Set up 5etools Image"
 | 
			
		||||
 | 
			
		||||
msg_info "Creating Service"
 | 
			
		||||
cat <<EOF >> /etc/apache2/apache2.conf
 | 
			
		||||
<Location /server-status>
 | 
			
		||||
    SetHandler server-status
 | 
			
		||||
    Order deny,allow
 | 
			
		||||
    Allow from all
 | 
			
		||||
</Location>
 | 
			
		||||
EOF
 | 
			
		||||
rm -rf /var/www/html
 | 
			
		||||
ln -s "/opt/5etools" /var/www/html
 | 
			
		||||
chown -R www-data: "/opt/5etools"
 | 
			
		||||
chmod -R 755 "/opt/5etools"
 | 
			
		||||
msg_ok "Created Service"
 | 
			
		||||
 | 
			
		||||
msg_info "Cleaning up"
 | 
			
		||||
rm -rf /opt/${IMG_RELEASE}.zip
 | 
			
		||||
rm -rf /opt/${RELEASE}.zip
 | 
			
		||||
$STD apt-get -y autoremove
 | 
			
		||||
$STD apt-get -y autoclean
 | 
			
		||||
msg_ok "Cleaned"
 | 
			
		||||
 | 
			
		||||
motd_ssh
 | 
			
		||||
customize
 | 
			
		||||
@@ -17,6 +17,7 @@ msg_info "Installing Dependencies"
 | 
			
		||||
$STD apk add \
 | 
			
		||||
  curl \
 | 
			
		||||
  mc \
 | 
			
		||||
  openssh \
 | 
			
		||||
  nginx \
 | 
			
		||||
  unzip
 | 
			
		||||
msg_ok "Installed Dependencies"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										97
									
								
								install/baikal-install.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								install/baikal-install.sh
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
 | 
			
		||||
# Copyright (c) 2021-2025 community-scripts ORG
 | 
			
		||||
# Author: bvdberg01
 | 
			
		||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
 | 
			
		||||
 | 
			
		||||
source /dev/stdin <<< "$FUNCTIONS_FILE_PATH"
 | 
			
		||||
color
 | 
			
		||||
verb_ip6
 | 
			
		||||
catch_errors
 | 
			
		||||
setting_up_container
 | 
			
		||||
network_check
 | 
			
		||||
update_os
 | 
			
		||||
 | 
			
		||||
msg_info "Installing Dependencies"
 | 
			
		||||
$STD apt-get install -y \
 | 
			
		||||
  curl \
 | 
			
		||||
  sudo \
 | 
			
		||||
  mc \
 | 
			
		||||
  postgresql \
 | 
			
		||||
  apache2 \
 | 
			
		||||
  libapache2-mod-php \
 | 
			
		||||
  php-{pgsql,dom}
 | 
			
		||||
msg_ok "Installed Dependencies"
 | 
			
		||||
 | 
			
		||||
msg_info "Setting up PostgreSQL"
 | 
			
		||||
DB_NAME=baikal
 | 
			
		||||
DB_USER=baikal
 | 
			
		||||
DB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | cut -c1-13)
 | 
			
		||||
$STD sudo -u postgres psql -c "CREATE ROLE $DB_USER WITH LOGIN PASSWORD '$DB_PASS';"
 | 
			
		||||
$STD sudo -u postgres psql -c "CREATE DATABASE $DB_NAME WITH OWNER $DB_USER TEMPLATE template0;"
 | 
			
		||||
{
 | 
			
		||||
echo "Baikal Credentials"
 | 
			
		||||
echo "Baikal Database User: $DB_USER"
 | 
			
		||||
echo "Baikal Database Password: $DB_PASS"
 | 
			
		||||
echo "Baikal Database Name: $DB_NAME"
 | 
			
		||||
} >> ~/baikal.creds
 | 
			
		||||
msg_ok "Set up PostgreSQL"
 | 
			
		||||
 | 
			
		||||
msg_info "Installing Baikal"
 | 
			
		||||
RELEASE=$(curl -s https://api.github.com/repos/sabre-io/Baikal/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }')
 | 
			
		||||
cd /opt
 | 
			
		||||
wget -q "https://github.com/sabre-io/baikal/releases/download/${RELEASE}/baikal-${RELEASE}.zip"
 | 
			
		||||
unzip -q "baikal-${RELEASE}.zip"
 | 
			
		||||
cat <<EOF >/opt/baikal/config/baikal.yaml
 | 
			
		||||
database:
 | 
			
		||||
    backend: pgsql
 | 
			
		||||
    pgsql_host: localhost
 | 
			
		||||
    pgsql_dbname: $DB_NAME
 | 
			
		||||
    pgsql_username: $DB_USER
 | 
			
		||||
    pgsql_password: $DB_PASS
 | 
			
		||||
EOF
 | 
			
		||||
chown -R www-data:www-data /opt/baikal/
 | 
			
		||||
chmod -R 755 /opt/baikal/
 | 
			
		||||
echo "${RELEASE}" >/opt/${APPLICATION}_version.txt
 | 
			
		||||
msg_ok "Installed Baikal"
 | 
			
		||||
 | 
			
		||||
msg_info "Creating Service"
 | 
			
		||||
cat <<EOF > /etc/apache2/sites-available/baikal.conf
 | 
			
		||||
<VirtualHost *:80>
 | 
			
		||||
    ServerName baikal
 | 
			
		||||
    DocumentRoot /opt/baikal/html
 | 
			
		||||
 | 
			
		||||
    RewriteEngine on
 | 
			
		||||
    RewriteRule /.well-known/carddav /dav.php [R=308,L]
 | 
			
		||||
    RewriteRule /.well-known/caldav  /dav.php [R=308,L]
 | 
			
		||||
    RewriteCond %{REQUEST_URI} ^/dav.php$ [NC]
 | 
			
		||||
    RewriteRule ^(.*)$ /dav.php/ [R=301,L]
 | 
			
		||||
        
 | 
			
		||||
    <Directory /opt/baikal/html>
 | 
			
		||||
        Options FollowSymLinks
 | 
			
		||||
        AllowOverride All
 | 
			
		||||
        Require all granted
 | 
			
		||||
    </Directory>
 | 
			
		||||
 | 
			
		||||
    <IfModule mod_expires.c>
 | 
			
		||||
        ExpiresActive Off
 | 
			
		||||
    </IfModule>
 | 
			
		||||
 | 
			
		||||
    ErrorLog /var/log/apache2/baikal_error.log
 | 
			
		||||
    CustomLog /var/log/apache2/baikal_access.log combined
 | 
			
		||||
</VirtualHost>
 | 
			
		||||
EOF
 | 
			
		||||
$STD a2ensite baikal
 | 
			
		||||
$STD a2enmod rewrite
 | 
			
		||||
$STD a2dissite 000-default.conf
 | 
			
		||||
$STD systemctl reload apache2
 | 
			
		||||
msg_ok "Created Service"
 | 
			
		||||
 | 
			
		||||
motd_ssh
 | 
			
		||||
customize
 | 
			
		||||
 | 
			
		||||
msg_info "Cleaning up"
 | 
			
		||||
rm -rf "/opt/baikal-${RELEASE}.zip"
 | 
			
		||||
$STD apt-get -y autoremove
 | 
			
		||||
$STD apt-get -y autoclean
 | 
			
		||||
msg_ok "Cleaned"
 | 
			
		||||
							
								
								
									
										140
									
								
								install/paymenter-install.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								install/paymenter-install.sh
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,140 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
 | 
			
		||||
# Copyright (c) 2021-2025 community-scripts ORG
 | 
			
		||||
# Author: Nícolas Pastorello (opastorello)
 | 
			
		||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
 | 
			
		||||
 | 
			
		||||
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
 | 
			
		||||
color
 | 
			
		||||
verb_ip6
 | 
			
		||||
catch_errors
 | 
			
		||||
setting_up_container
 | 
			
		||||
network_check
 | 
			
		||||
update_os
 | 
			
		||||
 | 
			
		||||
msg_info "Installing Dependencies"
 | 
			
		||||
$STD apt-get install -y \
 | 
			
		||||
    curl \
 | 
			
		||||
    sudo \
 | 
			
		||||
    mc \
 | 
			
		||||
    git \
 | 
			
		||||
    software-properties-common \
 | 
			
		||||
    apt-transport-https \
 | 
			
		||||
    ca-certificates \
 | 
			
		||||
    gnupg \
 | 
			
		||||
    php8.2 \
 | 
			
		||||
    php8.2-{common,cli,gd,mysql,mbstring,bcmath,xml,fpm,curl,zip} \
 | 
			
		||||
    mariadb-server \
 | 
			
		||||
    nginx \
 | 
			
		||||
    redis-server
 | 
			
		||||
$STD curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
 | 
			
		||||
msg_ok "Installed Dependencies"
 | 
			
		||||
 | 
			
		||||
msg_info "Installing Paymenter"
 | 
			
		||||
RELEASE=$(curl -s https://api.github.com/repos/paymenter/paymenter/releases/latest | grep '"tag_name"' | sed -E 's/.*"tag_name": "([^"]+)".*/\1/')
 | 
			
		||||
echo "${RELEASE}" >/opt/${APPLICATION}_version.txt
 | 
			
		||||
mkdir -p /opt/paymenter
 | 
			
		||||
cd /opt/paymenter
 | 
			
		||||
wget -q "https://github.com/paymenter/paymenter/releases/download/${RELEASE}/paymenter.tar.gz"
 | 
			
		||||
$STD tar -xzvf paymenter.tar.gz
 | 
			
		||||
chmod -R 755 storage/* bootstrap/cache/
 | 
			
		||||
msg_ok "Installed Paymenter"
 | 
			
		||||
 | 
			
		||||
msg_info "Setting up database"
 | 
			
		||||
DB_NAME=paymenter
 | 
			
		||||
DB_USER=paymenter
 | 
			
		||||
DB_PASS=$(openssl rand -base64 18 | tr -dc 'a-zA-Z0-9' | head -c13)
 | 
			
		||||
mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql mysql
 | 
			
		||||
mysql -u root -e "CREATE DATABASE $DB_NAME;"
 | 
			
		||||
mysql -u root -e "CREATE USER '$DB_USER'@'localhost' IDENTIFIED BY '$DB_PASS';"
 | 
			
		||||
mysql -u root -e "GRANT ALL PRIVILEGES ON $DB_NAME.* TO '$DB_USER'@'localhost' WITH GRANT OPTION;"
 | 
			
		||||
{
 | 
			
		||||
    echo "Paymenter Database Credentials"
 | 
			
		||||
    echo "Database: $DB_NAME"
 | 
			
		||||
    echo "Username: $DB_USER"
 | 
			
		||||
    echo "Password: $DB_PASS"
 | 
			
		||||
} >> ~/paymenter_db.creds
 | 
			
		||||
cp .env.example .env
 | 
			
		||||
$STD composer install --no-dev --optimize-autoloader --no-interaction
 | 
			
		||||
$STD php artisan key:generate --force
 | 
			
		||||
$STD php artisan storage:link
 | 
			
		||||
sed -i "s/^DB_DATABASE=.*/DB_DATABASE=${DB_NAME}/" .env
 | 
			
		||||
sed -i "s/^DB_USERNAME=.*/DB_USERNAME=${DB_USER}/" .env
 | 
			
		||||
sed -i "s/^DB_PASSWORD=.*/DB_PASSWORD=${DB_PASS}/" .env
 | 
			
		||||
$STD php artisan migrate --force --seed
 | 
			
		||||
msg_ok "Set up database"
 | 
			
		||||
 | 
			
		||||
msg_info "Creating Admin User"
 | 
			
		||||
$STD php artisan p:user:create <<EOF
 | 
			
		||||
admin@paymenter.org
 | 
			
		||||
paymenter
 | 
			
		||||
admin
 | 
			
		||||
paymenter
 | 
			
		||||
0
 | 
			
		||||
EOF
 | 
			
		||||
msg_ok "Created Admin User"
 | 
			
		||||
 | 
			
		||||
msg_info "Configuring Nginx"
 | 
			
		||||
cat <<EOF >/etc/nginx/sites-available/paymenter.conf
 | 
			
		||||
server {
 | 
			
		||||
    listen 80;
 | 
			
		||||
    listen [::]:80;
 | 
			
		||||
    server_name localhost;
 | 
			
		||||
    root /opt/paymenter/public;
 | 
			
		||||
 | 
			
		||||
    index index.php;
 | 
			
		||||
 | 
			
		||||
    location / {
 | 
			
		||||
        try_files \$uri \$uri/ /index.php?\$query_string;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    location ~ \.php\$ {
 | 
			
		||||
        include snippets/fastcgi-php.conf;
 | 
			
		||||
        fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
 | 
			
		||||
        fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
 | 
			
		||||
        include fastcgi_params;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    location ~ /\.ht {
 | 
			
		||||
        deny all;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
EOF
 | 
			
		||||
ln -s /etc/nginx/sites-available/paymenter.conf /etc/nginx/sites-enabled/
 | 
			
		||||
rm -f /etc/nginx/sites-enabled/default
 | 
			
		||||
$STD systemctl reload nginx
 | 
			
		||||
chown -R www-data:www-data /opt/paymenter/*
 | 
			
		||||
msg_ok "Configured Nginx"
 | 
			
		||||
 | 
			
		||||
msg_info "Setting up Cronjob"
 | 
			
		||||
echo "* * * * * php /opt/paymenter/artisan schedule:run >> /dev/null 2>&1" | crontab -
 | 
			
		||||
msg_ok "Setup Cronjob"
 | 
			
		||||
 | 
			
		||||
msg_info "Setting up Service"
 | 
			
		||||
cat <<EOF >/etc/systemd/system/paymenter.service
 | 
			
		||||
[Unit]
 | 
			
		||||
Description=Paymenter Queue Worker
 | 
			
		||||
 | 
			
		||||
[Service]
 | 
			
		||||
User=www-data
 | 
			
		||||
Group=www-data
 | 
			
		||||
Restart=always
 | 
			
		||||
ExecStart=/usr/bin/php /opt/paymenter/artisan queue:work
 | 
			
		||||
StartLimitInterval=180
 | 
			
		||||
StartLimitBurst=30
 | 
			
		||||
RestartSec=5s
 | 
			
		||||
 | 
			
		||||
[Install]
 | 
			
		||||
WantedBy=multi-user.target
 | 
			
		||||
EOF
 | 
			
		||||
$STD systemctl enable --now paymenter.service
 | 
			
		||||
msg_ok "Setup Service"
 | 
			
		||||
 | 
			
		||||
msg_info "Cleaning up"
 | 
			
		||||
rm -rf /opt/paymenter/paymenter.tar.gz
 | 
			
		||||
$STD apt-get -y autoremove
 | 
			
		||||
$STD apt-get -y autoclean
 | 
			
		||||
msg_ok "Cleaned"
 | 
			
		||||
 | 
			
		||||
motd_ssh
 | 
			
		||||
customize
 | 
			
		||||
@@ -1,75 +0,0 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
 | 
			
		||||
# Copyright (c) 2021-2025 community-scripts ORG
 | 
			
		||||
# Author: TheRealVira
 | 
			
		||||
# License: MIT
 | 
			
		||||
# Source: https://pf2etools.com/
 | 
			
		||||
 | 
			
		||||
# Import Functions und Setup
 | 
			
		||||
source /dev/stdin <<<"$FUNCTIONS_FILE_PATH"
 | 
			
		||||
color
 | 
			
		||||
verb_ip6
 | 
			
		||||
catch_errors
 | 
			
		||||
setting_up_container
 | 
			
		||||
network_check
 | 
			
		||||
update_os
 | 
			
		||||
 | 
			
		||||
msg_info "Installing Dependencies"
 | 
			
		||||
$STD apt-get install -y \
 | 
			
		||||
  curl \
 | 
			
		||||
  mc \
 | 
			
		||||
  sudo \
 | 
			
		||||
  apache2 \
 | 
			
		||||
  gpg \
 | 
			
		||||
  ca-certificates \
 | 
			
		||||
  git
 | 
			
		||||
msg_ok "Installed Dependencies"
 | 
			
		||||
 | 
			
		||||
msg_info "Setting up Node.js Repository"
 | 
			
		||||
mkdir -p /etc/apt/keyrings
 | 
			
		||||
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
 | 
			
		||||
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" >/etc/apt/sources.list.d/nodesource.list
 | 
			
		||||
msg_ok "Set up Node.js Repository"
 | 
			
		||||
 | 
			
		||||
msg_info "Installing Node.js"
 | 
			
		||||
$STD apt-get update
 | 
			
		||||
$STD apt-get install -y nodejs
 | 
			
		||||
msg_ok "Installed Node.js"
 | 
			
		||||
 | 
			
		||||
# Setup App
 | 
			
		||||
msg_info "Setup Pf2eTools"
 | 
			
		||||
cd /opt
 | 
			
		||||
RELEASE=$(curl -s https://api.github.com/repos/Pf2eToolsOrg/Pf2eTools/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }')
 | 
			
		||||
wget -q "https://github.com/Pf2eToolsOrg/Pf2eTools/archive/refs/tags/${RELEASE}.zip"
 | 
			
		||||
unzip -q "${RELEASE}.zip"
 | 
			
		||||
mv "Pf2eTools-${RELEASE:1}" /opt/Pf2eTools
 | 
			
		||||
cd /opt/Pf2eTools
 | 
			
		||||
$STD npm install
 | 
			
		||||
$STD npm run build
 | 
			
		||||
cd ~
 | 
			
		||||
echo "${RELEASE}" >/opt/Pf2eTools_version.txt
 | 
			
		||||
msg_ok "Set up Pf2eTools"
 | 
			
		||||
 | 
			
		||||
msg_info "Creating Service"
 | 
			
		||||
cat <<EOF >> /etc/apache2/apache2.conf
 | 
			
		||||
<Location /server-status>
 | 
			
		||||
    SetHandler server-status
 | 
			
		||||
    Order deny,allow
 | 
			
		||||
    Allow from all
 | 
			
		||||
</Location>
 | 
			
		||||
EOF
 | 
			
		||||
rm -rf /var/www/html
 | 
			
		||||
ln -s "/opt/Pf2eTools" /var/www/html
 | 
			
		||||
chown -R www-data: "/opt/Pf2eTools"
 | 
			
		||||
chmod -R 755 "/opt/Pf2eTools"
 | 
			
		||||
msg_ok "Created Service"
 | 
			
		||||
 | 
			
		||||
# Cleanup
 | 
			
		||||
msg_info "Cleaning up"
 | 
			
		||||
rm -rf /opt/${RELEASE}.zip
 | 
			
		||||
$STD apt-get -y autoremove
 | 
			
		||||
$STD apt-get -y autoclean
 | 
			
		||||
msg_ok "Cleaned"
 | 
			
		||||
 | 
			
		||||
motd_ssh
 | 
			
		||||
customize
 | 
			
		||||
@@ -28,7 +28,7 @@ rm -rf /usr/lib/python3.*/EXTERNALLY-MANAGED
 | 
			
		||||
msg_ok "Setup Python3"
 | 
			
		||||
 | 
			
		||||
msg_info "Installing Prometheus Proxmox VE Exporter"
 | 
			
		||||
python3 -m pip install --quiet --root-user-action=ignore prometheus-pve-exporter
 | 
			
		||||
python3 -m pip install --default-timeout=300 --quiet --root-user-action=ignore prometheus-pve-exporter
 | 
			
		||||
mkdir -p /opt/prometheus-pve-exporter
 | 
			
		||||
cat <<EOF > /opt/prometheus-pve-exporter/pve.yml
 | 
			
		||||
default:
 | 
			
		||||
 
 | 
			
		||||
@@ -22,8 +22,8 @@ msg_ok "Installed Dependencies"
 | 
			
		||||
RELEASE=$(curl -s https://api.github.com/repos/TriliumNext/Notes/releases/latest | grep "tag_name" | awk '{print substr($2, 2, length($2)-3) }')
 | 
			
		||||
 | 
			
		||||
msg_info "Installing TriliumNext"
 | 
			
		||||
wget -q https://github.com/TriliumNext/Notes/releases/download/${RELEASE}/TriliumNextNotes-${RELEASE}-server-linux-x64.tar.xz
 | 
			
		||||
tar -xf TriliumNextNotes-${RELEASE}-server-linux-x64.tar.xz
 | 
			
		||||
wget -q https://github.com/TriliumNext/Notes/releases/download/${RELEASE}/TriliumNextNotes-linux-x64-${RELEASE}.tar.xz
 | 
			
		||||
tar -xf TriliumNextNotes-linux-x64-${RELEASE}.tar.xz
 | 
			
		||||
mv trilium-linux-x64-server /opt/trilium
 | 
			
		||||
msg_ok "Installed TriliumNext"
 | 
			
		||||
 | 
			
		||||
@@ -53,5 +53,5 @@ customize
 | 
			
		||||
msg_info "Cleaning up"
 | 
			
		||||
$STD apt-get -y autoremove
 | 
			
		||||
$STD apt-get -y autoclean
 | 
			
		||||
rm -rf TriliumNextNotes-${RELEASE}-server-linux-x64.tar.xz
 | 
			
		||||
rm -rf TriliumNextNotes-linux-x64-${RELEASE}.tar.xz
 | 
			
		||||
msg_ok "Cleaned"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,34 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "5etools",
 | 
			
		||||
    "slug": "5etools",
 | 
			
		||||
    "categories": [
 | 
			
		||||
        24
 | 
			
		||||
    ],
 | 
			
		||||
    "date_created": "2025-01-02",
 | 
			
		||||
    "type": "ct",
 | 
			
		||||
    "updateable": true,
 | 
			
		||||
    "privileged": false,
 | 
			
		||||
    "interface_port": 80,
 | 
			
		||||
    "documentation": "https://wiki.tercept.net/en/5eTools",
 | 
			
		||||
    "website": "https://5e.tools/",
 | 
			
		||||
    "logo": "https://wiki.tercept.net/core-wiki-assets/5etoolslogocircle.png",
 | 
			
		||||
    "description": "5eTools is a website providing a suite of tools for 5th Edition Dungeons & Dragons players and Dungeon Masters.",
 | 
			
		||||
    "install_methods": [
 | 
			
		||||
        {
 | 
			
		||||
            "type": "default",
 | 
			
		||||
            "script": "ct/5etools.sh",
 | 
			
		||||
            "resources": {
 | 
			
		||||
                "cpu": 1,
 | 
			
		||||
                "ram": 512,
 | 
			
		||||
                "hdd": 13,
 | 
			
		||||
                "os": "debian",
 | 
			
		||||
                "version": "12"
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    ],
 | 
			
		||||
    "default_credentials": {
 | 
			
		||||
        "username": null,
 | 
			
		||||
        "password": null
 | 
			
		||||
    },
 | 
			
		||||
    "notes": []
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										34
									
								
								json/baikal.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								json/baikal.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "Baïkal",
 | 
			
		||||
    "slug": "baikal",
 | 
			
		||||
    "categories": [
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "date_created": "2025-01-31",
 | 
			
		||||
    "type": "ct",
 | 
			
		||||
    "updateable": true,
 | 
			
		||||
    "privileged": false,
 | 
			
		||||
    "interface_port": 80,
 | 
			
		||||
    "documentation": null,
 | 
			
		||||
    "website": "https://sabre.io/baikal/",
 | 
			
		||||
    "logo": "https://sabre.io/img/logo.png",
 | 
			
		||||
    "description": "Baïkal is a lightweight CalDAV+CardDAV server. It offers an extensive web interface with easy management of users, address books and calendars.",
 | 
			
		||||
    "install_methods": [
 | 
			
		||||
      {
 | 
			
		||||
        "type": "default",
 | 
			
		||||
        "script": "ct/baikal.sh",
 | 
			
		||||
        "resources": {
 | 
			
		||||
          "cpu": 1,
 | 
			
		||||
          "ram": 512,
 | 
			
		||||
          "hdd": 4,
 | 
			
		||||
          "os": "Debian",
 | 
			
		||||
          "version": "12"
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "default_credentials": {
 | 
			
		||||
      "username": "Admin",
 | 
			
		||||
      "password": null
 | 
			
		||||
    },
 | 
			
		||||
    "notes": []
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										34
									
								
								json/paymenter.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								json/paymenter.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "Paymenter",
 | 
			
		||||
  "slug": "paymenter",
 | 
			
		||||
  "categories": [
 | 
			
		||||
        21
 | 
			
		||||
  ],
 | 
			
		||||
  "date_created": "2025-01-28",
 | 
			
		||||
  "type": "ct",
 | 
			
		||||
  "updateable": true,
 | 
			
		||||
  "privileged": false,
 | 
			
		||||
  "interface_port": 80,
 | 
			
		||||
  "documentation": "https://paymenter.org/docs",
 | 
			
		||||
  "website": "https://paymenter.org/",
 | 
			
		||||
  "logo": "https://avatars.githubusercontent.com/u/115177786?s=200&v=4",
 | 
			
		||||
  "description": "Paymenter is an open source webshop solution for hosting companies. It's developed to provide an more easy way to manage your hosting company.",
 | 
			
		||||
  "install_methods": [
 | 
			
		||||
    {
 | 
			
		||||
      "type": "default",
 | 
			
		||||
      "script": "ct/paymenter.sh",
 | 
			
		||||
      "resources": {
 | 
			
		||||
        "cpu": 2,
 | 
			
		||||
        "ram": 1024,
 | 
			
		||||
        "hdd": 5,
 | 
			
		||||
        "os":"Debian",
 | 
			
		||||
        "version":"12"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "default_credentials": {
 | 
			
		||||
    "username": "admin@paymenter.org",
 | 
			
		||||
    "password": "paymenter"
 | 
			
		||||
  },
 | 
			
		||||
  "notes": []
 | 
			
		||||
}
 | 
			
		||||
@@ -1,34 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "Pf2eTools",
 | 
			
		||||
    "slug": "Pf2eTools",
 | 
			
		||||
    "categories": [
 | 
			
		||||
        24
 | 
			
		||||
    ],
 | 
			
		||||
    "date_created": "2025-01-02",
 | 
			
		||||
    "type": "ct",
 | 
			
		||||
    "updateable": true,
 | 
			
		||||
    "privileged": false,
 | 
			
		||||
    "interface_port": 80,
 | 
			
		||||
    "documentation": "https://github.com/Pf2eToolsOrg/Pf2eTools/wiki",
 | 
			
		||||
    "website": "https://pf2etools.com/",
 | 
			
		||||
    "logo": "https://raw.githubusercontent.com/Pf2eToolsOrg/Pf2eTools/refs/heads/dev/android-chrome-192x192.png",
 | 
			
		||||
    "description": "Pf2eTools is an open-source website aiming to provide tools and information for Pathfinder 2nd Edition players and gamemasters. It's built using basic web technologies to ensure wide compatibility, and utilises client-side caching for speed, efficiency, and offline access.",
 | 
			
		||||
    "install_methods": [
 | 
			
		||||
        {
 | 
			
		||||
            "type": "default",
 | 
			
		||||
            "script": "ct/pf2etools.sh",
 | 
			
		||||
            "resources": {
 | 
			
		||||
                "cpu": 1,
 | 
			
		||||
                "ram": 512,
 | 
			
		||||
                "hdd": 6,
 | 
			
		||||
                "os": "debian",
 | 
			
		||||
                "version": "12"
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    ],
 | 
			
		||||
    "default_credentials": {
 | 
			
		||||
        "username": null,
 | 
			
		||||
        "password": null
 | 
			
		||||
    },
 | 
			
		||||
    "notes": []
 | 
			
		||||
}
 | 
			
		||||
@@ -2,8 +2,7 @@
 | 
			
		||||
# Author: tteck (tteckster)
 | 
			
		||||
# Co-Author: MickLesk
 | 
			
		||||
# Co-Author: michelroegl-brunner
 | 
			
		||||
# License: MIT
 | 
			
		||||
# https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
 | 
			
		||||
# License: MIT | https://github.com/community-scripts/ProxmoxVE/raw/main/LICENSE
 | 
			
		||||
 | 
			
		||||
variables() {
 | 
			
		||||
  NSAPP=$(echo ${APP,,} | tr -d ' ') # This function sets the NSAPP variable by converting the value of the APP variable to lowercase and removing any spaces.
 | 
			
		||||
@@ -73,7 +72,6 @@ error_handler() {
 | 
			
		||||
  local exit_code="$?"
 | 
			
		||||
  local line_number="$1"
 | 
			
		||||
  local command="$2"
 | 
			
		||||
  post_update_to_api "failed"
 | 
			
		||||
  local error_message="${RD}[ERROR]${CL} in line ${RD}$line_number${CL}: exit code ${RD}$exit_code${CL}: while executing command ${YW}$command${CL}"
 | 
			
		||||
  echo -e "\n$error_message\n"
 | 
			
		||||
}
 | 
			
		||||
@@ -794,6 +792,9 @@ advanced_settings() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
post_to_api() {
 | 
			
		||||
  if [ "$DIAGNOSTICS" = "no" ]; then
 | 
			
		||||
    return 0
 | 
			
		||||
  fi
 | 
			
		||||
  local API_URL="http://api.community-scripts.org/upload"
 | 
			
		||||
  local pve_version="not found"
 | 
			
		||||
  pve_version=$(pveversion | awk -F'[/ ]' '{print $2}')
 | 
			
		||||
@@ -831,13 +832,17 @@ EOF
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
POST_UPDATE_DONE=false
 | 
			
		||||
 | 
			
		||||
post_update_to_api() {
 | 
			
		||||
    if [ "$DIAGNOSTICS" = "no" ]; then
 | 
			
		||||
        return 0
 | 
			
		||||
    fi
 | 
			
		||||
    if [ "$POST_UPDATE_DONE" = true ]; then
 | 
			
		||||
        return 0
 | 
			
		||||
    fi
 | 
			
		||||
	
 | 
			
		||||
    local API_URL="http://api.community-scripts.org/upload/updatestatus"    
 | 
			
		||||
    local status="${1:-}"
 | 
			
		||||
    local status="${1:-failed}"
 | 
			
		||||
    
 | 
			
		||||
    JSON_PAYLOAD=$(cat <<EOF
 | 
			
		||||
{
 | 
			
		||||
    "status": "$status",
 | 
			
		||||
@@ -846,17 +851,18 @@ post_update_to_api() {
 | 
			
		||||
EOF
 | 
			
		||||
)
 | 
			
		||||
   
 | 
			
		||||
   RESPONSE=$(curl -s -o response.txt -w "%{http_code}" -L -X POST "$API_URL" --post301 --post302 \
 | 
			
		||||
        -H "Content-Type: application/json" \
 | 
			
		||||
        -d "$JSON_PAYLOAD")
 | 
			
		||||
  RESPONSE=$(curl -s -o response.txt -w "%{http_code}" -L -X POST "$API_URL" --post301 --post302 \
 | 
			
		||||
      -H "Content-Type: application/json" \
 | 
			
		||||
      -d "$JSON_PAYLOAD")
 | 
			
		||||
 | 
			
		||||
    if [ "$RESPONSE" -ne 201 ] && [ "$RESPONSE" -ne 302 ]; then
 | 
			
		||||
        msg_error "API UPDATE request failed with HTTP code $RESPONSE"
 | 
			
		||||
    fi
 | 
			
		||||
  if [ "$RESPONSE" -ne 201 ] && [ "$RESPONSE" -ne 302 ]; then
 | 
			
		||||
    msg_error "API UPDATE request failed with HTTP code $RESPONSE"
 | 
			
		||||
  fi
 | 
			
		||||
 | 
			
		||||
   POST_UPDATE_DONE=true
 | 
			
		||||
  POST_UPDATE_DONE=true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
diagnostics_check(){
 | 
			
		||||
  if ! [ -d "/usr/local/community-scripts" ]; then
 | 
			
		||||
      mkdir -p /usr/local/community-scripts
 | 
			
		||||
@@ -1232,13 +1238,11 @@ EOF
 | 
			
		||||
  if [[ -f /etc/systemd/system/ping-instances.service ]]; then
 | 
			
		||||
    systemctl start ping-instances.service
 | 
			
		||||
  fi
 | 
			
		||||
 | 
			
		||||
  if [[ $DIAGNOSTICS == "yes" ]]; then
 | 
			
		||||
    post_update_to_api
 | 
			
		||||
  fi
 | 
			
		||||
  
 | 
			
		||||
  post_update_to_api "done"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
trap 'post_update_to_api "done"' EXIT
 | 
			
		||||
trap 'post_update_to_api "failed"' EXIT
 | 
			
		||||
trap 'post_update_to_api "failed"' SIGINT 
 | 
			
		||||
trap 'post_update_to_api "failed"' SIGTERM
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user