mirror of
https://github.com/fosrl/pangolin.git
synced 2026-07-06 12:19:50 +00:00
Merge branch 'main' into dev
This commit is contained in:
8
.github/workflows/cicd.yml
vendored
8
.github/workflows/cicd.yml
vendored
@@ -77,7 +77,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||||
with:
|
with:
|
||||||
registry: docker.io
|
registry: docker.io
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
@@ -149,7 +149,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||||
with:
|
with:
|
||||||
registry: docker.io
|
registry: docker.io
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
@@ -204,7 +204,7 @@ jobs:
|
|||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||||
with:
|
with:
|
||||||
registry: docker.io
|
registry: docker.io
|
||||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
@@ -407,7 +407,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry (for cosign)
|
- name: Login to GitHub Container Registry (for cosign)
|
||||||
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
|
|||||||
2
.github/workflows/linting.yml
vendored
2
.github/workflows/linting.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Set up Node.js
|
- name: Set up Node.js
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version: '24'
|
node-version: '24'
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/mirror.yaml
vendored
2
.github/workflows/mirror.yaml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
skopeo --version
|
skopeo --version
|
||||||
|
|
||||||
- name: Install cosign
|
- name: Install cosign
|
||||||
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
|
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
|
||||||
|
|
||||||
- name: Input check
|
- name: Input check
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
2
.github/workflows/stale-bot.yml
vendored
2
.github/workflows/stale-bot.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
|||||||
stale:
|
stale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
|
- uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
|
||||||
with:
|
with:
|
||||||
days-before-stale: 14
|
days-before-stale: 14
|
||||||
days-before-close: 14
|
days-before-close: 14
|
||||||
|
|||||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
|
||||||
- name: Install Node
|
- name: Install Node
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version: '24'
|
node-version: '24'
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ server:
|
|||||||
methods: ["GET", "POST", "PUT", "DELETE", "PATCH"]
|
methods: ["GET", "POST", "PUT", "DELETE", "PATCH"]
|
||||||
allowed_headers: ["X-CSRF-Token", "Content-Type"]
|
allowed_headers: ["X-CSRF-Token", "Content-Type"]
|
||||||
credentials: false
|
credentials: false
|
||||||
{{if .EnableGeoblocking}}maxmind_db_path: "./config/GeoLite2-Country.mmdb"{{end}}
|
{{if .EnableMaxMind}}maxmind_db_path: "./config/GeoLite2-Country.mmdb"{{end}}
|
||||||
|
{{if .EnableMaxMind}}maxmind_asn_path: "./config/GeoLite2-ASN.mmdb"{{end}}
|
||||||
{{if .EnableEmail}}
|
{{if .EnableEmail}}
|
||||||
email:
|
email:
|
||||||
smtp_host: "{{.EmailSMTPHost}}"
|
smtp_host: "{{.EmailSMTPHost}}"
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ type Config struct {
|
|||||||
InstallGerbil bool
|
InstallGerbil bool
|
||||||
TraefikBouncerKey string
|
TraefikBouncerKey string
|
||||||
DoCrowdsecInstall bool
|
DoCrowdsecInstall bool
|
||||||
EnableGeoblocking bool
|
EnableMaxMind bool
|
||||||
Secret string
|
Secret string
|
||||||
IsEnterprise bool
|
IsEnterprise bool
|
||||||
}
|
}
|
||||||
@@ -123,11 +123,11 @@ func main() {
|
|||||||
|
|
||||||
fmt.Println("\nConfiguration files created successfully!")
|
fmt.Println("\nConfiguration files created successfully!")
|
||||||
|
|
||||||
// Download MaxMind database if requested
|
// Download MaxMind Country / ASN database if requested
|
||||||
if config.EnableGeoblocking {
|
if config.EnableMaxMind {
|
||||||
fmt.Println("\n=== Downloading MaxMind Database ===")
|
fmt.Println("\n=== Downloading MaxMind Country and ASN Databases ===")
|
||||||
if err := downloadMaxMindDatabase(); err != nil {
|
if err := downloadMaxMindDatabase(); err != nil {
|
||||||
fmt.Printf("Error downloading MaxMind database: %v\n", err)
|
fmt.Printf("Error downloading MaxMind databases: %v\n", err)
|
||||||
fmt.Println("You can download it manually later if needed.")
|
fmt.Println("You can download it manually later if needed.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,15 +188,15 @@ func main() {
|
|||||||
fmt.Println("\n=== MaxMind Database Update ===")
|
fmt.Println("\n=== MaxMind Database Update ===")
|
||||||
if _, err := os.Stat("config/GeoLite2-Country.mmdb"); err == nil {
|
if _, err := os.Stat("config/GeoLite2-Country.mmdb"); err == nil {
|
||||||
fmt.Println("MaxMind GeoLite2 Country database found.")
|
fmt.Println("MaxMind GeoLite2 Country database found.")
|
||||||
if readBool("Would you like to update the MaxMind database to the latest version?", false) {
|
if readBool("Would you like to update the MaxMind databases (Country and ASN) to the latest version?", false) {
|
||||||
if err := downloadMaxMindDatabase(); err != nil {
|
if err := downloadMaxMindDatabase(); err != nil {
|
||||||
fmt.Printf("Error updating MaxMind database: %v\n", err)
|
fmt.Printf("Error updating MaxMind database: %v\n", err)
|
||||||
fmt.Println("You can try updating it manually later if needed.")
|
fmt.Println("You can try updating it manually later if needed.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("MaxMind GeoLite2 Country database not found.")
|
fmt.Println("MaxMind GeoLite2 Country and ASN databases not found.")
|
||||||
if readBool("Would you like to download the MaxMind GeoLite2 database for geoblocking functionality?", false) {
|
if readBool("Would you like to download the MaxMind GeoLite2 databases for blocking functionality?", false) {
|
||||||
if err := downloadMaxMindDatabase(); err != nil {
|
if err := downloadMaxMindDatabase(); err != nil {
|
||||||
fmt.Printf("Error downloading MaxMind database: %v\n", err)
|
fmt.Printf("Error downloading MaxMind database: %v\n", err)
|
||||||
fmt.Println("You can try downloading it manually later if needed.")
|
fmt.Println("You can try downloading it manually later if needed.")
|
||||||
@@ -204,8 +204,10 @@ func main() {
|
|||||||
// Now you need to update your config file accordingly to enable geoblocking
|
// Now you need to update your config file accordingly to enable geoblocking
|
||||||
fmt.Print("Please remember to update your config/config.yml file to enable geoblocking! \n\n")
|
fmt.Print("Please remember to update your config/config.yml file to enable geoblocking! \n\n")
|
||||||
// add maxmind_db_path: "./config/GeoLite2-Country.mmdb" under server
|
// add maxmind_db_path: "./config/GeoLite2-Country.mmdb" under server
|
||||||
fmt.Println("Add the following line under the 'server' section:")
|
// add maxmind_asn_path: "./config/GeoLite2-ASN.mmdb" under server
|
||||||
|
fmt.Println("Add the following lines under the 'server' section:")
|
||||||
fmt.Println(" maxmind_db_path: \"./config/GeoLite2-Country.mmdb\"")
|
fmt.Println(" maxmind_db_path: \"./config/GeoLite2-Country.mmdb\"")
|
||||||
|
fmt.Println(" maxmind_asn_path: \"./config/GeoLite2-ASN.mmdb\"")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -527,7 +529,7 @@ func collectUserInput() Config {
|
|||||||
fmt.Println("\n=== Advanced Configuration ===")
|
fmt.Println("\n=== Advanced Configuration ===")
|
||||||
|
|
||||||
config.EnableIPv6 = readBool("Is your server IPv6 capable?", true)
|
config.EnableIPv6 = readBool("Is your server IPv6 capable?", true)
|
||||||
config.EnableGeoblocking = readBool("Do you want to download the MaxMind GeoLite2 database for geoblocking functionality?", true)
|
config.EnableMaxMind = readBool("Do you want to download the MaxMind GeoLite2 Country and ADN databases for blocking functionality?", true)
|
||||||
|
|
||||||
if config.DashboardDomain == "" {
|
if config.DashboardDomain == "" {
|
||||||
fmt.Println("Error: Dashboard Domain name is required")
|
fmt.Println("Error: Dashboard Domain name is required")
|
||||||
@@ -780,29 +782,42 @@ func checkPortsAvailable(port int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func downloadMaxMindDatabase() error {
|
func downloadMaxMindDatabase() error {
|
||||||
fmt.Println("Downloading MaxMind GeoLite2 Country database...")
|
fmt.Println("Downloading MaxMind GeoLite2 Country and ASN databases...")
|
||||||
|
|
||||||
// Download the GeoLite2 Country database
|
// Download the GeoLite2 Country databases
|
||||||
if err := run("curl", "-L", "-o", "GeoLite2-Country.tar.gz",
|
if err := run("curl", "-L", "-o", "GeoLite2-Country.tar.gz",
|
||||||
"https://github.com/GitSquared/node-geolite2-redist/raw/refs/heads/master/redist/GeoLite2-Country.tar.gz"); err != nil {
|
"https://github.com/GitSquared/node-geolite2-redist/raw/refs/heads/master/redist/GeoLite2-Country.tar.gz"); err != nil {
|
||||||
return fmt.Errorf("failed to download GeoLite2 database: %v", err)
|
return fmt.Errorf("failed to download GeoLite2 Country database: %v", err)
|
||||||
|
}
|
||||||
|
if err := run("curl", "-L", "-o", "GeoLite2-ASN.tar.gz",
|
||||||
|
"https://github.com/GitSquared/node-geolite2-redist/raw/refs/heads/master/redist/GeoLite2-ASN.tar.gz"); err != nil {
|
||||||
|
return fmt.Errorf("failed to download GeoLite2 ASN database: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract the database
|
// Extract the Country database
|
||||||
if err := run("tar", "-xzf", "GeoLite2-Country.tar.gz"); err != nil {
|
if err := run("tar", "-xzf", "GeoLite2-Country.tar.gz"); err != nil {
|
||||||
return fmt.Errorf("failed to extract GeoLite2 database: %v", err)
|
return fmt.Errorf("failed to extract GeoLite2 Country database: %v", err)
|
||||||
|
}
|
||||||
|
if err := run("tar", "-xzf", "GeoLite2-ASN.tar.gz"); err != nil {
|
||||||
|
return fmt.Errorf("failed to extract GeoLite2 ASN database: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the .mmdb file and move it to the config directory
|
// Find the .mmdb file and move it to the config directory
|
||||||
if err := run("bash", "-c", "mv GeoLite2-Country_*/GeoLite2-Country.mmdb config/"); err != nil {
|
if err := run("bash", "-c", "mv GeoLite2-Country_*/GeoLite2-Country.mmdb config/"); err != nil {
|
||||||
return fmt.Errorf("failed to move GeoLite2 database to config directory: %v", err)
|
return fmt.Errorf("failed to move GeoLite2 Country database to config directory: %v", err)
|
||||||
|
}
|
||||||
|
if err := run("bash", "-c", "mv GeoLite2-ASN_*/GeoLite2-ASN.mmdb config/"); err != nil {
|
||||||
|
return fmt.Errorf("failed to move GeoLite2 ASN database to config directory: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up the downloaded files
|
// Clean up the downloaded files
|
||||||
if err := run("rm", "-rf", "GeoLite2-Country.tar.gz", "GeoLite2-Country_*"); err != nil {
|
if err := run("sh", "-c", "rm -rf GeoLite2-Country.tar.gz GeoLite2-Country_*"); err != nil {
|
||||||
fmt.Printf("Warning: failed to clean up temporary files: %v\n", err)
|
fmt.Printf("Warning: failed to clean up temporary country files: %v\n", err)
|
||||||
|
}
|
||||||
|
if err := run("sh", "-c", "rm -rf GeoLite2-ASN.tar.gz GeoLite2-ASN_*"); err != nil {
|
||||||
|
fmt.Printf("Warning: failed to clean up temporary ASN files: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("MaxMind GeoLite2 Country database downloaded successfully!")
|
fmt.Println("MaxMind GeoLite2 Country and ASN database downloaded successfully!")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1987,7 +1987,7 @@
|
|||||||
"sshSudoModeCommandsDescription": "User can run only the specified commands with sudo.",
|
"sshSudoModeCommandsDescription": "User can run only the specified commands with sudo.",
|
||||||
"sshSudo": "Allow sudo",
|
"sshSudo": "Allow sudo",
|
||||||
"sshSudoCommands": "Sudo Commands",
|
"sshSudoCommands": "Sudo Commands",
|
||||||
"sshSudoCommandsDescription": "Comma separated list of commands the user is allowed to run with sudo.",
|
"sshSudoCommandsDescription": "Comma separated list of commands the user is allowed to run with sudo. Absolute paths must be used.",
|
||||||
"sshCreateHomeDir": "Create Home Directory",
|
"sshCreateHomeDir": "Create Home Directory",
|
||||||
"sshUnixGroups": "Unix Groups",
|
"sshUnixGroups": "Unix Groups",
|
||||||
"sshUnixGroupsDescription": "Comma separated Unix groups to add the user to on the target host.",
|
"sshUnixGroupsDescription": "Comma separated Unix groups to add the user to on the target host.",
|
||||||
|
|||||||
505
package-lock.json
generated
505
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
21
package.json
21
package.json
@@ -64,9 +64,9 @@
|
|||||||
"@react-email/render": "2.0.8",
|
"@react-email/render": "2.0.8",
|
||||||
"@react-email/tailwind": "2.0.7",
|
"@react-email/tailwind": "2.0.7",
|
||||||
"@simplewebauthn/browser": "13.3.0",
|
"@simplewebauthn/browser": "13.3.0",
|
||||||
"@simplewebauthn/server": "13.3.0",
|
"@simplewebauthn/server": "13.3.1",
|
||||||
"@tailwindcss/forms": "0.5.11",
|
"@tailwindcss/forms": "0.5.11",
|
||||||
"@tanstack/react-query": "5.100.10",
|
"@tanstack/react-query": "5.100.14",
|
||||||
"@tanstack/react-table": "8.21.3",
|
"@tanstack/react-table": "8.21.3",
|
||||||
"arctic": "3.7.0",
|
"arctic": "3.7.0",
|
||||||
"axios": "1.16.1",
|
"axios": "1.16.1",
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
"next-themes": "0.4.6",
|
"next-themes": "0.4.6",
|
||||||
"nextjs-toploader": "3.9.17",
|
"nextjs-toploader": "3.9.17",
|
||||||
"node-cache": "5.1.2",
|
"node-cache": "5.1.2",
|
||||||
"nodemailer": "8.0.7",
|
"nodemailer": "8.0.9",
|
||||||
"oslo": "1.2.1",
|
"oslo": "1.2.1",
|
||||||
"pg": "8.20.0",
|
"pg": "8.20.0",
|
||||||
"posthog-node": "5.34.1",
|
"posthog-node": "5.34.1",
|
||||||
@@ -111,8 +111,7 @@
|
|||||||
"react-icons": "5.6.0",
|
"react-icons": "5.6.0",
|
||||||
"recharts": "3.8.1",
|
"recharts": "3.8.1",
|
||||||
"reodotdev": "1.1.0",
|
"reodotdev": "1.1.0",
|
||||||
"resend": "6.12.3",
|
"semver": "7.8.1",
|
||||||
"semver": "7.8.0",
|
|
||||||
"sshpk": "1.18.0",
|
"sshpk": "1.18.0",
|
||||||
"stripe": "20.4.1",
|
"stripe": "20.4.1",
|
||||||
"swagger-ui-express": "5.0.1",
|
"swagger-ui-express": "5.0.1",
|
||||||
@@ -132,9 +131,9 @@
|
|||||||
"zod-validation-error": "5.0.0"
|
"zod-validation-error": "5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@dotenvx/dotenvx": "1.66.0",
|
"@dotenvx/dotenvx": "1.69.1",
|
||||||
"@esbuild-plugins/tsconfig-paths": "0.1.2",
|
"@esbuild-plugins/tsconfig-paths": "0.1.2",
|
||||||
"@react-email/ui": "^6.1.4",
|
"@react-email/ui": "^6.5.0",
|
||||||
"@tailwindcss/postcss": "4.3.0",
|
"@tailwindcss/postcss": "4.3.0",
|
||||||
"@tanstack/react-query-devtools": "5.100.10",
|
"@tanstack/react-query-devtools": "5.100.10",
|
||||||
"@types/better-sqlite3": "7.6.13",
|
"@types/better-sqlite3": "7.6.13",
|
||||||
@@ -147,7 +146,7 @@
|
|||||||
"@types/jmespath": "0.15.2",
|
"@types/jmespath": "0.15.2",
|
||||||
"@types/js-yaml": "4.0.9",
|
"@types/js-yaml": "4.0.9",
|
||||||
"@types/jsonwebtoken": "9.0.10",
|
"@types/jsonwebtoken": "9.0.10",
|
||||||
"@types/node": "25.8.0",
|
"@types/node": "25.9.1",
|
||||||
"@types/nodemailer": "8.0.0",
|
"@types/nodemailer": "8.0.0",
|
||||||
"@types/nprogress": "0.2.3",
|
"@types/nprogress": "0.2.3",
|
||||||
"@types/pg": "8.20.0",
|
"@types/pg": "8.20.0",
|
||||||
@@ -163,16 +162,16 @@
|
|||||||
"drizzle-kit": "0.31.10",
|
"drizzle-kit": "0.31.10",
|
||||||
"esbuild": "0.28.0",
|
"esbuild": "0.28.0",
|
||||||
"esbuild-node-externals": "1.22.0",
|
"esbuild-node-externals": "1.22.0",
|
||||||
"eslint": "10.3.0",
|
"eslint": "10.4.0",
|
||||||
"eslint-config-next": "16.2.6",
|
"eslint-config-next": "16.2.6",
|
||||||
"postcss": "8.5.14",
|
"postcss": "8.5.14",
|
||||||
"prettier": "3.8.3",
|
"prettier": "3.8.3",
|
||||||
"react-email": "6.1.4",
|
"react-email": "6.5.0",
|
||||||
"tailwindcss": "4.3.0",
|
"tailwindcss": "4.3.0",
|
||||||
"tsc-alias": "1.8.17",
|
"tsc-alias": "1.8.17",
|
||||||
"tsx": "4.22.0",
|
"tsx": "4.22.0",
|
||||||
"typescript": "6.0.3",
|
"typescript": "6.0.3",
|
||||||
"typescript-eslint": "8.59.3"
|
"typescript-eslint": "8.60.0"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"esbuild": "0.28.0",
|
"esbuild": "0.28.0",
|
||||||
|
|||||||
@@ -221,10 +221,18 @@ async function handleResource(
|
|||||||
)
|
)
|
||||||
.where(eq(targets.resourceId, resource.resourceId));
|
.where(eq(targets.resourceId, resource.resourceId));
|
||||||
|
|
||||||
|
const monitoredTargets = otherTargets.filter(
|
||||||
|
(t) => t.hcHealth !== "unknown"
|
||||||
|
);
|
||||||
|
|
||||||
let health = "healthy";
|
let health = "healthy";
|
||||||
const allUnknown = otherTargets.every((t) => t.hcHealth === "unknown");
|
const allUnknown = monitoredTargets.length === 0;
|
||||||
const allHealthy = otherTargets.every((t) => t.hcHealth === "healthy");
|
const allHealthy = monitoredTargets.every(
|
||||||
const allUnhealthy = otherTargets.every((t) => t.hcHealth === "unhealthy");
|
(t) => t.hcHealth === "healthy"
|
||||||
|
);
|
||||||
|
const allUnhealthy = monitoredTargets.every(
|
||||||
|
(t) => t.hcHealth === "unhealthy"
|
||||||
|
);
|
||||||
|
|
||||||
if (allUnknown) {
|
if (allUnknown) {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export const RuleSchema = z
|
|||||||
.object({
|
.object({
|
||||||
action: z.enum(["allow", "deny", "pass"]),
|
action: z.enum(["allow", "deny", "pass"]),
|
||||||
match: z.enum(["cidr", "path", "ip", "country", "asn", "region"]),
|
match: z.enum(["cidr", "path", "ip", "country", "asn", "region"]),
|
||||||
value: z.string(),
|
value: z.coerce.string(),
|
||||||
priority: z.int().optional()
|
priority: z.int().optional()
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
@@ -340,7 +340,8 @@ export const ResourceSchema = z
|
|||||||
if (parts.includes("*", 1)) return false; // no further wildcards
|
if (parts.includes("*", 1)) return false; // no further wildcards
|
||||||
if (parts.length < 3) return false; // need at least *.label.tld
|
if (parts.length < 3) return false; // need at least *.label.tld
|
||||||
|
|
||||||
const labelRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$|^[a-zA-Z0-9]$/;
|
const labelRegex =
|
||||||
|
/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$|^[a-zA-Z0-9]$/;
|
||||||
return parts.slice(1).every((label) => labelRegex.test(label));
|
return parts.slice(1).every((label) => labelRegex.test(label));
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1826,3 +1826,77 @@ export async function verifyClientAssociationsCache(
|
|||||||
extraSiteIds: extraSiteIds.sort((a, b) => a - b)
|
extraSiteIds: extraSiteIds.sort((a, b) => a - b)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cleanupSiteAssociations efficiently removes all client associations for a
|
||||||
|
// site that is being deleted. Instead of calling
|
||||||
|
// rebuildClientAssociationsFromSiteResource once per site resource (which is
|
||||||
|
// O(resources) in DB round-trips and message fan-out), this function performs
|
||||||
|
// a single bulk lookup of affected clients and site resources, deletes all
|
||||||
|
// cache rows at once, and fires all peer/proxy removal messages in parallel.
|
||||||
|
//
|
||||||
|
// The caller is responsible for deleting the site row itself (and for sending
|
||||||
|
// the newt/wg/terminate signal to the newt process).
|
||||||
|
export async function cleanupSiteAssociations(
|
||||||
|
site: Site,
|
||||||
|
trx: Transaction | typeof db = db
|
||||||
|
): Promise<void> {
|
||||||
|
const siteId = site.siteId;
|
||||||
|
|
||||||
|
logger.debug(`cleanupSiteAssociations: START siteId=${siteId}`);
|
||||||
|
|
||||||
|
// 1. Find every client currently cached against this site.
|
||||||
|
const cachedSiteClientRows = await trx
|
||||||
|
.select({ clientId: clientSitesAssociationsCache.clientId })
|
||||||
|
.from(clientSitesAssociationsCache)
|
||||||
|
.where(eq(clientSitesAssociationsCache.siteId, siteId));
|
||||||
|
|
||||||
|
const cachedClientIds = cachedSiteClientRows.map((r) => r.clientId);
|
||||||
|
|
||||||
|
// 2. Load full client details (needed for WireGuard public-key references).
|
||||||
|
const allClients =
|
||||||
|
cachedClientIds.length > 0
|
||||||
|
? await trx
|
||||||
|
.select({
|
||||||
|
clientId: clients.clientId,
|
||||||
|
pubKey: clients.pubKey,
|
||||||
|
subnet: clients.subnet
|
||||||
|
})
|
||||||
|
.from(clients)
|
||||||
|
.where(inArray(clients.clientId, cachedClientIds))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
// 6. Bulk-delete all cache entries for this site. Do this before sending
|
||||||
|
// destination-update messages so updateClientSiteDestinations computes
|
||||||
|
// the correct (post-deletion) set of destinations.
|
||||||
|
await trx
|
||||||
|
.delete(clientSitesAssociationsCache)
|
||||||
|
.where(eq(clientSitesAssociationsCache.siteId, siteId));
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
`cleanupSiteAssociations: siteId=${siteId} cache cleared. clients=${allClients.length}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// 7. Fire all removal messages in parallel.
|
||||||
|
const jobs: Promise<any>[] = [];
|
||||||
|
|
||||||
|
for (const client of allClients) {
|
||||||
|
// Tell each olm to drop the site's WireGuard peer.
|
||||||
|
if (site.publicKey) {
|
||||||
|
jobs.push(olmDeletePeer(client.clientId, siteId, site.publicKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recompute and push updated relay destinations (now excluding this site).
|
||||||
|
if (client.pubKey && client.subnet) {
|
||||||
|
jobs.push(updateClientSiteDestinations(client, trx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(jobs).catch((error) => {
|
||||||
|
logger.error(
|
||||||
|
`cleanupSiteAssociations: error sending cleanup messages for siteId=${siteId}:`,
|
||||||
|
error
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.debug(`cleanupSiteAssociations: DONE siteId=${siteId}`);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { logsDb, requestAuditLog, driver, primaryLogsDb } from "@server/db";
|
import { logsDb, requestAuditLog, driver } from "@server/db";
|
||||||
import { registry } from "@server/openApi";
|
import { registry } from "@server/openApi";
|
||||||
import { NextFunction } from "express";
|
import { NextFunction } from "express";
|
||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
@@ -74,12 +74,12 @@ async function query(query: Q) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [all] = await primaryLogsDb
|
const [all] = await logsDb
|
||||||
.select({ total: count() })
|
.select({ total: count() })
|
||||||
.from(requestAuditLog)
|
.from(requestAuditLog)
|
||||||
.where(baseConditions);
|
.where(baseConditions);
|
||||||
|
|
||||||
const [blocked] = await primaryLogsDb
|
const [blocked] = await logsDb
|
||||||
.select({ total: count() })
|
.select({ total: count() })
|
||||||
.from(requestAuditLog)
|
.from(requestAuditLog)
|
||||||
.where(and(baseConditions, eq(requestAuditLog.action, false)));
|
.where(and(baseConditions, eq(requestAuditLog.action, false)));
|
||||||
@@ -90,7 +90,7 @@ async function query(query: Q) {
|
|||||||
|
|
||||||
const DISTINCT_LIMIT = 500;
|
const DISTINCT_LIMIT = 500;
|
||||||
|
|
||||||
const requestsPerCountry = await primaryLogsDb
|
const requestsPerCountry = await logsDb
|
||||||
.selectDistinct({
|
.selectDistinct({
|
||||||
code: requestAuditLog.location,
|
code: requestAuditLog.location,
|
||||||
count: totalQ
|
count: totalQ
|
||||||
@@ -118,7 +118,7 @@ async function query(query: Q) {
|
|||||||
const booleanTrue = driver === "pg" ? sql`true` : sql`1`;
|
const booleanTrue = driver === "pg" ? sql`true` : sql`1`;
|
||||||
const booleanFalse = driver === "pg" ? sql`false` : sql`0`;
|
const booleanFalse = driver === "pg" ? sql`false` : sql`0`;
|
||||||
|
|
||||||
const requestsPerDay = await primaryLogsDb
|
const requestsPerDay = await logsDb
|
||||||
.select({
|
.select({
|
||||||
day: groupByDayFunction.as("day"),
|
day: groupByDayFunction.as("day"),
|
||||||
allowedCount:
|
allowedCount:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { logsDb, primaryLogsDb, requestAuditLog, resources, siteResources, db, primaryDb } from "@server/db";
|
import { logsDb, requestAuditLog, resources, siteResources, db, primaryDb } from "@server/db";
|
||||||
import { registry } from "@server/openApi";
|
import { registry } from "@server/openApi";
|
||||||
import { NextFunction } from "express";
|
import { NextFunction } from "express";
|
||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
@@ -110,7 +110,7 @@ function getWhere(data: Q) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function queryRequest(data: Q) {
|
export function queryRequest(data: Q) {
|
||||||
return primaryLogsDb
|
return logsDb
|
||||||
.select({
|
.select({
|
||||||
id: requestAuditLog.id,
|
id: requestAuditLog.id,
|
||||||
timestamp: requestAuditLog.timestamp,
|
timestamp: requestAuditLog.timestamp,
|
||||||
@@ -211,7 +211,7 @@ async function enrichWithResourceDetails(logs: Awaited<ReturnType<typeof queryRe
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function countRequestQuery(data: Q) {
|
export function countRequestQuery(data: Q) {
|
||||||
const countQuery = primaryLogsDb
|
const countQuery = logsDb
|
||||||
.select({ count: count() })
|
.select({ count: count() })
|
||||||
.from(requestAuditLog)
|
.from(requestAuditLog)
|
||||||
.where(getWhere(data));
|
.where(getWhere(data));
|
||||||
@@ -254,34 +254,34 @@ async function queryUniqueFilterAttributes(
|
|||||||
uniqueResources,
|
uniqueResources,
|
||||||
uniqueSiteResources
|
uniqueSiteResources
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
primaryLogsDb
|
logsDb
|
||||||
.selectDistinct({ actor: requestAuditLog.actor })
|
.selectDistinct({ actor: requestAuditLog.actor })
|
||||||
.from(requestAuditLog)
|
.from(requestAuditLog)
|
||||||
.where(baseConditions)
|
.where(baseConditions)
|
||||||
.limit(DISTINCT_LIMIT + 1),
|
.limit(DISTINCT_LIMIT + 1),
|
||||||
primaryLogsDb
|
logsDb
|
||||||
.selectDistinct({ locations: requestAuditLog.location })
|
.selectDistinct({ locations: requestAuditLog.location })
|
||||||
.from(requestAuditLog)
|
.from(requestAuditLog)
|
||||||
.where(baseConditions)
|
.where(baseConditions)
|
||||||
.limit(DISTINCT_LIMIT + 1),
|
.limit(DISTINCT_LIMIT + 1),
|
||||||
primaryLogsDb
|
logsDb
|
||||||
.selectDistinct({ hosts: requestAuditLog.host })
|
.selectDistinct({ hosts: requestAuditLog.host })
|
||||||
.from(requestAuditLog)
|
.from(requestAuditLog)
|
||||||
.where(baseConditions)
|
.where(baseConditions)
|
||||||
.limit(DISTINCT_LIMIT + 1),
|
.limit(DISTINCT_LIMIT + 1),
|
||||||
primaryLogsDb
|
logsDb
|
||||||
.selectDistinct({ paths: requestAuditLog.path })
|
.selectDistinct({ paths: requestAuditLog.path })
|
||||||
.from(requestAuditLog)
|
.from(requestAuditLog)
|
||||||
.where(baseConditions)
|
.where(baseConditions)
|
||||||
.limit(DISTINCT_LIMIT + 1),
|
.limit(DISTINCT_LIMIT + 1),
|
||||||
primaryLogsDb
|
logsDb
|
||||||
.selectDistinct({
|
.selectDistinct({
|
||||||
id: requestAuditLog.resourceId
|
id: requestAuditLog.resourceId
|
||||||
})
|
})
|
||||||
.from(requestAuditLog)
|
.from(requestAuditLog)
|
||||||
.where(baseConditions)
|
.where(baseConditions)
|
||||||
.limit(DISTINCT_LIMIT + 1),
|
.limit(DISTINCT_LIMIT + 1),
|
||||||
primaryLogsDb
|
logsDb
|
||||||
.selectDistinct({
|
.selectDistinct({
|
||||||
id: requestAuditLog.siteResourceId
|
id: requestAuditLog.siteResourceId
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1156,7 +1156,7 @@ export const authRouter = Router();
|
|||||||
unauthenticated.use("/auth", authRouter);
|
unauthenticated.use("/auth", authRouter);
|
||||||
authRouter.use(
|
authRouter.use(
|
||||||
rateLimit({
|
rateLimit({
|
||||||
windowMs: config.getRawConfig().rate_limits.auth.window_minutes,
|
windowMs: config.getRawConfig().rate_limits.auth.window_minutes * 60 * 1000,
|
||||||
max: config.getRawConfig().rate_limits.auth.max_requests,
|
max: config.getRawConfig().rate_limits.auth.max_requests,
|
||||||
keyGenerator: (req) =>
|
keyGenerator: (req) =>
|
||||||
`authRouterGlobal:${ipKeyGenerator(req.ip || "")}:${req.path}`,
|
`authRouterGlobal:${ipKeyGenerator(req.ip || "")}:${req.path}`,
|
||||||
@@ -1252,7 +1252,7 @@ authRouter.post(
|
|||||||
windowMs: 15 * 60 * 1000,
|
windowMs: 15 * 60 * 1000,
|
||||||
max: 900,
|
max: 900,
|
||||||
keyGenerator: (req) =>
|
keyGenerator: (req) =>
|
||||||
`olmGetToken:${req.body.newtId || ipKeyGenerator(req.ip || "")}`,
|
`olmGetToken:${req.body.olmId || ipKeyGenerator(req.ip || "")}`,
|
||||||
handler: (req, res, next) => {
|
handler: (req, res, next) => {
|
||||||
const message = `You can only request an Olm token ${900} times every ${15} minutes. Please try again later.`;
|
const message = `You can only request an Olm token ${900} times every ${15} minutes. Please try again later.`;
|
||||||
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
return next(createHttpError(HttpCode.TOO_MANY_REQUESTS, message));
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db, Site, siteNetworks, siteResources } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { newts, newtSessions, sites } from "@server/db";
|
import { newts, sites } from "@server/db";
|
||||||
import { eq, inArray } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
@@ -11,7 +11,7 @@ import { deletePeer } from "../gerbil/peers";
|
|||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { sendToClient } from "#dynamic/routers/ws";
|
import { sendToClient } from "#dynamic/routers/ws";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { rebuildClientAssociationsFromSiteResource } from "@server/lib/rebuildClientAssociations";
|
import { cleanupSiteAssociations } from "@server/lib/rebuildClientAssociations";
|
||||||
import { usageService } from "@server/lib/billing/usageService";
|
import { usageService } from "@server/lib/billing/usageService";
|
||||||
import { FeatureId } from "@server/lib/billing";
|
import { FeatureId } from "@server/lib/billing";
|
||||||
|
|
||||||
@@ -63,7 +63,11 @@ export async function deleteSite(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let deletedNewtId: string | null = null;
|
const [deletedNewt] = await db
|
||||||
|
.select()
|
||||||
|
.from(newts)
|
||||||
|
.where(eq(newts.siteId, siteId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
await db.transaction(async (trx) => {
|
await db.transaction(async (trx) => {
|
||||||
if (site.type == "wireguard") {
|
if (site.type == "wireguard") {
|
||||||
@@ -71,56 +75,24 @@ export async function deleteSite(
|
|||||||
await deletePeer(site.exitNodeId!, site.pubKey);
|
await deletePeer(site.exitNodeId!, site.pubKey);
|
||||||
}
|
}
|
||||||
} else if (site.type == "newt") {
|
} else if (site.type == "newt") {
|
||||||
const networks = await trx
|
// Clean up all client associations and send peer/proxy removal
|
||||||
.select({ networkId: siteNetworks.networkId })
|
// messages in a single efficient pass before deleting the row.
|
||||||
.from(siteNetworks)
|
await cleanupSiteAssociations(site, trx);
|
||||||
.where(eq(siteNetworks.siteId, siteId));
|
|
||||||
|
|
||||||
// loop through them
|
await trx.delete(sites).where(eq(sites.siteId, siteId));
|
||||||
const updatedSiteResources = await trx
|
|
||||||
.select()
|
|
||||||
.from(siteResources)
|
|
||||||
.where(
|
|
||||||
inArray(
|
|
||||||
siteResources.networkId,
|
|
||||||
networks.map((n) => n.networkId)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
for (const siteResource of updatedSiteResources) {
|
|
||||||
await rebuildClientAssociationsFromSiteResource(
|
|
||||||
siteResource,
|
|
||||||
trx
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the newt on the site by querying the newt table for siteId
|
|
||||||
const [deletedNewt] = await trx
|
|
||||||
.delete(newts)
|
|
||||||
.where(eq(newts.siteId, siteId))
|
|
||||||
.returning();
|
|
||||||
if (deletedNewt) {
|
|
||||||
deletedNewtId = deletedNewt.newtId;
|
|
||||||
|
|
||||||
// delete all of the sessions for the newt
|
|
||||||
await trx
|
|
||||||
.delete(newtSessions)
|
|
||||||
.where(eq(newtSessions.newtId, deletedNewt.newtId));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await trx.delete(sites).where(eq(sites.siteId, siteId));
|
|
||||||
|
|
||||||
await usageService.add(site.orgId, FeatureId.SITES, -1, trx);
|
await usageService.add(site.orgId, FeatureId.SITES, -1, trx);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Send termination message outside of transaction to prevent blocking
|
// Send termination message outside of transaction to prevent blocking
|
||||||
if (deletedNewtId) {
|
if (deletedNewt) {
|
||||||
const payload = {
|
const payload = {
|
||||||
type: `newt/wg/terminate`,
|
type: `newt/wg/terminate`,
|
||||||
data: {}
|
data: {}
|
||||||
};
|
};
|
||||||
// Don't await this to prevent blocking the response
|
// Don't await this to prevent blocking the response
|
||||||
sendToClient(deletedNewtId, payload).catch((error) => {
|
sendToClient(deletedNewt.newtId, payload).catch((error) => {
|
||||||
logger.error(
|
logger.error(
|
||||||
"Failed to send termination message to newt:",
|
"Failed to send termination message to newt:",
|
||||||
error
|
error
|
||||||
|
|||||||
@@ -15,10 +15,7 @@ import logger from "@server/logger";
|
|||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { eq, and, inArray } from "drizzle-orm";
|
import { eq, and, inArray } from "drizzle-orm";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import {
|
import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations";
|
||||||
rebuildClientAssociationsFromClient,
|
|
||||||
rebuildClientAssociationsFromSiteResource
|
|
||||||
} from "@server/lib/rebuildClientAssociations";
|
|
||||||
|
|
||||||
const batchAddClientToSiteResourcesParamsSchema = z
|
const batchAddClientToSiteResourcesParamsSchema = z
|
||||||
.object({
|
.object({
|
||||||
|
|||||||
@@ -182,6 +182,115 @@ export default function GeneralPage() {
|
|||||||
router.replace(`?${params.toString()}`, { scroll: false });
|
router.replace(`?${params.toString()}`, { scroll: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const queryDateTime = async (
|
||||||
|
startDate: DateTimeValue,
|
||||||
|
endDate: DateTimeValue,
|
||||||
|
page: number = currentPage,
|
||||||
|
size: number = pageSize,
|
||||||
|
filtersParam?: {
|
||||||
|
action?: string;
|
||||||
|
type?: string;
|
||||||
|
resourceId?: string;
|
||||||
|
location?: string;
|
||||||
|
actor?: string;
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
console.log("Date range changed:", { startDate, endDate, page, size });
|
||||||
|
if (!isPaidUser(tierMatrix.accessLogs) || build === "oss") {
|
||||||
|
console.log(
|
||||||
|
"Access denied: subscription inactive or license locked"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use the provided filters or fall back to current state
|
||||||
|
const activeFilters = filtersParam || filters;
|
||||||
|
|
||||||
|
// Convert the date/time values to API parameters
|
||||||
|
const params: any = {
|
||||||
|
limit: size,
|
||||||
|
offset: page * size,
|
||||||
|
...activeFilters
|
||||||
|
};
|
||||||
|
|
||||||
|
if (startDate?.date) {
|
||||||
|
const startDateTime = new Date(startDate.date);
|
||||||
|
if (startDate.time) {
|
||||||
|
const [hours, minutes, seconds] = startDate.time
|
||||||
|
.split(":")
|
||||||
|
.map(Number);
|
||||||
|
startDateTime.setHours(hours, minutes, seconds || 0);
|
||||||
|
}
|
||||||
|
params.timeStart = startDateTime.toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endDate?.date) {
|
||||||
|
const endDateTime = new Date(endDate.date);
|
||||||
|
if (endDate.time) {
|
||||||
|
const [hours, minutes, seconds] = endDate.time
|
||||||
|
.split(":")
|
||||||
|
.map(Number);
|
||||||
|
endDateTime.setHours(hours, minutes, seconds || 0);
|
||||||
|
} else {
|
||||||
|
// If no time is specified, set to NOW
|
||||||
|
const now = new Date();
|
||||||
|
endDateTime.setHours(
|
||||||
|
now.getHours(),
|
||||||
|
now.getMinutes(),
|
||||||
|
now.getSeconds(),
|
||||||
|
now.getMilliseconds()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
params.timeEnd = endDateTime.toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await api.get(`/org/${orgId}/logs/access`, { params });
|
||||||
|
if (res.status === 200) {
|
||||||
|
setRows(res.data.data.log || []);
|
||||||
|
setTotalCount(res.data.data.pagination?.total || 0);
|
||||||
|
setFilterAttributes(res.data.data.filterAttributes);
|
||||||
|
console.log("Fetched logs:", res.data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
title: t("error"),
|
||||||
|
description: t("Failed to filter logs"),
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshData = async () => {
|
||||||
|
console.log("Data refreshed");
|
||||||
|
setIsRefreshing(true);
|
||||||
|
try {
|
||||||
|
const endDate = searchParams.get("end")
|
||||||
|
? dateRange.endDate
|
||||||
|
: { date: new Date() };
|
||||||
|
setDateRange((current) => ({ ...current, endDate }));
|
||||||
|
// Refresh data with current date range and pagination
|
||||||
|
await queryDateTime(
|
||||||
|
dateRange.startDate,
|
||||||
|
endDate,
|
||||||
|
currentPage,
|
||||||
|
pageSize
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
title: t("error"),
|
||||||
|
description: t("refreshError"),
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsRefreshing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const exportData = async () => {
|
const exportData = async () => {
|
||||||
try {
|
try {
|
||||||
const params: any = {
|
const params: any = {
|
||||||
|
|||||||
@@ -169,6 +169,114 @@ export default function GeneralPage() {
|
|||||||
router.replace(`?${params.toString()}`, { scroll: false });
|
router.replace(`?${params.toString()}`, { scroll: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
const queryDateTime = async (
|
||||||
|
startDate: DateTimeValue,
|
||||||
|
endDate: DateTimeValue,
|
||||||
|
page: number = currentPage,
|
||||||
|
size: number = pageSize,
|
||||||
|
filtersParam?: {
|
||||||
|
action?: string;
|
||||||
|
actor?: string;
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
console.log("Date range changed:", { startDate, endDate, page, size });
|
||||||
|
if (!isPaidUser(tierMatrix.actionLogs)) {
|
||||||
|
console.log(
|
||||||
|
"Access denied: subscription inactive or license locked"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use the provided filters or fall back to current state
|
||||||
|
const activeFilters = filtersParam || filters;
|
||||||
|
|
||||||
|
// Convert the date/time values to API parameters
|
||||||
|
const params: any = {
|
||||||
|
limit: size,
|
||||||
|
offset: page * size,
|
||||||
|
...activeFilters
|
||||||
|
};
|
||||||
|
|
||||||
|
if (startDate?.date) {
|
||||||
|
const startDateTime = new Date(startDate.date);
|
||||||
|
if (startDate.time) {
|
||||||
|
const [hours, minutes, seconds] = startDate.time
|
||||||
|
.split(":")
|
||||||
|
.map(Number);
|
||||||
|
startDateTime.setHours(hours, minutes, seconds || 0);
|
||||||
|
}
|
||||||
|
params.timeStart = startDateTime.toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endDate?.date) {
|
||||||
|
const endDateTime = new Date(endDate.date);
|
||||||
|
if (endDate.time) {
|
||||||
|
const [hours, minutes, seconds] = endDate.time
|
||||||
|
.split(":")
|
||||||
|
.map(Number);
|
||||||
|
endDateTime.setHours(hours, minutes, seconds || 0);
|
||||||
|
} else {
|
||||||
|
// If no time is specified, set to NOW
|
||||||
|
const now = new Date();
|
||||||
|
endDateTime.setHours(
|
||||||
|
now.getHours(),
|
||||||
|
now.getMinutes(),
|
||||||
|
now.getSeconds(),
|
||||||
|
now.getMilliseconds()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
params.timeEnd = endDateTime.toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await api.get(`/org/${orgId}/logs/action`, { params });
|
||||||
|
if (res.status === 200) {
|
||||||
|
setRows(res.data.data.log || []);
|
||||||
|
setTotalCount(res.data.data.pagination?.total || 0);
|
||||||
|
setFilterAttributes(res.data.data.filterAttributes);
|
||||||
|
console.log("Fetched logs:", res.data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
title: t("error"),
|
||||||
|
description: t("Failed to filter logs"),
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshData = async () => {
|
||||||
|
console.log("Data refreshed");
|
||||||
|
setIsRefreshing(true);
|
||||||
|
try {
|
||||||
|
const endDate = searchParams.get("end")
|
||||||
|
? dateRange.endDate
|
||||||
|
: { date: new Date() };
|
||||||
|
setDateRange((current) => ({ ...current, endDate }));
|
||||||
|
// Refresh data with current date range and pagination
|
||||||
|
await queryDateTime(
|
||||||
|
dateRange.startDate,
|
||||||
|
endDate,
|
||||||
|
currentPage,
|
||||||
|
pageSize
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
title: t("error"),
|
||||||
|
description: t("refreshError"),
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsRefreshing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
>>>>>>> main
|
||||||
const exportData = async () => {
|
const exportData = async () => {
|
||||||
try {
|
try {
|
||||||
const params: any = {
|
const params: any = {
|
||||||
|
|||||||
@@ -205,6 +205,113 @@ export default function ConnectionLogsPage() {
|
|||||||
router.replace(`?${params.toString()}`, { scroll: false });
|
router.replace(`?${params.toString()}`, { scroll: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
=======
|
||||||
|
const queryDateTime = async (
|
||||||
|
startDate: DateTimeValue,
|
||||||
|
endDate: DateTimeValue,
|
||||||
|
page: number = currentPage,
|
||||||
|
size: number = pageSize,
|
||||||
|
filtersParam?: typeof filters
|
||||||
|
) => {
|
||||||
|
console.log("Date range changed:", { startDate, endDate, page, size });
|
||||||
|
if (!isPaidUser(tierMatrix.connectionLogs)) {
|
||||||
|
console.log(
|
||||||
|
"Access denied: subscription inactive or license locked"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use the provided filters or fall back to current state
|
||||||
|
const activeFilters = filtersParam || filters;
|
||||||
|
|
||||||
|
// Convert the date/time values to API parameters
|
||||||
|
const params: any = {
|
||||||
|
limit: size,
|
||||||
|
offset: page * size,
|
||||||
|
...activeFilters
|
||||||
|
};
|
||||||
|
|
||||||
|
if (startDate?.date) {
|
||||||
|
const startDateTime = new Date(startDate.date);
|
||||||
|
if (startDate.time) {
|
||||||
|
const [hours, minutes, seconds] = startDate.time
|
||||||
|
.split(":")
|
||||||
|
.map(Number);
|
||||||
|
startDateTime.setHours(hours, minutes, seconds || 0);
|
||||||
|
}
|
||||||
|
params.timeStart = startDateTime.toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endDate?.date) {
|
||||||
|
const endDateTime = new Date(endDate.date);
|
||||||
|
if (endDate.time) {
|
||||||
|
const [hours, minutes, seconds] = endDate.time
|
||||||
|
.split(":")
|
||||||
|
.map(Number);
|
||||||
|
endDateTime.setHours(hours, minutes, seconds || 0);
|
||||||
|
} else {
|
||||||
|
// If no time is specified, set to NOW
|
||||||
|
const now = new Date();
|
||||||
|
endDateTime.setHours(
|
||||||
|
now.getHours(),
|
||||||
|
now.getMinutes(),
|
||||||
|
now.getSeconds(),
|
||||||
|
now.getMilliseconds()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
params.timeEnd = endDateTime.toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await api.get(`/org/${orgId}/logs/connection`, {
|
||||||
|
params
|
||||||
|
});
|
||||||
|
if (res.status === 200) {
|
||||||
|
setRows(res.data.data.log || []);
|
||||||
|
setTotalCount(res.data.data.pagination?.total || 0);
|
||||||
|
setFilterAttributes(res.data.data.filterAttributes);
|
||||||
|
console.log("Fetched connection logs:", res.data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
title: t("error"),
|
||||||
|
description: t("Failed to filter logs"),
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshData = async () => {
|
||||||
|
console.log("Data refreshed");
|
||||||
|
setIsRefreshing(true);
|
||||||
|
try {
|
||||||
|
const endDate = searchParams.get("end")
|
||||||
|
? dateRange.endDate
|
||||||
|
: { date: new Date() };
|
||||||
|
setDateRange((current) => ({ ...current, endDate }));
|
||||||
|
// Refresh data with current date range and pagination
|
||||||
|
await queryDateTime(
|
||||||
|
dateRange.startDate,
|
||||||
|
endDate,
|
||||||
|
currentPage,
|
||||||
|
pageSize
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
title: t("error"),
|
||||||
|
description: t("refreshError"),
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsRefreshing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
>>>>>>> main
|
||||||
const exportData = async () => {
|
const exportData = async () => {
|
||||||
try {
|
try {
|
||||||
const params: any = {
|
const params: any = {
|
||||||
|
|||||||
@@ -185,6 +185,105 @@ export default function GeneralPage() {
|
|||||||
router.replace(`?${params.toString()}`, { scroll: false });
|
router.replace(`?${params.toString()}`, { scroll: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const queryDateTime = async (
|
||||||
|
startDate: DateTimeValue,
|
||||||
|
endDate: DateTimeValue,
|
||||||
|
page: number = currentPage,
|
||||||
|
size: number = pageSize,
|
||||||
|
filtersParam?: {
|
||||||
|
action?: string;
|
||||||
|
type?: string;
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
console.log("Date range changed:", { startDate, endDate, page, size });
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Use the provided filters or fall back to current state
|
||||||
|
const activeFilters = filtersParam || filters;
|
||||||
|
|
||||||
|
// Convert the date/time values to API parameters
|
||||||
|
const params: any = {
|
||||||
|
limit: size,
|
||||||
|
offset: page * size,
|
||||||
|
...activeFilters
|
||||||
|
};
|
||||||
|
|
||||||
|
if (startDate?.date) {
|
||||||
|
const startDateTime = new Date(startDate.date);
|
||||||
|
if (startDate.time) {
|
||||||
|
const [hours, minutes, seconds] = startDate.time
|
||||||
|
.split(":")
|
||||||
|
.map(Number);
|
||||||
|
startDateTime.setHours(hours, minutes, seconds || 0);
|
||||||
|
}
|
||||||
|
params.timeStart = startDateTime.toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endDate?.date) {
|
||||||
|
const endDateTime = new Date(endDate.date);
|
||||||
|
if (endDate.time) {
|
||||||
|
const [hours, minutes, seconds] = endDate.time
|
||||||
|
.split(":")
|
||||||
|
.map(Number);
|
||||||
|
endDateTime.setHours(hours, minutes, seconds || 0);
|
||||||
|
} else {
|
||||||
|
// If no time is specified, set to NOW
|
||||||
|
const now = new Date();
|
||||||
|
endDateTime.setHours(
|
||||||
|
now.getHours(),
|
||||||
|
now.getMinutes(),
|
||||||
|
now.getSeconds(),
|
||||||
|
now.getMilliseconds()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
params.timeEnd = endDateTime.toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await api.get(`/org/${orgId}/logs/request`, { params });
|
||||||
|
if (res.status === 200) {
|
||||||
|
setRows(res.data.data.log || []);
|
||||||
|
setTotalCount(res.data.data.pagination?.total || 0);
|
||||||
|
setFilterAttributes(res.data.data.filterAttributes);
|
||||||
|
console.log("Fetched logs:", res.data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
title: t("error"),
|
||||||
|
description: t("Failed to filter logs"),
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const refreshData = async () => {
|
||||||
|
console.log("Data refreshed");
|
||||||
|
setIsRefreshing(true);
|
||||||
|
try {
|
||||||
|
const endDate = searchParams.get("end")
|
||||||
|
? dateRange.endDate
|
||||||
|
: { date: new Date() };
|
||||||
|
setDateRange((current) => ({ ...current, endDate }));
|
||||||
|
// Refresh data with current date range and pagination
|
||||||
|
await queryDateTime(
|
||||||
|
dateRange.startDate,
|
||||||
|
endDate,
|
||||||
|
currentPage,
|
||||||
|
pageSize
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
title: t("error"),
|
||||||
|
description: t("refreshError"),
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsRefreshing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const exportData = async () => {
|
const exportData = async () => {
|
||||||
try {
|
try {
|
||||||
// Prepare query params for export
|
// Prepare query params for export
|
||||||
|
|||||||
Reference in New Issue
Block a user