diff --git a/messages/en-US.json b/messages/en-US.json index 10a35e42..597d1516 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -436,6 +436,13 @@ "inviteEmailSent": "Send invite email to user", "inviteValid": "Valid For", "selectDuration": "Select duration", + "selectResource": "Select Resource", + "filterByResource": "Filter By Resource", + "resetFilters": "Reset Filters", + "totalBlocked": "Requests Blocked By Pangolin", + "totalRequests": "Total Requests", + "requestsByCountry": "Requests By Country", + "topCountries": "Top Countries", "accessRoleSelect": "Select role", "inviteEmailSentDescription": "An email has been sent to the user with the access link below. They must access the link to accept the invitation.", "inviteSentDescription": "The user has been invited. They must access the link below to accept the invitation.", @@ -1167,7 +1174,7 @@ "sidebarLogAndAnalytics": "Log & Analytics", "sidebarBluePrints": "Blueprints", "sidebarOrganization": "Organization", - "sidebarLogsAnalytics": "Log Analytics", + "sidebarLogsAnalytics": "Request Analytics", "blueprints": "Blueprints", "blueprintsDescription": "Apply declarative configurations and view previous runs", "blueprintAdd": "Add Blueprint", @@ -2002,6 +2009,7 @@ "clientMessageRemove": "Once removed, the client will no longer be able to connect to the site.", "sidebarLogs": "Logs", "request": "Request", + "requests": "Requests", "logs": "Logs", "logsSettingsDescription": "Monitor logs collected from this orginization", "searchLogs": "Search logs...", diff --git a/package-lock.json b/package-lock.json index 7c70fc2c..614a1733 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56,6 +56,7 @@ "cookies": "^0.9.1", "cors": "2.8.5", "crypto-js": "^4.2.0", + "d3": "^7.9.0", "date-fns": "4.1.0", "drizzle-orm": "0.44.7", "eslint": "9.39.1", @@ -100,9 +101,11 @@ "stripe": "18.2.1", "swagger-ui-express": "^5.0.1", "tailwind-merge": "3.3.1", + "topojson-client": "^3.1.0", "tw-animate-css": "^1.3.8", "uuid": "^13.0.0", "vaul": "1.1.2", + "visionscarto-world-atlas": "^1.0.0", "winston": "3.18.3", "winston-daily-rotate-file": "5.0.0", "ws": "8.18.3", @@ -121,6 +124,7 @@ "@types/cookie-parser": "1.4.10", "@types/cors": "2.8.19", "@types/crypto-js": "^4.2.2", + "@types/d3": "^7.4.3", "@types/express": "5.0.5", "@types/express-session": "^1.18.2", "@types/jmespath": "^0.15.2", @@ -134,6 +138,7 @@ "@types/react-dom": "19.2.2", "@types/semver": "^7.7.1", "@types/swagger-ui-express": "^4.1.8", + "@types/topojson-client": "^3.1.5", "@types/ws": "8.18.1", "@types/yargs": "17.0.34", "babel-plugin-react-compiler": "^1.0.0", @@ -9046,6 +9051,290 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", @@ -9109,6 +9398,13 @@ "@types/express": "*" } }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/http-errors": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", @@ -9306,6 +9602,27 @@ "@types/serve-static": "*" } }, + "node_modules/@types/topojson-client": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@types/topojson-client/-/topojson-client-3.1.5.tgz", + "integrity": "sha512-C79rySTyPxnQNNguTZNI1Ct4D7IXgvyAs3p9HPecnl6mNrJ5+UhvGNYcZfpROYV2lMHI48kJPxwR+F9C6c7nmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*", + "@types/topojson-specification": "*" + } + }, + "node_modules/@types/topojson-specification": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/topojson-specification/-/topojson-specification-1.0.5.tgz", + "integrity": "sha512-C7KvcQh+C2nr6Y2Ub4YfgvWvWCgP2nOQMtfhlnwsRL4pYmmwzBS7HclGiS87eQfDOU/DLQpX6GEscviaz4yLIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/triple-beam": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", @@ -11378,6 +11695,416 @@ "devOptional": true, "license": "MIT" }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -11592,6 +12319,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -14559,6 +15295,15 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/intl-messageformat": { "version": "10.7.18", "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.18.tgz", @@ -21386,6 +22131,12 @@ "node": ">=0.10.0" } }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -21425,6 +22176,12 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -22956,6 +23713,26 @@ "node": ">=0.6" } }, + "node_modules/topojson-client": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", + "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==", + "license": "ISC", + "dependencies": { + "commander": "2" + }, + "bin": { + "topo2geo": "bin/topo2geo", + "topomerge": "bin/topomerge", + "topoquantize": "bin/topoquantize" + } + }, + "node_modules/topojson-client/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, "node_modules/triple-beam": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", @@ -24045,6 +24822,12 @@ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, + "node_modules/visionscarto-world-atlas": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/visionscarto-world-atlas/-/visionscarto-world-atlas-1.0.0.tgz", + "integrity": "sha512-jHl/NQgASfw5ZML3cnbjdfr/gXK5zO8a2xKSoCVe+5+EsIaO9tMTh7SsnfhESnCpZ+Xb6XBeU91wiuyERUPshQ==", + "license": "BSD-3-Clause" + }, "node_modules/watchpack": { "version": "2.4.4", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", diff --git a/package.json b/package.json index d1458e35..f947d7dd 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "cookies": "^0.9.1", "cors": "2.8.5", "crypto-js": "^4.2.0", + "d3": "^7.9.0", "date-fns": "4.1.0", "drizzle-orm": "0.44.7", "eslint": "9.39.1", @@ -123,9 +124,11 @@ "stripe": "18.2.1", "swagger-ui-express": "^5.0.1", "tailwind-merge": "3.3.1", + "topojson-client": "^3.1.0", "tw-animate-css": "^1.3.8", "uuid": "^13.0.0", "vaul": "1.1.2", + "visionscarto-world-atlas": "^1.0.0", "winston": "3.18.3", "winston-daily-rotate-file": "5.0.0", "ws": "8.18.3", @@ -144,6 +147,7 @@ "@types/cookie-parser": "1.4.10", "@types/cors": "2.8.19", "@types/crypto-js": "^4.2.2", + "@types/d3": "^7.4.3", "@types/express": "5.0.5", "@types/express-session": "^1.18.2", "@types/jmespath": "^0.15.2", @@ -157,6 +161,7 @@ "@types/react-dom": "19.2.2", "@types/semver": "^7.7.1", "@types/swagger-ui-express": "^4.1.8", + "@types/topojson-client": "^3.1.5", "@types/ws": "8.18.1", "@types/yargs": "17.0.34", "babel-plugin-react-compiler": "^1.0.0", diff --git a/server/routers/auditLogs/queryRequestAnalytics.ts b/server/routers/auditLogs/queryRequestAnalytics.ts index 75bb6901..c9eeaeef 100644 --- a/server/routers/auditLogs/queryRequestAnalytics.ts +++ b/server/routers/auditLogs/queryRequestAnalytics.ts @@ -71,13 +71,13 @@ async function query(query: Q) { ); } - const [totalRequests] = await db + const [all] = await db .select({ total: count() }) .from(requestAuditLog) .where(baseConditions); - const [totalBlocked] = await db - .select({ blocked: count() }) + const [blocked] = await db + .select({ total: count() }) .from(requestAuditLog) .where(and(baseConditions, eq(requestAuditLog.action, false))); @@ -92,7 +92,11 @@ async function query(query: Q) { .where(baseConditions) .groupBy(requestAuditLog.location); - return { requestsPerCountry, totalBlocked, totalRequests }; + return { + requestsPerCountry, + totalBlocked: blocked.total, + totalRequests: all.total + }; } registry.registerPath({ diff --git a/src/app/[orgId]/settings/logs/analytics/page.tsx b/src/app/[orgId]/settings/logs/analytics/page.tsx index ae74ac0c..f5bd4e7a 100644 --- a/src/app/[orgId]/settings/logs/analytics/page.tsx +++ b/src/app/[orgId]/settings/logs/analytics/page.tsx @@ -1,11 +1,18 @@ +import { LogAnalyticsData } from "@app/components/LogAnalyticsData"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; -import { Card, CardContent, CardHeader } from "@app/components/ui/card"; import { getTranslations } from "next-intl/server"; +import { Suspense } from "react"; -export interface AnalyticsPageProps {} +export interface AnalyticsPageProps { + params: Promise<{ orgId: string }>; + searchParams: Promise>; +} export default async function AnalyticsPage(props: AnalyticsPageProps) { const t = await getTranslations(); + + const orgId = (await props.params).orgId; + return ( <>
- - - - +
); diff --git a/src/components/BlueprintDetailsForm.tsx b/src/components/BlueprintDetailsForm.tsx index c97ca31a..ae6d5cb1 100644 --- a/src/components/BlueprintDetailsForm.tsx +++ b/src/components/BlueprintDetailsForm.tsx @@ -11,7 +11,6 @@ import { useTranslations } from "next-intl"; import { Form, FormControl, - FormDescription, FormField, FormItem, FormLabel, diff --git a/src/components/DateTimePicker.tsx b/src/components/DateTimePicker.tsx index d0b6d40e..150bafdb 100644 --- a/src/components/DateTimePicker.tsx +++ b/src/components/DateTimePicker.tsx @@ -7,209 +7,220 @@ import { Calendar } from "@app/components/ui/calendar"; import { Input } from "@app/components/ui/input"; import { Label } from "@app/components/ui/label"; import { - Popover, - PopoverContent, - PopoverTrigger, + Popover, + PopoverContent, + PopoverTrigger } from "@app/components/ui/popover"; import { cn } from "@app/lib/cn"; import { ChangeEvent, useEffect, useState } from "react"; export interface DateTimeValue { - date?: Date; - time?: string; + date?: Date; + time?: string; } export interface DateTimePickerProps { - label?: string; - value?: DateTimeValue; - onChange?: (value: DateTimeValue) => void; - placeholder?: string; - className?: string; - disabled?: boolean; - showTime?: boolean; + label?: string; + value?: DateTimeValue; + onChange?: (value: DateTimeValue) => void; + placeholder?: string; + className?: string; + disabled?: boolean; + showTime?: boolean; } export function DateTimePicker({ - label, - value, - onChange, - placeholder = "Select date & time", - className, - disabled = false, - showTime = true, + label, + value, + onChange, + placeholder = "Select date & time", + className, + disabled = false, + showTime = true }: DateTimePickerProps) { - const [open, setOpen] = useState(false); - const [internalDate, setInternalDate] = useState(value?.date); - const [internalTime, setInternalTime] = useState(value?.time || ""); + const [open, setOpen] = useState(false); + const [internalDate, setInternalDate] = useState( + value?.date + ); + const [internalTime, setInternalTime] = useState(value?.time || ""); - // Sync internal state with external value prop - useEffect(() => { - setInternalDate(value?.date); - setInternalTime(value?.time || ""); - }, [value?.date, value?.time]); + // Sync internal state with external value prop + useEffect(() => { + setInternalDate(value?.date); + setInternalTime(value?.time || ""); + }, [value?.date, value?.time]); - const handleDateChange = (date: Date | undefined) => { - setInternalDate(date); - const newValue = { date, time: internalTime }; - onChange?.(newValue); - }; + const handleDateChange = (date: Date | undefined) => { + setInternalDate(date); + const newValue = { date, time: internalTime }; + onChange?.(newValue); + }; - const handleTimeChange = (event: ChangeEvent) => { - const time = event.target.value; - setInternalTime(time); - const newValue = { date: internalDate, time }; - onChange?.(newValue); - }; + const handleTimeChange = (event: ChangeEvent) => { + const time = event.target.value; + setInternalTime(time); + const newValue = { date: internalDate, time }; + onChange?.(newValue); + }; -const getDisplayText = () => { - if (!internalDate) return placeholder; - - const dateStr = internalDate.toLocaleDateString(); - if (!showTime || !internalTime) return dateStr; - - // Parse time and format in local timezone - const [hours, minutes, seconds] = internalTime.split(':'); - const timeDate = new Date(); - timeDate.setHours(parseInt(hours, 10), parseInt(minutes, 10), parseInt(seconds || '0', 10)); - const timeStr = timeDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); - - return `${dateStr} ${timeStr}`; -}; + const getDisplayText = () => { + if (!internalDate) return placeholder; - const hasValue = internalDate || (showTime && internalTime); + const dateStr = internalDate.toLocaleDateString(); + if (!showTime || !internalTime) return dateStr; - return ( -
-
- {label && ( - - )} -
- - - - - - {showTime ? ( -
- { - handleDateChange(date); - if (!showTime) { - setOpen(false); - } - }} - className="flex-grow w-[250px]" - /> -
-
- - -
-
+ // Parse time and format in local timezone + const [hours, minutes, seconds] = internalTime.split(":"); + const timeDate = new Date(); + timeDate.setHours( + parseInt(hours, 10), + parseInt(minutes, 10), + parseInt(seconds || "0", 10) + ); + const timeStr = timeDate.toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit" + }); + + return `${dateStr} ${timeStr}`; + }; + + const hasValue = internalDate || (showTime && internalTime); + + return ( +
+
+ {label && } +
+ + + + + + {showTime ? ( +
+ { + handleDateChange(date); + if (!showTime) { + setOpen(false); + } + }} + className="grow w-[250px]" + /> +
+
+ + +
+
+
+ ) : ( + { + handleDateChange(date); + setOpen(false); + }} + /> + )} +
+
- ) : ( - { - handleDateChange(date); - setOpen(false); - }} - /> - )} - - +
-
-
- ); + ); } export interface DateRangePickerProps { - startLabel?: string; - endLabel?: string; - startValue?: DateTimeValue; - endValue?: DateTimeValue; - onStartChange?: (value: DateTimeValue) => void; - onEndChange?: (value: DateTimeValue) => void; - onRangeChange?: (start: DateTimeValue, end: DateTimeValue) => void; - className?: string; - disabled?: boolean; - showTime?: boolean; + startLabel?: string; + endLabel?: string; + startValue?: DateTimeValue; + endValue?: DateTimeValue; + onStartChange?: (value: DateTimeValue) => void; + onEndChange?: (value: DateTimeValue) => void; + onRangeChange?: (start: DateTimeValue, end: DateTimeValue) => void; + className?: string; + disabled?: boolean; + showTime?: boolean; } export function DateRangePicker({ -// startLabel = "From", -// endLabel = "To", - startValue, - endValue, - onStartChange, - onEndChange, - onRangeChange, - className, - disabled = false, - showTime = true, + // startLabel = "From", + // endLabel = "To", + startValue, + endValue, + onStartChange, + onEndChange, + onRangeChange, + className, + disabled = false, + showTime = true }: DateRangePickerProps) { - const handleStartChange = (value: DateTimeValue) => { - onStartChange?.(value); - if (onRangeChange && endValue) { - onRangeChange(value, endValue); - } - }; + const handleStartChange = (value: DateTimeValue) => { + onStartChange?.(value); + if (onRangeChange && endValue) { + onRangeChange(value, endValue); + } + }; - const handleEndChange = (value: DateTimeValue) => { - onEndChange?.(value); - if (onRangeChange && startValue) { - onRangeChange(startValue, value); - } - }; + const handleEndChange = (value: DateTimeValue) => { + onEndChange?.(value); + if (onRangeChange && startValue) { + onRangeChange(startValue, value); + } + }; - return ( -
- - -
- ); -} \ No newline at end of file + return ( +
+ + +
+ ); +} diff --git a/src/components/InfoSection.tsx b/src/components/InfoSection.tsx index 5959bfc3..b1cc74a8 100644 --- a/src/components/InfoSection.tsx +++ b/src/components/InfoSection.tsx @@ -11,7 +11,7 @@ export function InfoSections({ }) { return (
createApiClient(env)); + const router = useRouter(); + + const dateRange = { + startDate: filters.timeStart ? new Date(filters.timeStart) : undefined, + endDate: filters.timeEnd ? new Date(filters.timeEnd) : undefined + }; + + const { data: resources = [], isFetching: isFetchingResources } = useQuery( + resourceQueries.listNamesPerOrg(props.orgId, api) + ); + + const { + data: stats, + isFetching: isFetchingAnalytics, + refetch: refreshAnalytics + } = useQuery( + logQueries.requestAnalytics({ + orgId: props.orgId, + api, + filters + }) + ); + + const percentBlocked = stats + ? new Intl.NumberFormat(navigator.language, { + maximumFractionDigits: 5 + }).format(stats.totalBlocked / stats.totalRequests) + : null; + const totalRequests = stats + ? new Intl.NumberFormat(navigator.language, { + maximumFractionDigits: 0 + }).format(stats.totalRequests) + : null; + + function handleTimeRangeUpdate(start: DateTimeValue, end: DateTimeValue) { + const newSearch = new URLSearchParams(searchParams); + const timeRegex = + /^(?\d{1,2})\:(?\d{1,2})(\:(?\d{1,2}))?$/; + + if (start.date) { + const startDate = new Date(start.date); + if (start.time) { + const time = timeRegex.exec(start.time); + const groups = time?.groups ?? {}; + startDate.setHours(Number(groups.hours)); + startDate.setMinutes(Number(groups.minutes)); + if (groups.seconds) { + startDate.setSeconds(Number(groups.seconds)); + } + } + newSearch.set("timeStart", startDate.toISOString()); + } + if (end.date) { + const endDate = new Date(end.date); + + if (end.time) { + const time = timeRegex.exec(end.time); + const groups = time?.groups ?? {}; + endDate.setHours(Number(groups.hours)); + endDate.setMinutes(Number(groups.minutes)); + if (groups.seconds) { + endDate.setSeconds(Number(groups.seconds)); + } + } + + console.log({ + endDate + }); + newSearch.set("timeEnd", endDate.toISOString()); + } + router.replace(`${path}?${newSearch.toString()}`); + } + function getDateTime(date: Date) { + return `${date.getHours()}:${date.getMinutes()}`; + } + + return ( +
+ + +
+ + + + +
+
+ + +
+ + {!isEmptySearchParams && ( + + )} +
+
+
+ +
+
+
+ + + + + + + {t("totalRequests")} + + + {totalRequests ?? "--"} + + + + + {t("totalBlocked")} + + + {stats?.totalBlocked ?? "--"} +  ( + {percentBlocked ?? "--"} + % + ) + + + + + + +
+ + +

+ {t("requestsByCountry")} +

+
+ + ({ + count: item.total, + code: item.country_code ?? "US" + })) ?? [] + } + label={{ + singular: "request", + plural: "requests" + }} + /> + +
+ + + +

{t("topCountries")}

+
+ + {/* ... */} + +
+
+
+ ); +} diff --git a/src/components/WorldMap.tsx b/src/components/WorldMap.tsx new file mode 100644 index 00000000..5b64ac8b --- /dev/null +++ b/src/components/WorldMap.tsx @@ -0,0 +1,281 @@ +/** + * Inspired from plausible: https://github.com/plausible/analytics/blob/1df08a25b4a536c9cc1e03855ddcfeac1d1cf6e5/assets/js/dashboard/stats/locations/map.tsx + */ +import { cn } from "@app/lib/cn"; +import worldJson from "visionscarto-world-atlas/world/110m.json"; +import * as topojson from "topojson-client"; +import * as d3 from "d3"; +import { useRef, type ComponentRef, useState, useEffect, useMemo } from "react"; +import { useTheme } from "next-themes"; +import { COUNTRY_CODE_LIST } from "@app/lib/countryCodeList"; +import { useTranslations } from "next-intl"; + +type CountryData = { + alpha_3: string; + name: string; + count: number; + code: string; +}; + +export type WorldMapProps = { + data: Pick[]; + label: { + singular: string; + plural: string; + }; +}; + +export function WorldMap({ data, label }: WorldMapProps) { + const svgRef = useRef>(null); + const [tooltip, setTooltip] = useState<{ + x: number; + y: number; + hoveredCountryAlpha3Code: string | null; + }>({ x: 0, y: 0, hoveredCountryAlpha3Code: null }); + const { theme, systemTheme } = useTheme(); + + const t = useTranslations(); + + useEffect(() => { + if (!svgRef.current) return; + const svg = drawInteractiveCountries(svgRef.current, setTooltip); + + return () => { + svg.selectAll("*").remove(); + }; + }, []); + + const displayNames = new Intl.DisplayNames(navigator.language, { + type: "region", + fallback: "code" + }); + + const maxValue = Math.max(...data.map((item) => item.count)); + const dataByCountryCode = useMemo(() => { + const byCountryCode = new Map(); + for (const country of data) { + const countryISOData = COUNTRY_CODE_LIST[country.code]; + + if (countryISOData) { + byCountryCode.set(countryISOData.alpha3, { + ...country, + name: displayNames.of(country.code)!, + alpha_3: countryISOData.alpha3 + }); + } + } + return byCountryCode; + }, [data]); + + useEffect(() => { + if (svgRef.current) { + const palette = + colorScales[theme ?? "light"] ?? + colorScales[systemTheme ?? "light"]; + + const getColorForValue = d3 + .scaleLinear() + .domain([0, maxValue]) + .range(palette); + + colorInCountriesWithValues( + svgRef.current, + getColorForValue, + dataByCountryCode + ).on("click", (_event, countryPath) => { + console.log({ + _event, + countryPath + }); + // onCountryClick(countryPath as unknown as WorldJsonCountryData); + }); + } + }, [theme, systemTheme, maxValue, dataByCountryCode]); + + const hoveredCountryData = tooltip.hoveredCountryAlpha3Code + ? dataByCountryCode.get(tooltip.hoveredCountryAlpha3Code) + : undefined; + + return ( +
+ + + {!!hoveredCountryData && ( + + )} +
+ ); +} + +interface MapTooltipProps { + name: string; + value: string; + label: string; + x: number; + y: number; +} + +function MapTooltip({ name, value, label, x, y }: MapTooltipProps) { + return ( +
+
{name}
+ {value} {label} +
+ ); +} + +const width = 475; +const height = 335; +const sharedCountryClass = cn("transition-colors"); + +const colorScales: Record = { + dark: ["#4F4444", "#f36117"], + light: ["#FFF5F3", "#f36117"] +}; + +const countryClass = cn( + sharedCountryClass, + "stroke-1", + "fill-[#fafafa]", + "stroke-[#dae1e7]", + "dark:fill-[#323236]", + "dark:stroke-[#18181b]" +); + +const highlightedCountryClass = cn( + sharedCountryClass, + "stroke-2", + "fill-[#f4f4f5]", + "stroke-[#f36117]", + "dark:fill-[#3f3f46]" +); + +function setupProjetionPath() { + const projection = d3 + .geoMercator() + .scale(75) + .translate([width / 2, height / 1.5]); + + const path = d3.geoPath().projection(projection); + return path; +} + +/** @returns the d3 selected svg element */ +function drawInteractiveCountries( + element: SVGSVGElement, + setTooltip: React.Dispatch< + React.SetStateAction<{ + x: number; + y: number; + hoveredCountryAlpha3Code: string | null; + }> + > +) { + const path = setupProjetionPath(); + const data = parseWorldTopoJsonToGeoJsonFeatures(); + const svg = d3.select(element); + + svg.selectAll("path") + .data(data) + .enter() + .append("path") + .attr("class", countryClass) + .attr("d", path as never) + + .on("mouseover", function (event, country) { + const [x, y] = d3.pointer(event, svg.node()?.parentNode); + setTooltip({ + x, + y, + hoveredCountryAlpha3Code: country.properties.a3 + }); + // brings country to front + this.parentNode?.appendChild(this); + d3.select(this).attr("class", highlightedCountryClass); + }) + + .on("mousemove", function (event) { + const [x, y] = d3.pointer(event, svg.node()?.parentNode); + setTooltip((currentState) => ({ ...currentState, x, y })); + }) + + .on("mouseout", function () { + setTooltip({ x: 0, y: 0, hoveredCountryAlpha3Code: null }); + d3.select(this).attr("class", countryClass); + }); + + return svg; +} + +type WorldJsonCountryData = { properties: { name: string; a3: string } }; + +function parseWorldTopoJsonToGeoJsonFeatures(): Array { + const collection = topojson.feature( + // @ts-expect-error strings in worldJson not recongizable as the enum values declared in library + worldJson, + worldJson.objects.countries + ); + // @ts-expect-error topojson.feature return type incorrectly inferred as not a collection + return collection.features; +} + +/** + * Used to color the countries + * @returns the svg elements represeting countries + */ +function colorInCountriesWithValues( + element: SVGSVGElement, + getColorForValue: d3.ScaleLinear, + dataByCountryCode: Map +) { + function getCountryByCountryPath(countryPath: unknown) { + return dataByCountryCode.get( + (countryPath as unknown as WorldJsonCountryData).properties.a3 + ); + } + + const svg = d3.select(element); + + return svg + .selectAll("path") + .style("fill", (countryPath) => { + const country = getCountryByCountryPath(countryPath); + if (!country?.count) { + return null; + } + return getColorForValue(country.count); + }) + .style("cursor", (countryPath) => { + const country = getCountryByCountryPath(countryPath); + if (!country?.count) { + return null; + } + return "pointer"; + }); +} diff --git a/src/contexts/envContext.ts b/src/contexts/envContext.ts index f488c71b..7e3b2fb3 100644 --- a/src/contexts/envContext.ts +++ b/src/contexts/envContext.ts @@ -1,7 +1,7 @@ -import { Env } from "@app/lib/types/env"; +import type { Env } from "@app/lib/types/env"; import { createContext } from "react"; -interface EnvContextType { +export interface EnvContextType { env: Env; } diff --git a/src/lib/countryCodeList.ts b/src/lib/countryCodeList.ts new file mode 100644 index 00000000..929d5082 --- /dev/null +++ b/src/lib/countryCodeList.ts @@ -0,0 +1,1002 @@ +// taken from: https://github.com/zonicdoe/ISO-3166-Country-codes/blob/master/Indented-lists/ISO-3166-ID-SHORT-ALPHA2.json +export const COUNTRY_CODE_LIST: Record< + string, + { alpha3: string; name: string } +> = { + AF: { + alpha3: "AFG", + name: "Afghanistan" + }, + AL: { + alpha3: "ALB", + name: "Albania" + }, + DZ: { + alpha3: "DZA", + name: "Algeria" + }, + AS: { + alpha3: "ASM", + name: "American Samoa" + }, + AD: { + alpha3: "AND", + name: "Andorra" + }, + AO: { + alpha3: "AGO", + name: "Angola" + }, + AI: { + alpha3: "AIA", + name: "Anguilla" + }, + AQ: { + alpha3: "ATA", + name: "Antarctica" + }, + AG: { + alpha3: "ATG", + name: "Antigua and Barbuda" + }, + AR: { + alpha3: "ARG", + name: "Argentina" + }, + AM: { + alpha3: "ARM", + name: "Armenia" + }, + AW: { + alpha3: "ABW", + name: "Aruba" + }, + AU: { + alpha3: "AUS", + name: "Australia" + }, + AT: { + alpha3: "AUT", + name: "Austria" + }, + AZ: { + alpha3: "AZE", + name: "Azerbaijan" + }, + BS: { + alpha3: "BHS", + name: "Bahamas (the)" + }, + BH: { + alpha3: "BHR", + name: "Bahrain" + }, + BD: { + alpha3: "BGD", + name: "Bangladesh" + }, + BB: { + alpha3: "BRB", + name: "Barbados" + }, + BY: { + alpha3: "BLR", + name: "Belarus" + }, + BE: { + alpha3: "BEL", + name: "Belgium" + }, + BZ: { + alpha3: "BLZ", + name: "Belize" + }, + BJ: { + alpha3: "BEN", + name: "Benin" + }, + BM: { + alpha3: "BMU", + name: "Bermuda" + }, + BT: { + alpha3: "BTN", + name: "Bhutan" + }, + BO: { + alpha3: "BOL", + name: "Bolivia (Plurinational State of)" + }, + BQ: { + alpha3: "BES", + name: "Bonaire, Sint Eustatius and Saba" + }, + BA: { + alpha3: "BIH", + name: "Bosnia and Herzegovina" + }, + BW: { + alpha3: "BWA", + name: "Botswana" + }, + BV: { + alpha3: "BVT", + name: "Bouvet Island" + }, + BR: { + alpha3: "BRA", + name: "Brazil" + }, + IO: { + alpha3: "IOT", + name: "British Indian Ocean Territory (the)" + }, + BN: { + alpha3: "BRN", + name: "Brunei Darussalam" + }, + BG: { + alpha3: "BGR", + name: "Bulgaria" + }, + BF: { + alpha3: "BFA", + name: "Burkina Faso" + }, + BI: { + alpha3: "BDI", + name: "Burundi" + }, + CV: { + alpha3: "CPV", + name: "Cabo Verde" + }, + KH: { + alpha3: "KHM", + name: "Cambodia" + }, + CM: { + alpha3: "CMR", + name: "Cameroon" + }, + CA: { + alpha3: "CAN", + name: "Canada" + }, + KY: { + alpha3: "CYM", + name: "Cayman Islands (the)" + }, + CF: { + alpha3: "CAF", + name: "Central African Republic (the)" + }, + TD: { + alpha3: "TCD", + name: "Chad" + }, + CL: { + alpha3: "CHL", + name: "Chile" + }, + CN: { + alpha3: "CHN", + name: "China" + }, + CX: { + alpha3: "CXR", + name: "Christmas Island" + }, + CC: { + alpha3: "CCK", + name: "Cocos (Keeling) Islands (the)" + }, + CO: { + alpha3: "COL", + name: "Colombia" + }, + KM: { + alpha3: "COM", + name: "Comoros (the)" + }, + CD: { + alpha3: "COD", + name: "Congo (the Democratic Republic of the)" + }, + CG: { + alpha3: "COG", + name: "Congo (the)" + }, + CK: { + alpha3: "COK", + name: "Cook Islands (the)" + }, + CR: { + alpha3: "CRI", + name: "Costa Rica" + }, + HR: { + alpha3: "HRV", + name: "Croatia" + }, + CU: { + alpha3: "CUB", + name: "Cuba" + }, + CW: { + alpha3: "CUW", + name: "Curaçao" + }, + CY: { + alpha3: "CYP", + name: "Cyprus" + }, + CZ: { + alpha3: "CZE", + name: "Czechia" + }, + CI: { + alpha3: "CIV", + name: "Côte d'Ivoire" + }, + DK: { + alpha3: "DNK", + name: "Denmark" + }, + DJ: { + alpha3: "DJI", + name: "Djibouti" + }, + DM: { + alpha3: "DMA", + name: "Dominica" + }, + DO: { + alpha3: "DOM", + name: "Dominican Republic (the)" + }, + EC: { + alpha3: "ECU", + name: "Ecuador" + }, + EG: { + alpha3: "EGY", + name: "Egypt" + }, + SV: { + alpha3: "SLV", + name: "El Salvador" + }, + GQ: { + alpha3: "GNQ", + name: "Equatorial Guinea" + }, + ER: { + alpha3: "ERI", + name: "Eritrea" + }, + EE: { + alpha3: "EST", + name: "Estonia" + }, + SZ: { + alpha3: "SWZ", + name: "Eswatini" + }, + ET: { + alpha3: "ETH", + name: "Ethiopia" + }, + FK: { + alpha3: "FLK", + name: "Falkland Islands (the) [Malvinas]" + }, + FO: { + alpha3: "FRO", + name: "Faroe Islands (the)" + }, + FJ: { + alpha3: "FJI", + name: "Fiji" + }, + FI: { + alpha3: "FIN", + name: "Finland" + }, + FR: { + alpha3: "FRA", + name: "France" + }, + GF: { + alpha3: "GUF", + name: "French Guiana" + }, + PF: { + alpha3: "PYF", + name: "French Polynesia" + }, + TF: { + alpha3: "ATF", + name: "French Southern Territories (the)" + }, + GA: { + alpha3: "GAB", + name: "Gabon" + }, + GM: { + alpha3: "GMB", + name: "Gambia (the)" + }, + GE: { + alpha3: "GEO", + name: "Georgia" + }, + DE: { + alpha3: "DEU", + name: "Germany" + }, + GH: { + alpha3: "GHA", + name: "Ghana" + }, + GI: { + alpha3: "GIB", + name: "Gibraltar" + }, + GR: { + alpha3: "GRC", + name: "Greece" + }, + GL: { + alpha3: "GRL", + name: "Greenland" + }, + GD: { + alpha3: "GRD", + name: "Grenada" + }, + GP: { + alpha3: "GLP", + name: "Guadeloupe" + }, + GU: { + alpha3: "GUM", + name: "Guam" + }, + GT: { + alpha3: "GTM", + name: "Guatemala" + }, + GG: { + alpha3: "GGY", + name: "Guernsey" + }, + GN: { + alpha3: "GIN", + name: "Guinea" + }, + GW: { + alpha3: "GNB", + name: "Guinea-Bissau" + }, + GY: { + alpha3: "GUY", + name: "Guyana" + }, + HT: { + alpha3: "HTI", + name: "Haiti" + }, + HM: { + alpha3: "HMD", + name: "Heard Island and McDonald Islands" + }, + VA: { + alpha3: "VAT", + name: "Holy See (the)" + }, + HN: { + alpha3: "HND", + name: "Honduras" + }, + HK: { + alpha3: "HKG", + name: "Hong Kong" + }, + HU: { + alpha3: "HUN", + name: "Hungary" + }, + IS: { + alpha3: "ISL", + name: "Iceland" + }, + IN: { + alpha3: "IND", + name: "India" + }, + ID: { + alpha3: "IDN", + name: "Indonesia" + }, + IR: { + alpha3: "IRN", + name: "Iran (Islamic Republic of)" + }, + IQ: { + alpha3: "IRQ", + name: "Iraq" + }, + IE: { + alpha3: "IRL", + name: "Ireland" + }, + IM: { + alpha3: "IMN", + name: "Isle of Man" + }, + IL: { + alpha3: "ISR", + name: "Israel" + }, + IT: { + alpha3: "ITA", + name: "Italy" + }, + JM: { + alpha3: "JAM", + name: "Jamaica" + }, + JP: { + alpha3: "JPN", + name: "Japan" + }, + JE: { + alpha3: "JEY", + name: "Jersey" + }, + JO: { + alpha3: "JOR", + name: "Jordan" + }, + KZ: { + alpha3: "KAZ", + name: "Kazakhstan" + }, + KE: { + alpha3: "KEN", + name: "Kenya" + }, + KI: { + alpha3: "KIR", + name: "Kiribati" + }, + KP: { + alpha3: "PRK", + name: "Korea (the Democratic People's Republic of)" + }, + KR: { + alpha3: "KOR", + name: "Korea (the Republic of)" + }, + KW: { + alpha3: "KWT", + name: "Kuwait" + }, + KG: { + alpha3: "KGZ", + name: "Kyrgyzstan" + }, + LA: { + alpha3: "LAO", + name: "Lao People's Democratic Republic (the)" + }, + LV: { + alpha3: "LVA", + name: "Latvia" + }, + LB: { + alpha3: "LBN", + name: "Lebanon" + }, + LS: { + alpha3: "LSO", + name: "Lesotho" + }, + LR: { + alpha3: "LBR", + name: "Liberia" + }, + LY: { + alpha3: "LBY", + name: "Libya" + }, + LI: { + alpha3: "LIE", + name: "Liechtenstein" + }, + LT: { + alpha3: "LTU", + name: "Lithuania" + }, + LU: { + alpha3: "LUX", + name: "Luxembourg" + }, + MO: { + alpha3: "MAC", + name: "Macao" + }, + MG: { + alpha3: "MDG", + name: "Madagascar" + }, + MW: { + alpha3: "MWI", + name: "Malawi" + }, + MY: { + alpha3: "MYS", + name: "Malaysia" + }, + MV: { + alpha3: "MDV", + name: "Maldives" + }, + ML: { + alpha3: "MLI", + name: "Mali" + }, + MT: { + alpha3: "MLT", + name: "Malta" + }, + MH: { + alpha3: "MHL", + name: "Marshall Islands (the)" + }, + MQ: { + alpha3: "MTQ", + name: "Martinique" + }, + MR: { + alpha3: "MRT", + name: "Mauritania" + }, + MU: { + alpha3: "MUS", + name: "Mauritius" + }, + YT: { + alpha3: "MYT", + name: "Mayotte" + }, + MX: { + alpha3: "MEX", + name: "Mexico" + }, + FM: { + alpha3: "FSM", + name: "Micronesia (Federated States of)" + }, + MD: { + alpha3: "MDA", + name: "Moldova (the Republic of)" + }, + MC: { + alpha3: "MCO", + name: "Monaco" + }, + MN: { + alpha3: "MNG", + name: "Mongolia" + }, + ME: { + alpha3: "MNE", + name: "Montenegro" + }, + MS: { + alpha3: "MSR", + name: "Montserrat" + }, + MA: { + alpha3: "MAR", + name: "Morocco" + }, + MZ: { + alpha3: "MOZ", + name: "Mozambique" + }, + MM: { + alpha3: "MMR", + name: "Myanmar" + }, + NA: { + alpha3: "NAM", + name: "Namibia" + }, + NR: { + alpha3: "NRU", + name: "Nauru" + }, + NP: { + alpha3: "NPL", + name: "Nepal" + }, + NL: { + alpha3: "NLD", + name: "Netherlands (the)" + }, + NC: { + alpha3: "NCL", + name: "New Caledonia" + }, + NZ: { + alpha3: "NZL", + name: "New Zealand" + }, + NI: { + alpha3: "NIC", + name: "Nicaragua" + }, + NE: { + alpha3: "NER", + name: "Niger (the)" + }, + NG: { + alpha3: "NGA", + name: "Nigeria" + }, + NU: { + alpha3: "NIU", + name: "Niue" + }, + NF: { + alpha3: "NFK", + name: "Norfolk Island" + }, + MK: { + alpha3: "MKD", + name: "North Macedonia" + }, + MP: { + alpha3: "MNP", + name: "Northern Mariana Islands (the)" + }, + NO: { + alpha3: "NOR", + name: "Norway" + }, + OM: { + alpha3: "OMN", + name: "Oman" + }, + PK: { + alpha3: "PAK", + name: "Pakistan" + }, + PW: { + alpha3: "PLW", + name: "Palau" + }, + PS: { + alpha3: "PSE", + name: "Palestine, State of" + }, + PA: { + alpha3: "PAN", + name: "Panama" + }, + PG: { + alpha3: "PNG", + name: "Papua New Guinea" + }, + PY: { + alpha3: "PRY", + name: "Paraguay" + }, + PE: { + alpha3: "PER", + name: "Peru" + }, + PH: { + alpha3: "PHL", + name: "Philippines (the)" + }, + PN: { + alpha3: "PCN", + name: "Pitcairn" + }, + PL: { + alpha3: "POL", + name: "Poland" + }, + PT: { + alpha3: "PRT", + name: "Portugal" + }, + PR: { + alpha3: "PRI", + name: "Puerto Rico" + }, + QA: { + alpha3: "QAT", + name: "Qatar" + }, + RO: { + alpha3: "ROU", + name: "Romania" + }, + RU: { + alpha3: "RUS", + name: "Russian Federation (the)" + }, + RW: { + alpha3: "RWA", + name: "Rwanda" + }, + RE: { + alpha3: "REU", + name: "Réunion" + }, + BL: { + alpha3: "BLM", + name: "Saint Barthélemy" + }, + SH: { + alpha3: "SHN", + name: "Saint Helena, Ascension and Tristan da Cunha" + }, + KN: { + alpha3: "KNA", + name: "Saint Kitts and Nevis" + }, + LC: { + alpha3: "LCA", + name: "Saint Lucia" + }, + MF: { + alpha3: "MAF", + name: "Saint Martin (French part)" + }, + PM: { + alpha3: "SPM", + name: "Saint Pierre and Miquelon" + }, + VC: { + alpha3: "VCT", + name: "Saint Vincent and the Grenadines" + }, + WS: { + alpha3: "WSM", + name: "Samoa" + }, + SM: { + alpha3: "SMR", + name: "San Marino" + }, + ST: { + alpha3: "STP", + name: "Sao Tome and Principe" + }, + SA: { + alpha3: "SAU", + name: "Saudi Arabia" + }, + SN: { + alpha3: "SEN", + name: "Senegal" + }, + RS: { + alpha3: "SRB", + name: "Serbia" + }, + SC: { + alpha3: "SYC", + name: "Seychelles" + }, + SL: { + alpha3: "SLE", + name: "Sierra Leone" + }, + SG: { + alpha3: "SGP", + name: "Singapore" + }, + SX: { + alpha3: "SXM", + name: "Sint Maarten (Dutch part)" + }, + SK: { + alpha3: "SVK", + name: "Slovakia" + }, + SI: { + alpha3: "SVN", + name: "Slovenia" + }, + SB: { + alpha3: "SLB", + name: "Solomon Islands" + }, + SO: { + alpha3: "SOM", + name: "Somalia" + }, + ZA: { + alpha3: "ZAF", + name: "South Africa" + }, + GS: { + alpha3: "SGS", + name: "South Georgia and the South Sandwich Islands" + }, + SS: { + alpha3: "SSD", + name: "South Sudan" + }, + ES: { + alpha3: "ESP", + name: "Spain" + }, + LK: { + alpha3: "LKA", + name: "Sri Lanka" + }, + SD: { + alpha3: "SDN", + name: "Sudan (the)" + }, + SR: { + alpha3: "SUR", + name: "Suriname" + }, + SJ: { + alpha3: "SJM", + name: "Svalbard and Jan Mayen" + }, + SE: { + alpha3: "SWE", + name: "Sweden" + }, + CH: { + alpha3: "CHE", + name: "Switzerland" + }, + SY: { + alpha3: "SYR", + name: "Syrian Arab Republic (the)" + }, + TW: { + alpha3: "TWN", + name: "Taiwan (Province of China)" + }, + TJ: { + alpha3: "TJK", + name: "Tajikistan" + }, + TZ: { + alpha3: "TZA", + name: "Tanzania, the United Republic of" + }, + TH: { + alpha3: "THA", + name: "Thailand" + }, + TL: { + alpha3: "TLS", + name: "Timor-Leste" + }, + TG: { + alpha3: "TGO", + name: "Togo" + }, + TK: { + alpha3: "TKL", + name: "Tokelau" + }, + TO: { + alpha3: "TON", + name: "Tonga" + }, + TT: { + alpha3: "TTO", + name: "Trinidad and Tobago" + }, + TN: { + alpha3: "TUN", + name: "Tunisia" + }, + TR: { + alpha3: "TUR", + name: "Turkey" + }, + TM: { + alpha3: "TKM", + name: "Turkmenistan" + }, + TC: { + alpha3: "TCA", + name: "Turks and Caicos Islands (the)" + }, + TV: { + alpha3: "TUV", + name: "Tuvalu" + }, + UG: { + alpha3: "UGA", + name: "Uganda" + }, + UA: { + alpha3: "UKR", + name: "Ukraine" + }, + AE: { + alpha3: "ARE", + name: "United Arab Emirates (the)" + }, + GB: { + alpha3: "GBR", + name: "United Kingdom of Great Britain and Northern Ireland (the)" + }, + UM: { + alpha3: "UMI", + name: "United States Minor Outlying Islands (the)" + }, + US: { + alpha3: "USA", + name: "United States of America (the)" + }, + UY: { + alpha3: "URY", + name: "Uruguay" + }, + UZ: { + alpha3: "UZB", + name: "Uzbekistan" + }, + VU: { + alpha3: "VUT", + name: "Vanuatu" + }, + VE: { + alpha3: "VEN", + name: "Venezuela (Bolivarian Republic of)" + }, + VN: { + alpha3: "VNM", + name: "Viet Nam" + }, + VG: { + alpha3: "VGB", + name: "Virgin Islands (British)" + }, + VI: { + alpha3: "VIR", + name: "Virgin Islands (U.S.)" + }, + WF: { + alpha3: "WLF", + name: "Wallis and Futuna" + }, + EH: { + alpha3: "ESH", + name: "Western Sahara*" + }, + YE: { + alpha3: "YEM", + name: "Yemen" + }, + ZM: { + alpha3: "ZMB", + name: "Zambia" + }, + ZW: { + alpha3: "ZWE", + name: "Zimbabwe" + }, + AX: { + alpha3: "ALA", + name: "Åland Islands" + } +}; diff --git a/src/lib/queries.ts b/src/lib/queries.ts index 3ddf32bf..aacd8fc6 100644 --- a/src/lib/queries.ts +++ b/src/lib/queries.ts @@ -3,6 +3,10 @@ import { durationToMs } from "./durationToMs"; import { build } from "@server/build"; import { remote } from "./api"; import type ResponseT from "@server/types/Response"; +import z from "zod"; +import type { AxiosInstance, AxiosResponse } from "axios"; +import type { QueryRequestAnalyticsResponse } from "@server/routers/auditLogs"; +import type { ListResourceNamesResponse } from "@server/routers/resource"; export type ProductUpdate = { link: string | null; @@ -65,3 +69,65 @@ export const productUpdatesQueries = { // because we don't need to listen for new versions there }) }; + +export const logAnalyticsFiltersSchema = z.object({ + timeStart: z + .string() + .refine((val) => !isNaN(Date.parse(val)), { + error: "timeStart must be a valid ISO date string" + }) + .optional(), + timeEnd: z + .string() + .refine((val) => !isNaN(Date.parse(val)), { + error: "timeEnd must be a valid ISO date string" + }) + .optional(), + resourceId: z + .string() + .optional() + .transform(Number) + .pipe(z.int().positive()) + .optional() +}); + +export type LogAnalyticsFilters = z.TypeOf; + +export const logQueries = { + requestAnalytics: ({ + orgId, + filters, + api + }: { + orgId: string; + filters: LogAnalyticsFilters; + api: AxiosInstance; + }) => + queryOptions({ + queryKey: ["REQUEST_LOG_ANALYTICS", orgId, filters] as const, + queryFn: async ({ signal }) => { + const res = await api.get< + AxiosResponse + >(`/org/${orgId}/logs/analytics`, { + params: filters, + signal + }); + return res.data.data; + } + }) +}; + +export const resourceQueries = { + listNamesPerOrg: (orgId: string, api: AxiosInstance) => + queryOptions({ + queryKey: ["RESOURCES_NAMES", orgId] as const, + queryFn: async ({ signal }) => { + const res = await api.get< + AxiosResponse + >(`/org/${orgId}/resource-names`, { + signal + }); + return res.data.data; + } + }) +};