Leo dev 5 napja
szülő
commit
2537609aea
5 módosított fájl, 446 hozzáadás és 139 törlés
  1. 364 6
      package-lock.json
  2. 5 2
      package.json
  3. 1 1
      src/app/api/servers/servers.ts
  4. 28 130
      src/app/servers/Map.tsx
  5. 48 0
      src/components/ui/popover.tsx

+ 364 - 6
package-lock.json

@@ -12,12 +12,14 @@
         "@radix-ui/react-context-menu": "^2.2.15",
         "@radix-ui/react-dialog": "^1.1.14",
         "@radix-ui/react-label": "^2.1.7",
+        "@radix-ui/react-popover": "^1.1.14",
         "@radix-ui/react-progress": "^1.1.7",
         "@radix-ui/react-separator": "^1.1.7",
         "@radix-ui/react-slot": "^1.2.3",
         "@radix-ui/react-tabs": "^1.1.12",
         "@radix-ui/react-tooltip": "^1.2.7",
         "@types/js-cookie": "^3.0.6",
+        "@types/react-map-gl": "^6.1.7",
         "axios": "^1.10.0",
         "class-variance-authority": "^0.7.1",
         "clsx": "^2.1.1",
@@ -27,14 +29,15 @@
         "next": "15.4.1",
         "next-themes": "^0.4.6",
         "react": "19.1.0",
-        "react-dom": "19.1.0",
+        "react-dom": "^19.1.0",
+        "react-map-gl": "^8.0.4",
         "recharts": "^2.15.4",
         "sonner": "^2.0.6",
         "tailwind-merge": "^3.3.1"
       },
       "devDependencies": {
         "@tailwindcss/postcss": "^4",
-        "@types/node": "^20.19.8",
+        "@types/node": "^20.19.9",
         "@types/react": "^19",
         "@types/react-dom": "^19",
         "tailwindcss": "^4",
@@ -1147,6 +1150,43 @@
         }
       }
     },
+    "node_modules/@radix-ui/react-popover": {
+      "version": "1.1.14",
+      "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.14.tgz",
+      "integrity": "sha512-ODz16+1iIbGUfFEfKx2HTPKizg2MN39uIOV8MXeHnmdd3i/N9Wt7vU46wbHsqA0xoaQyXVcs0KIlBdOA2Y95bw==",
+      "license": "MIT",
+      "dependencies": {
+        "@radix-ui/primitive": "1.1.2",
+        "@radix-ui/react-compose-refs": "1.1.2",
+        "@radix-ui/react-context": "1.1.2",
+        "@radix-ui/react-dismissable-layer": "1.1.10",
+        "@radix-ui/react-focus-guards": "1.1.2",
+        "@radix-ui/react-focus-scope": "1.1.7",
+        "@radix-ui/react-id": "1.1.1",
+        "@radix-ui/react-popper": "1.2.7",
+        "@radix-ui/react-portal": "1.1.9",
+        "@radix-ui/react-presence": "1.1.4",
+        "@radix-ui/react-primitive": "2.1.3",
+        "@radix-ui/react-slot": "1.2.3",
+        "@radix-ui/react-use-controllable-state": "1.2.2",
+        "aria-hidden": "^1.2.4",
+        "react-remove-scroll": "^2.6.3"
+      },
+      "peerDependencies": {
+        "@types/react": "*",
+        "@types/react-dom": "*",
+        "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+        "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        },
+        "@types/react-dom": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@radix-ui/react-popper": {
       "version": "1.2.7",
       "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.7.tgz",
@@ -1946,10 +1986,19 @@
         "@types/pbf": "*"
       }
     },
+    "node_modules/@types/mapbox-gl": {
+      "version": "3.4.1",
+      "resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-3.4.1.tgz",
+      "integrity": "sha512-NsGKKtgW93B+UaLPti6B7NwlxYlES5DpV5Gzj9F75rK5ALKsqSk15CiEHbOnTr09RGbr6ZYiCdI+59NNNcAImg==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/geojson": "*"
+      }
+    },
     "node_modules/@types/node": {
-      "version": "20.19.8",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.8.tgz",
-      "integrity": "sha512-HzbgCY53T6bfu4tT7Aq3TvViJyHjLjPNaAS3HOuMc9pw97KHsUtXNX4L+wu59g1WnjsZSko35MbEqnO58rihhw==",
+      "version": "20.19.9",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.9.tgz",
+      "integrity": "sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -1966,7 +2015,6 @@
       "version": "19.1.8",
       "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz",
       "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==",
-      "devOptional": true,
       "license": "MIT",
       "dependencies": {
         "csstype": "^3.0.2"
@@ -1982,6 +2030,18 @@
         "@types/react": "^19.0.0"
       }
     },
+    "node_modules/@types/react-map-gl": {
+      "version": "6.1.7",
+      "resolved": "https://registry.npmjs.org/@types/react-map-gl/-/react-map-gl-6.1.7.tgz",
+      "integrity": "sha512-szkfkWd3FbySDkxyn0MDj9yzD8XYk+RIi4od6sGb3lVHNBGcW20G2v2vcq2N5k18UYAdqAoKGSYuHkGV4JOCrA==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/geojson": "*",
+        "@types/mapbox-gl": "*",
+        "@types/react": "*",
+        "@types/viewport-mercator-project": "*"
+      }
+    },
     "node_modules/@types/supercluster": {
       "version": "7.1.3",
       "resolved": "https://registry.npmjs.org/@types/supercluster/-/supercluster-7.1.3.tgz",
@@ -1991,6 +2051,75 @@
         "@types/geojson": "*"
       }
     },
+    "node_modules/@types/viewport-mercator-project": {
+      "version": "6.1.6",
+      "resolved": "https://registry.npmjs.org/@types/viewport-mercator-project/-/viewport-mercator-project-6.1.6.tgz",
+      "integrity": "sha512-uWrbqhRXFeiT6CAvRjf0BkQKRkKED+ofrPhglKpUktQML3463dEPiA4iwe7cZQs6m49Zo/g03rL7ChMLiE5Z8w==",
+      "license": "MIT",
+      "dependencies": {
+        "gl-matrix": "^3.2.0"
+      }
+    },
+    "node_modules/@vis.gl/react-mapbox": {
+      "version": "8.0.4",
+      "resolved": "https://registry.npmjs.org/@vis.gl/react-mapbox/-/react-mapbox-8.0.4.tgz",
+      "integrity": "sha512-NFk0vsWcNzSs0YCsVdt2100Zli9QWR+pje8DacpLkkGEAXFaJsFtI1oKD0Hatiate4/iAIW39SQHhgfhbeEPfQ==",
+      "license": "MIT",
+      "peerDependencies": {
+        "mapbox-gl": ">=3.5.0",
+        "react": ">=16.3.0",
+        "react-dom": ">=16.3.0"
+      },
+      "peerDependenciesMeta": {
+        "mapbox-gl": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@vis.gl/react-maplibre": {
+      "version": "8.0.4",
+      "resolved": "https://registry.npmjs.org/@vis.gl/react-maplibre/-/react-maplibre-8.0.4.tgz",
+      "integrity": "sha512-HwZyfLjEu+y1mUFvwDAkVxinGm8fEegaWN+O8np/WZ2Sqe5Lv6OXFpV6GWz9LOEvBYMbGuGk1FQdejo+4HCJ5w==",
+      "license": "MIT",
+      "dependencies": {
+        "@maplibre/maplibre-gl-style-spec": "^19.2.1"
+      },
+      "peerDependencies": {
+        "maplibre-gl": ">=4.0.0",
+        "react": ">=16.3.0",
+        "react-dom": ">=16.3.0"
+      },
+      "peerDependenciesMeta": {
+        "maplibre-gl": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@vis.gl/react-maplibre/node_modules/@maplibre/maplibre-gl-style-spec": {
+      "version": "19.3.3",
+      "resolved": "https://registry.npmjs.org/@maplibre/maplibre-gl-style-spec/-/maplibre-gl-style-spec-19.3.3.tgz",
+      "integrity": "sha512-cOZZOVhDSulgK0meTsTkmNXb1ahVvmTmWmfx9gRBwc6hq98wS9JP35ESIoNq3xqEan+UN+gn8187Z6E4NKhLsw==",
+      "license": "ISC",
+      "dependencies": {
+        "@mapbox/jsonlint-lines-primitives": "~2.0.2",
+        "@mapbox/unitbezier": "^0.0.1",
+        "json-stringify-pretty-compact": "^3.0.0",
+        "minimist": "^1.2.8",
+        "rw": "^1.3.3",
+        "sort-object": "^3.0.3"
+      },
+      "bin": {
+        "gl-style-format": "dist/gl-style-format.mjs",
+        "gl-style-migrate": "dist/gl-style-migrate.mjs",
+        "gl-style-validate": "dist/gl-style-validate.mjs"
+      }
+    },
+    "node_modules/@vis.gl/react-maplibre/node_modules/json-stringify-pretty-compact": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-3.0.0.tgz",
+      "integrity": "sha512-Rc2suX5meI0S3bfdZuA7JMFBGkJ875ApfVyq2WHELjBiiG22My/l7/8zPpH/CfFVQHuVLd8NLR0nv6vi0BYYKA==",
+      "license": "MIT"
+    },
     "node_modules/aria-hidden": {
       "version": "1.2.6",
       "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
@@ -2003,6 +2132,24 @@
         "node": ">=10"
       }
     },
+    "node_modules/arr-union": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
+      "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/assign-symbols": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
+      "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/asynckit": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -2020,6 +2167,25 @@
         "proxy-from-env": "^1.1.0"
       }
     },
+    "node_modules/bytewise": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/bytewise/-/bytewise-1.1.0.tgz",
+      "integrity": "sha512-rHuuseJ9iQ0na6UDhnrRVDh8YnWVlU6xM3VH6q/+yHDeUH2zIhUzP+2/h3LIrhLDBtTqzWpE3p3tP/boefskKQ==",
+      "license": "MIT",
+      "dependencies": {
+        "bytewise-core": "^1.2.2",
+        "typewise": "^1.0.3"
+      }
+    },
+    "node_modules/bytewise-core": {
+      "version": "1.2.3",
+      "resolved": "https://registry.npmjs.org/bytewise-core/-/bytewise-core-1.2.3.tgz",
+      "integrity": "sha512-nZD//kc78OOxeYtRlVk8/zXqTB4gf/nlguL1ggWA8FuchMyOxcyHR4QPQZMUmA7czC+YnaBrPUCubqAWe50DaA==",
+      "license": "MIT",
+      "dependencies": {
+        "typewise-core": "^1.2"
+      }
+    },
     "node_modules/call-bind-apply-helpers": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
@@ -2400,6 +2566,18 @@
       "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
       "license": "MIT"
     },
+    "node_modules/extend-shallow": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+      "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
+      "license": "MIT",
+      "dependencies": {
+        "is-extendable": "^0.1.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/fast-equals": {
       "version": "5.2.2",
       "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz",
@@ -2518,6 +2696,15 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/get-value": {
+      "version": "2.0.6",
+      "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
+      "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/gl-matrix": {
       "version": "3.4.3",
       "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz",
@@ -2641,6 +2828,27 @@
       "license": "MIT",
       "optional": true
     },
+    "node_modules/is-extendable": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+      "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-plain-object": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+      "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+      "license": "MIT",
+      "dependencies": {
+        "isobject": "^3.0.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/isexe": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz",
@@ -2650,6 +2858,15 @@
         "node": ">=16"
       }
     },
+    "node_modules/isobject": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+      "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/jiti": {
       "version": "2.4.2",
       "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
@@ -3330,6 +3547,30 @@
       "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
       "license": "MIT"
     },
+    "node_modules/react-map-gl": {
+      "version": "8.0.4",
+      "resolved": "https://registry.npmjs.org/react-map-gl/-/react-map-gl-8.0.4.tgz",
+      "integrity": "sha512-SHdpvFIvswsZBg6BCPcwY/nbKuCo3sJM1Cj7Sd+gA3gFRFOixD+KtZ2XSuUWq2WySL2emYEXEgrLZoXsV4Ut4Q==",
+      "license": "MIT",
+      "dependencies": {
+        "@vis.gl/react-mapbox": "8.0.4",
+        "@vis.gl/react-maplibre": "8.0.4"
+      },
+      "peerDependencies": {
+        "mapbox-gl": ">=1.13.0",
+        "maplibre-gl": ">=1.13.0",
+        "react": ">=16.3.0",
+        "react-dom": ">=16.3.0"
+      },
+      "peerDependenciesMeta": {
+        "mapbox-gl": {
+          "optional": true
+        },
+        "maplibre-gl": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/react-remove-scroll": {
       "version": "2.7.1",
       "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz",
@@ -3496,6 +3737,21 @@
         "node": ">=10"
       }
     },
+    "node_modules/set-value": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz",
+      "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==",
+      "license": "MIT",
+      "dependencies": {
+        "extend-shallow": "^2.0.1",
+        "is-extendable": "^0.1.1",
+        "is-plain-object": "^2.0.3",
+        "split-string": "^3.0.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/sharp": {
       "version": "0.34.3",
       "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.3.tgz",
@@ -3559,6 +3815,41 @@
         "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
       }
     },
+    "node_modules/sort-asc": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/sort-asc/-/sort-asc-0.2.0.tgz",
+      "integrity": "sha512-umMGhjPeHAI6YjABoSTrFp2zaBtXBej1a0yKkuMUyjjqu6FJsTF+JYwCswWDg+zJfk/5npWUUbd33HH/WLzpaA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/sort-desc": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/sort-desc/-/sort-desc-0.2.0.tgz",
+      "integrity": "sha512-NqZqyvL4VPW+RAxxXnB8gvE1kyikh8+pR+T+CXLksVRN9eiQqkQlPwqWYU0mF9Jm7UnctShlxLyAt1CaBOTL1w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/sort-object": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/sort-object/-/sort-object-3.0.3.tgz",
+      "integrity": "sha512-nK7WOY8jik6zaG9CRwZTaD5O7ETWDLZYMM12pqY8htll+7dYeqGfEUPcUBHOpSJg2vJOrvFIY2Dl5cX2ih1hAQ==",
+      "license": "MIT",
+      "dependencies": {
+        "bytewise": "^1.1.0",
+        "get-value": "^2.0.2",
+        "is-extendable": "^0.1.1",
+        "sort-asc": "^0.2.0",
+        "sort-desc": "^0.2.0",
+        "union-value": "^1.0.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/source-map-js": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -3568,6 +3859,43 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/split-string": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
+      "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
+      "license": "MIT",
+      "dependencies": {
+        "extend-shallow": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/split-string/node_modules/extend-shallow": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+      "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==",
+      "license": "MIT",
+      "dependencies": {
+        "assign-symbols": "^1.0.0",
+        "is-extendable": "^1.0.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/split-string/node_modules/is-extendable": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+      "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+      "license": "MIT",
+      "dependencies": {
+        "is-plain-object": "^2.0.4"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/styled-jsx": {
       "version": "5.1.6",
       "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
@@ -3687,6 +4015,21 @@
         "node": ">=14.17"
       }
     },
+    "node_modules/typewise": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/typewise/-/typewise-1.0.3.tgz",
+      "integrity": "sha512-aXofE06xGhaQSPzt8hlTY+/YWQhm9P0jYUp1f2XtmW/3Bk0qzXcyFWAtPoo2uTGQj1ZwbDuSyuxicq+aDo8lCQ==",
+      "license": "MIT",
+      "dependencies": {
+        "typewise-core": "^1.2.0"
+      }
+    },
+    "node_modules/typewise-core": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/typewise-core/-/typewise-core-1.2.0.tgz",
+      "integrity": "sha512-2SCC/WLzj2SbUwzFOzqMCkz5amXLlxtJqDKTICqg30x+2DZxcfZN2MvQZmGfXWKNWaKK9pBPsvkcwv8bF/gxKg==",
+      "license": "MIT"
+    },
     "node_modules/undici-types": {
       "version": "6.21.0",
       "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
@@ -3694,6 +4037,21 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/union-value": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
+      "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==",
+      "license": "MIT",
+      "dependencies": {
+        "arr-union": "^3.1.0",
+        "get-value": "^2.0.6",
+        "is-extendable": "^0.1.1",
+        "set-value": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/use-callback-ref": {
       "version": "1.3.3",
       "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",

+ 5 - 2
package.json

@@ -13,12 +13,14 @@
     "@radix-ui/react-context-menu": "^2.2.15",
     "@radix-ui/react-dialog": "^1.1.14",
     "@radix-ui/react-label": "^2.1.7",
+    "@radix-ui/react-popover": "^1.1.14",
     "@radix-ui/react-progress": "^1.1.7",
     "@radix-ui/react-separator": "^1.1.7",
     "@radix-ui/react-slot": "^1.2.3",
     "@radix-ui/react-tabs": "^1.1.12",
     "@radix-ui/react-tooltip": "^1.2.7",
     "@types/js-cookie": "^3.0.6",
+    "@types/react-map-gl": "^6.1.7",
     "axios": "^1.10.0",
     "class-variance-authority": "^0.7.1",
     "clsx": "^2.1.1",
@@ -28,14 +30,15 @@
     "next": "15.4.1",
     "next-themes": "^0.4.6",
     "react": "19.1.0",
-    "react-dom": "19.1.0",
+    "react-dom": "^19.1.0",
+    "react-map-gl": "^8.0.4",
     "recharts": "^2.15.4",
     "sonner": "^2.0.6",
     "tailwind-merge": "^3.3.1"
   },
   "devDependencies": {
     "@tailwindcss/postcss": "^4",
-    "@types/node": "^20.19.8",
+    "@types/node": "^20.19.9",
     "@types/react": "^19",
     "@types/react-dom": "^19",
     "tailwindcss": "^4",

+ 1 - 1
src/app/api/servers/servers.ts

@@ -48,4 +48,4 @@ export var currentServers: Server[] = servers.data.map(
     } as Server)
 );
 
-servers.data.map(useServer);
+servers.data.forEach(useServer);

+ 28 - 130
src/app/servers/Map.tsx

@@ -1,132 +1,30 @@
-import React, { useRef, useEffect, useState } from "react";
-import maplibregl from "maplibre-gl";
-import "maplibre-gl/dist/maplibre-gl.css";
-import Server from "@/types/server";
-import {
-  Sheet,
-  SheetContent,
-  SheetDescription,
-  SheetHeader,
-  SheetTitle,
-  SheetTrigger,
-} from "@/components/ui/sheet";
-import ServerComponent, { ByteUnit, UsageBar } from "../dashboard/server";
-import {
-  Cpu,
-  HardDrive,
-  MapPin,
-  MemoryStick,
-  Network,
-  ServerIcon,
-} from "lucide-react";
-
-export default function Map({ servers }: { servers: Server[] }) {
-  const mapContainer = useRef<HTMLDivElement | null>(null);
-  const map = useRef<maplibregl.Map | null>(null);
-  const markers = useRef<maplibregl.Marker[]>([]);
-  const [sheet, setSheet] = useState<number | undefined>();
-
-  // Initialize map only once
-  useEffect(() => {
-    if (map.current || !mapContainer.current) return;
-
-    map.current = new maplibregl.Map({
-      container: mapContainer.current,
-      style: "/map_style.json",
-      center: [0, 0],
-      zoom: 2,
-    });
-
-    return () => {
-      // Clean up markers
-      markers.current.forEach((m) => m.remove());
-      markers.current = [];
-
-      // Clean up map
-      map.current?.remove();
-      map.current = null;
-    };
-  }, []);
-
-  // Update markers when servers change
-  useEffect(() => {
-    if (!map.current) return;
-
-    // Remove old markers
-    markers.current.forEach((marker) => marker.remove());
-    markers.current = [];
-
-    // Add new markers
-    servers.forEach((server, i) => {
-      if (server.coordinates) {
-        const marker = new maplibregl.Marker({ color: "#ff3e00" })
-          .setLngLat(server.coordinates)
-          .addTo(map.current!);
-
-        marker.getElement().addEventListener("click", () => {
-          setSheet(i);
-        });
-
-        markers.current.push(marker);
-      }
-    });
-  }, [servers]);
-
+import Map, { Marker } from 'react-map-gl/maplibre';
+import 'maplibre-gl/dist/maplibre-gl.css';
+import Server from '@/types/server';
+import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
+import { MapPin } from 'lucide-react';
+import ServerComponent from '../dashboard/server';
+
+export default function ServerMap({ servers }: { servers: Server[] }) {
   return (
-    <div ref={mapContainer} className="w-full h-full">
-      <Sheet open={sheet != undefined} onOpenChange={() => setSheet(undefined)}>
-        {sheet != undefined && (
-          <SheetContent>
-            <SheetHeader>
-              <div className="flex items-center gap-2">
-                <div className="bg-muted text-muted-foreground p-1.5 rounded-md">
-                  <ServerIcon className="w-4.5 h-4.5 text-current my-auto" />
-                </div>
-                <div>
-                  <SheetTitle>{servers[sheet].name}</SheetTitle>
-                  <SheetDescription className="flex gap-2">
-                    {servers[sheet].status[0].toUpperCase() + servers[sheet].status.slice(1)}{" "}
-                    {" · "}
-                    {servers[sheet].ip}
-                    {servers[sheet].location && (
-                      <span className="flex items-center gap-0.5">
-                        <MapPin size={14} />
-                        {servers[sheet].location}
-                      </span>
-                    )}
-                  </SheetDescription>
-                </div>
-              </div>
-            </SheetHeader>
-            <div className="px-4 gap-4 flex flex-col">
-              <UsageBar
-                value={servers[sheet].cpu}
-                Icon={Cpu}
-                text="CPU"
-                color="var(--chart-2)"
-              />
-              <UsageBar
-                value={servers[sheet].memory}
-                Icon={MemoryStick}
-                text="Memory"
-                color="var(--chart-1)"
-              />
-              <UsageBar
-                value={servers[sheet].storage}
-                Icon={HardDrive}
-                text="Storage"
-                color="var(--chart-5)"
-              />
-              <ByteUnit
-                value={servers[sheet].network}
-                Icon={Network}
-                text="Network"
-                color="var(--chart-3)"
-              />
-            </div>
-          </SheetContent>
-        )}
-      </Sheet>
-    </div>
+    <Map
+      style={{width: "100%", height: "100%"}}
+      mapStyle="/map_style.json"
+    >
+      {
+        servers.filter(s => s.coordinates).map((s, i) => (
+          <Marker longitude={s.coordinates?.[0] || 0} latitude={s.coordinates?.[1] || 0} color="red" key={i}>
+            <Popover>
+              <PopoverTrigger>
+                <MapPin />
+              </PopoverTrigger>
+              <PopoverContent className='w-96'>
+                <ServerComponent server={s} index={i} session={null} />
+              </PopoverContent>
+            </Popover>
+          </Marker>
+        ))
+      }
+    </Map>
   );
-}
+}

+ 48 - 0
src/components/ui/popover.tsx

@@ -0,0 +1,48 @@
+"use client"
+
+import * as React from "react"
+import * as PopoverPrimitive from "@radix-ui/react-popover"
+
+import { cn } from "@/lib/utils"
+
+function Popover({
+  ...props
+}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
+  return <PopoverPrimitive.Root data-slot="popover" {...props} />
+}
+
+function PopoverTrigger({
+  ...props
+}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
+  return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
+}
+
+function PopoverContent({
+  className,
+  align = "center",
+  sideOffset = 4,
+  ...props
+}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
+  return (
+    <PopoverPrimitive.Portal>
+      <PopoverPrimitive.Content
+        data-slot="popover-content"
+        align={align}
+        sideOffset={sideOffset}
+        className={cn(
+          "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
+          className
+        )}
+        {...props}
+      />
+    </PopoverPrimitive.Portal>
+  )
+}
+
+function PopoverAnchor({
+  ...props
+}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
+  return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
+}
+
+export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }