Compare commits
3 Commits
14d11cd99d
...
b06cb4606f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b06cb4606f | ||
|
|
de32552159 | ||
|
|
99771a88c5 |
25
.env
25
.env
@@ -1,25 +0,0 @@
|
|||||||
# Database
|
|
||||||
POSTGRES_SERVER=localhost
|
|
||||||
POSTGRES_USER=postgres
|
|
||||||
POSTGRES_PASSWORD=postgres
|
|
||||||
POSTGRES_DB=planet_db
|
|
||||||
DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/planet_db
|
|
||||||
|
|
||||||
# Redis
|
|
||||||
REDIS_SERVER=localhost
|
|
||||||
REDIS_PORT=6379
|
|
||||||
REDIS_URL=redis://localhost:6379/0
|
|
||||||
|
|
||||||
# Security
|
|
||||||
SECRET_KEY=your-secret-key-change-in-production
|
|
||||||
ALGORITHM=HS256
|
|
||||||
ACCESS_TOKEN_EXPIRE_MINUTES=15
|
|
||||||
REFRESH_TOKEN_EXPIRE_DAYS=7
|
|
||||||
|
|
||||||
# API
|
|
||||||
API_V1_STR=/api/v1
|
|
||||||
PROJECT_NAME="Intelligent Planet Plan"
|
|
||||||
VERSION=1.0.0
|
|
||||||
|
|
||||||
# CORS
|
|
||||||
CORS_ORIGINS=["http://localhost:3000", "http://localhost:8000"]
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -10,6 +10,7 @@ from app.models.user import User
|
|||||||
from app.core.security import get_current_user
|
from app.core.security import get_current_user
|
||||||
from app.models.alert import Alert, AlertSeverity, AlertStatus
|
from app.models.alert import Alert, AlertSeverity, AlertStatus
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ from app.models.task import CollectionTask
|
|||||||
from app.core.security import get_current_user
|
from app.core.security import get_current_user
|
||||||
from app.core.cache import cache
|
from app.core.cache import cache
|
||||||
|
|
||||||
|
|
||||||
# Built-in collectors info (mirrored from datasources.py)
|
# Built-in collectors info (mirrored from datasources.py)
|
||||||
COLLECTOR_INFO = {
|
COLLECTOR_INFO = {
|
||||||
"top500": {
|
"top500": {
|
||||||
|
|||||||
@@ -307,3 +307,40 @@ async def test_new_config(
|
|||||||
"error": "Connection failed",
|
"error": "Connection failed",
|
||||||
"message": str(e),
|
"message": str(e),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/configs/all")
|
||||||
|
async def list_all_datasources(
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""List all data sources: YAML defaults + DB overrides"""
|
||||||
|
from app.core.data_sources import COLLECTOR_URL_KEYS, get_data_sources_config
|
||||||
|
|
||||||
|
config = get_data_sources_config()
|
||||||
|
|
||||||
|
db_query = await db.execute(select(DataSourceConfig))
|
||||||
|
db_configs = {c.name: c for c in db_query.scalars().all()}
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for name, yaml_key in COLLECTOR_URL_KEYS.items():
|
||||||
|
yaml_url = config.get_yaml_url(name)
|
||||||
|
db_config = db_configs.get(name)
|
||||||
|
|
||||||
|
result.append(
|
||||||
|
{
|
||||||
|
"name": name,
|
||||||
|
"default_url": yaml_url,
|
||||||
|
"endpoint": db_config.endpoint if db_config else yaml_url,
|
||||||
|
"is_overridden": db_config is not None and db_config.endpoint != yaml_url
|
||||||
|
if yaml_url
|
||||||
|
else db_config is not None,
|
||||||
|
"is_active": db_config.is_active if db_config else True,
|
||||||
|
"source_type": db_config.source_type if db_config else "http",
|
||||||
|
"description": db_config.description
|
||||||
|
if db_config
|
||||||
|
else f"Data source from YAML: {yaml_key}",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"total": len(result), "data": result}
|
||||||
|
|||||||
@@ -99,8 +99,22 @@ COLLECTOR_INFO = {
|
|||||||
"priority": "P1",
|
"priority": "P1",
|
||||||
"frequency_hours": 168,
|
"frequency_hours": 168,
|
||||||
},
|
},
|
||||||
"fao_landing_points": {
|
"arcgis_landing_points": {
|
||||||
"id": 16,
|
"id": 16,
|
||||||
|
"name": "ArcGIS Landing Points",
|
||||||
|
"module": "L2",
|
||||||
|
"priority": "P1",
|
||||||
|
"frequency_hours": 168,
|
||||||
|
},
|
||||||
|
"arcgis_cable_landing_relation": {
|
||||||
|
"id": 17,
|
||||||
|
"name": "ArcGIS Cable-Landing Relations",
|
||||||
|
"module": "L2",
|
||||||
|
"priority": "P1",
|
||||||
|
"frequency_hours": 168,
|
||||||
|
},
|
||||||
|
"fao_landing_points": {
|
||||||
|
"id": 18,
|
||||||
"name": "FAO Landing Points",
|
"name": "FAO Landing Points",
|
||||||
"module": "L2",
|
"module": "L2",
|
||||||
"priority": "P1",
|
"priority": "P1",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from app.models.user import User
|
|||||||
from app.core.security import get_current_user
|
from app.core.security import get_current_user
|
||||||
from app.services.collectors.registry import collector_registry
|
from app.services.collectors.registry import collector_registry
|
||||||
|
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -146,14 +146,14 @@ async def get_cables_geojson(db: AsyncSession = Depends(get_db)):
|
|||||||
async def get_landing_points_geojson(db: AsyncSession = Depends(get_db)):
|
async def get_landing_points_geojson(db: AsyncSession = Depends(get_db)):
|
||||||
"""获取登陆点 GeoJSON 数据 (Point)"""
|
"""获取登陆点 GeoJSON 数据 (Point)"""
|
||||||
try:
|
try:
|
||||||
stmt = select(CollectedData).where(CollectedData.source == "fao_landing_points")
|
stmt = select(CollectedData).where(CollectedData.source == "arcgis_landing_points")
|
||||||
result = await db.execute(stmt)
|
result = await db.execute(stmt)
|
||||||
records = result.scalars().all()
|
records = result.scalars().all()
|
||||||
|
|
||||||
if not records:
|
if not records:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=404,
|
status_code=404,
|
||||||
detail="No landing point data found. Please run the fao_landing_points collector first.",
|
detail="No landing point data found. Please run the arcgis_landing_points collector first.",
|
||||||
)
|
)
|
||||||
|
|
||||||
return convert_landing_point_to_geojson(records)
|
return convert_landing_point_to_geojson(records)
|
||||||
@@ -170,7 +170,7 @@ async def get_all_geojson(db: AsyncSession = Depends(get_db)):
|
|||||||
cables_result = await db.execute(cables_stmt)
|
cables_result = await db.execute(cables_stmt)
|
||||||
cables_records = cables_result.scalars().all()
|
cables_records = cables_result.scalars().all()
|
||||||
|
|
||||||
points_stmt = select(CollectedData).where(CollectedData.source == "fao_landing_points")
|
points_stmt = select(CollectedData).where(CollectedData.source == "arcgis_landing_points")
|
||||||
points_result = await db.execute(points_stmt)
|
points_result = await db.execute(points_stmt)
|
||||||
points_records = points_result.scalars().all()
|
points_records = points_result.scalars().all()
|
||||||
|
|
||||||
@@ -208,7 +208,7 @@ async def get_cable_graph(db: AsyncSession) -> CableGraph:
|
|||||||
cables_result = await db.execute(cables_stmt)
|
cables_result = await db.execute(cables_stmt)
|
||||||
cables_records = list(cables_result.scalars().all())
|
cables_records = list(cables_result.scalars().all())
|
||||||
|
|
||||||
points_stmt = select(CollectedData).where(CollectedData.source == "fao_landing_points")
|
points_stmt = select(CollectedData).where(CollectedData.source == "arcgis_landing_points")
|
||||||
points_result = await db.execute(points_stmt)
|
points_result = await db.execute(points_stmt)
|
||||||
points_records = list(points_result.scalars().all())
|
points_records = list(points_result.scalars().all())
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
78
backend/app/core/data_sources.py
Normal file
78
backend/app/core/data_sources.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import os
|
||||||
|
import yaml
|
||||||
|
from functools import lru_cache
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
COLLECTOR_URL_KEYS = {
|
||||||
|
"arcgis_cables": "arcgis.cable_url",
|
||||||
|
"arcgis_landing_points": "arcgis.landing_point_url",
|
||||||
|
"arcgis_cable_landing_relation": "arcgis.cable_landing_relation_url",
|
||||||
|
"fao_landing_points": "fao.landing_point_url",
|
||||||
|
"telegeography_cables": "telegeography.cable_url",
|
||||||
|
"telegeography_landing": "telegeography.landing_point_url",
|
||||||
|
"huggingface_models": "huggingface.models_url",
|
||||||
|
"huggingface_datasets": "huggingface.datasets_url",
|
||||||
|
"huggingface_spaces": "huggingface.spaces_url",
|
||||||
|
"cloudflare_radar_device": "cloudflare.radar_device_url",
|
||||||
|
"cloudflare_radar_traffic": "cloudflare.radar_traffic_url",
|
||||||
|
"cloudflare_radar_top_locations": "cloudflare.radar_top_locations_url",
|
||||||
|
"peeringdb_ixp": "peeringdb.ixp_url",
|
||||||
|
"peeringdb_network": "peeringdb.network_url",
|
||||||
|
"peeringdb_facility": "peeringdb.facility_url",
|
||||||
|
"top500": "top500.url",
|
||||||
|
"epoch_ai_gpu": "epoch_ai.gpu_clusters_url",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DataSourcesConfig:
|
||||||
|
def __init__(self, config_path: str = None):
|
||||||
|
if config_path is None:
|
||||||
|
config_path = os.path.join(os.path.dirname(__file__), "data_sources.yaml")
|
||||||
|
|
||||||
|
self._yaml_config = {}
|
||||||
|
if os.path.exists(config_path):
|
||||||
|
with open(config_path, "r") as f:
|
||||||
|
self._yaml_config = yaml.safe_load(f) or {}
|
||||||
|
|
||||||
|
def get_yaml_url(self, collector_name: str) -> str:
|
||||||
|
key = COLLECTOR_URL_KEYS.get(collector_name, "")
|
||||||
|
if not key:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
parts = key.split(".")
|
||||||
|
value = self._yaml_config
|
||||||
|
for part in parts:
|
||||||
|
if isinstance(value, dict):
|
||||||
|
value = value.get(part, "")
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
return value if isinstance(value, str) else ""
|
||||||
|
|
||||||
|
async def get_url(self, collector_name: str, db) -> str:
|
||||||
|
yaml_url = self.get_yaml_url(collector_name)
|
||||||
|
|
||||||
|
if not db:
|
||||||
|
return yaml_url
|
||||||
|
|
||||||
|
try:
|
||||||
|
from sqlalchemy import select
|
||||||
|
from app.models.datasource_config import DataSourceConfig
|
||||||
|
|
||||||
|
query = select(DataSourceConfig).where(
|
||||||
|
DataSourceConfig.name == collector_name, DataSourceConfig.is_active == True
|
||||||
|
)
|
||||||
|
result = await db.execute(query)
|
||||||
|
db_config = result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if db_config and db_config.endpoint:
|
||||||
|
return db_config.endpoint
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return yaml_url
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache()
|
||||||
|
def get_data_sources_config() -> DataSourcesConfig:
|
||||||
|
return DataSourcesConfig()
|
||||||
35
backend/app/core/data_sources.yaml
Normal file
35
backend/app/core/data_sources.yaml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Data Sources Configuration
|
||||||
|
# All external data source URLs should be configured here
|
||||||
|
|
||||||
|
arcgis:
|
||||||
|
cable_url: "https://services.arcgis.com/6DIQcwlPy8knb6sg/ArcGIS/rest/services/SubmarineCables/FeatureServer/2/query"
|
||||||
|
landing_point_url: "https://services.arcgis.com/6DIQcwlPy8knb6sg/ArcGIS/rest/services/SubmarineCables/FeatureServer/1/query"
|
||||||
|
cable_landing_relation_url: "https://services.arcgis.com/6DIQcwlPy8knb6sg/ArcGIS/rest/services/SubmarineCables/FeatureServer/3/query"
|
||||||
|
|
||||||
|
fao:
|
||||||
|
landing_point_url: "https://data.apps.fao.org/catalog/dataset/1b75ff21-92f2-4b96-9b7b-98e8aa65ad5d/resource/b6071077-d1d4-4e97-aa00-42e902847c87/download/landing-point-geo.csv"
|
||||||
|
|
||||||
|
telegeography:
|
||||||
|
cable_url: "https://raw.githubusercontent.com/lintaojlu/submarine_cable_information/main/cable.json"
|
||||||
|
landing_point_url: "https://raw.githubusercontent.com/lintaojlu/submarine_cable_information/main/landing_point.json"
|
||||||
|
|
||||||
|
huggingface:
|
||||||
|
models_url: "https://huggingface.co/api/models"
|
||||||
|
datasets_url: "https://huggingface.co/api/datasets"
|
||||||
|
spaces_url: "https://huggingface.co/api/spaces"
|
||||||
|
|
||||||
|
cloudflare:
|
||||||
|
radar_device_url: "https://api.cloudflare.com/client/v4/radar/http/summary/device_type"
|
||||||
|
radar_traffic_url: "https://api.cloudflare.com/client/v4/radar/http/timeseries/requests"
|
||||||
|
radar_top_locations_url: "https://api.cloudflare.com/client/v4/radar/http/top/locations"
|
||||||
|
|
||||||
|
peeringdb:
|
||||||
|
ixp_url: "https://www.peeringdb.com/api/ix"
|
||||||
|
network_url: "https://www.peeringdb.com/api/net"
|
||||||
|
facility_url: "https://www.peeringdb.com/api/fac"
|
||||||
|
|
||||||
|
top500:
|
||||||
|
url: "https://top500.org/lists/top500/list/2025/11/"
|
||||||
|
|
||||||
|
epoch_ai:
|
||||||
|
gpu_clusters_url: "https://epoch.ai/data/gpu-clusters"
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -7,6 +7,7 @@ from typing import Dict, Any, Optional
|
|||||||
from app.core.websocket.manager import manager
|
from app.core.websocket.manager import manager
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class DataBroadcaster:
|
class DataBroadcaster:
|
||||||
"""Periodically broadcasts data to connected WebSocket clients"""
|
"""Periodically broadcasts data to connected WebSocket clients"""
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -9,6 +9,8 @@ from datetime import datetime
|
|||||||
import httpx
|
import httpx
|
||||||
|
|
||||||
from app.services.collectors.base import BaseCollector
|
from app.services.collectors.base import BaseCollector
|
||||||
|
from app.core.data_sources import get_data_sources_config
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ArcGISCableCollector(BaseCollector):
|
class ArcGISCableCollector(BaseCollector):
|
||||||
@@ -18,7 +20,14 @@ class ArcGISCableCollector(BaseCollector):
|
|||||||
frequency_hours = 168
|
frequency_hours = 168
|
||||||
data_type = "submarine_cable"
|
data_type = "submarine_cable"
|
||||||
|
|
||||||
base_url = "https://services.arcgis.com/6DIQcwlPy8knb6sg/arcgis/rest/services/SubmarineCables/FeatureServer/2/query"
|
@property
|
||||||
|
def base_url(self) -> str:
|
||||||
|
if self._resolved_url:
|
||||||
|
return self._resolved_url
|
||||||
|
from app.core.data_sources import get_data_sources_config
|
||||||
|
|
||||||
|
config = get_data_sources_config()
|
||||||
|
return config.get_yaml_url("arcgis_cables")
|
||||||
|
|
||||||
async def fetch(self) -> List[Dict[str, Any]]:
|
async def fetch(self) -> List[Dict[str, Any]]:
|
||||||
params = {"where": "1=1", "outFields": "*", "returnGeometry": "true", "f": "geojson"}
|
params = {"where": "1=1", "outFields": "*", "returnGeometry": "true", "f": "geojson"}
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
"""ArcGIS Landing Points Collector
|
|
||||||
|
|
||||||
Collects landing point data from ArcGIS GeoJSON API.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from typing import Dict, Any, List
|
from typing import Dict, Any, List
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import httpx
|
||||||
|
|
||||||
from app.services.collectors.base import BaseCollector
|
from app.services.collectors.base import BaseCollector
|
||||||
|
from app.core.data_sources import get_data_sources_config
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ArcGISLandingPointCollector(BaseCollector):
|
class ArcGISLandingPointCollector(BaseCollector):
|
||||||
@@ -16,21 +14,23 @@ class ArcGISLandingPointCollector(BaseCollector):
|
|||||||
frequency_hours = 168
|
frequency_hours = 168
|
||||||
data_type = "landing_point"
|
data_type = "landing_point"
|
||||||
|
|
||||||
base_url = "https://services.arcgis.com/6DIQcwlPy8knb6sg/arcgis/rest/services/SubmarineCables/FeatureServer/1/query"
|
@property
|
||||||
|
def base_url(self) -> str:
|
||||||
|
if self._resolved_url:
|
||||||
|
return self._resolved_url
|
||||||
|
from app.core.data_sources import get_data_sources_config
|
||||||
|
|
||||||
|
config = get_data_sources_config()
|
||||||
|
return config.get_yaml_url("arcgis_landing_points")
|
||||||
|
|
||||||
async def fetch(self) -> List[Dict[str, Any]]:
|
async def fetch(self) -> List[Dict[str, Any]]:
|
||||||
params = {"where": "1=1", "outFields": "*", "returnGeometry": "true", "f": "geojson"}
|
params = {"where": "1=1", "outFields": "*", "returnGeometry": "true", "f": "geojson"}
|
||||||
|
|
||||||
async with self._get_client() as client:
|
async with httpx.AsyncClient(timeout=60.0) as client:
|
||||||
response = await client.get(self.base_url, params=params)
|
response = await client.get(self.base_url, params=params)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
return self.parse_response(response.json())
|
return self.parse_response(response.json())
|
||||||
|
|
||||||
def _get_client(self):
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
return httpx.AsyncClient(timeout=60.0)
|
|
||||||
|
|
||||||
def parse_response(self, data: Dict[str, Any]) -> List[Dict[str, Any]]:
|
def parse_response(self, data: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||||
result = []
|
result = []
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
from typing import Dict, Any, List
|
from typing import Dict, Any, List
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import httpx
|
||||||
|
|
||||||
from app.services.collectors.base import BaseCollector
|
from app.services.collectors.base import BaseCollector
|
||||||
|
from app.core.data_sources import get_data_sources_config
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ArcGISCableLandingRelationCollector(BaseCollector):
|
class ArcGISCableLandingRelationCollector(BaseCollector):
|
||||||
@@ -11,11 +14,16 @@ class ArcGISCableLandingRelationCollector(BaseCollector):
|
|||||||
frequency_hours = 168
|
frequency_hours = 168
|
||||||
data_type = "cable_landing_relation"
|
data_type = "cable_landing_relation"
|
||||||
|
|
||||||
base_url = "https://services.arcgis.com/6DIQcwlPy8knb6sg/arcgis/rest/services/SubmarineCables/FeatureServer/3/query"
|
@property
|
||||||
|
def base_url(self) -> str:
|
||||||
|
if self._resolved_url:
|
||||||
|
return self._resolved_url
|
||||||
|
from app.core.data_sources import get_data_sources_config
|
||||||
|
|
||||||
|
config = get_data_sources_config()
|
||||||
|
return config.get_yaml_url("arcgis_cable_landing_relation")
|
||||||
|
|
||||||
async def fetch(self) -> List[Dict[str, Any]]:
|
async def fetch(self) -> List[Dict[str, Any]]:
|
||||||
import httpx
|
|
||||||
|
|
||||||
params = {"where": "1=1", "outFields": "*", "returnGeometry": "true", "f": "geojson"}
|
params = {"where": "1=1", "outFields": "*", "returnGeometry": "true", "f": "geojson"}
|
||||||
|
|
||||||
async with httpx.AsyncClient(timeout=60.0) as client:
|
async with httpx.AsyncClient(timeout=60.0) as client:
|
||||||
|
|||||||
@@ -23,6 +23,13 @@ class BaseCollector(ABC):
|
|||||||
self._current_task = None
|
self._current_task = None
|
||||||
self._db_session = None
|
self._db_session = None
|
||||||
self._datasource_id = 1
|
self._datasource_id = 1
|
||||||
|
self._resolved_url: Optional[str] = None
|
||||||
|
|
||||||
|
async def resolve_url(self, db: AsyncSession) -> None:
|
||||||
|
from app.core.data_sources import get_data_sources_config
|
||||||
|
|
||||||
|
config = get_data_sources_config()
|
||||||
|
self._resolved_url = await config.get_url(self.name, db)
|
||||||
|
|
||||||
def update_progress(self, records_processed: int):
|
def update_progress(self, records_processed: int):
|
||||||
"""Update task progress - call this during data processing"""
|
"""Update task progress - call this during data processing"""
|
||||||
@@ -65,6 +72,8 @@ class BaseCollector(ABC):
|
|||||||
self._current_task = task
|
self._current_task = task
|
||||||
self._db_session = db
|
self._db_session = db
|
||||||
|
|
||||||
|
await self.resolve_url(db)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
raw_data = await self.fetch()
|
raw_data = await self.fetch()
|
||||||
task.total_records = len(raw_data)
|
task.total_records = len(raw_data)
|
||||||
@@ -87,7 +96,6 @@ class BaseCollector(ABC):
|
|||||||
"execution_time_seconds": (datetime.utcnow() - start_time).total_seconds(),
|
"execution_time_seconds": (datetime.utcnow() - start_time).total_seconds(),
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Log task failure
|
|
||||||
task.status = "failed"
|
task.status = "failed"
|
||||||
task.error_message = str(e)
|
task.error_message = str(e)
|
||||||
task.completed_at = datetime.utcnow()
|
task.completed_at = datetime.utcnow()
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from datetime import datetime
|
|||||||
import httpx
|
import httpx
|
||||||
from app.services.collectors.base import HTTPCollector
|
from app.services.collectors.base import HTTPCollector
|
||||||
|
|
||||||
|
|
||||||
# Cloudflare API token (optional - for higher rate limits)
|
# Cloudflare API token (optional - for higher rate limits)
|
||||||
CLOUDFLARE_API_TOKEN = os.environ.get("CLOUDFLARE_API_TOKEN", "")
|
CLOUDFLARE_API_TOKEN = os.environ.get("CLOUDFLARE_API_TOKEN", "")
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import httpx
|
|||||||
from app.services.collectors.base import BaseCollector
|
from app.services.collectors.base import BaseCollector
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class EpochAIGPUCollector(BaseCollector):
|
class EpochAIGPUCollector(BaseCollector):
|
||||||
name = "epoch_ai_gpu"
|
name = "epoch_ai_gpu"
|
||||||
priority = "P0"
|
priority = "P0"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import httpx
|
|||||||
from app.services.collectors.base import BaseCollector
|
from app.services.collectors.base import BaseCollector
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class FAOLandingPointCollector(BaseCollector):
|
class FAOLandingPointCollector(BaseCollector):
|
||||||
name = "fao_landing_points"
|
name = "fao_landing_points"
|
||||||
priority = "P1"
|
priority = "P1"
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from datetime import datetime
|
|||||||
from app.services.collectors.base import HTTPCollector
|
from app.services.collectors.base import HTTPCollector
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class HuggingFaceModelCollector(HTTPCollector):
|
class HuggingFaceModelCollector(HTTPCollector):
|
||||||
name = "huggingface_models"
|
name = "huggingface_models"
|
||||||
priority = "P1"
|
priority = "P1"
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ from datetime import datetime
|
|||||||
import httpx
|
import httpx
|
||||||
from app.services.collectors.base import HTTPCollector
|
from app.services.collectors.base import HTTPCollector
|
||||||
|
|
||||||
|
|
||||||
# PeeringDB API key - read from environment variable
|
# PeeringDB API key - read from environment variable
|
||||||
PEERINGDB_API_KEY = os.environ.get("PEERINGDB_API_KEY", "")
|
PEERINGDB_API_KEY = os.environ.get("PEERINGDB_API_KEY", "")
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import httpx
|
|||||||
from app.services.collectors.base import BaseCollector
|
from app.services.collectors.base import BaseCollector
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TeleGeographyCableCollector(BaseCollector):
|
class TeleGeographyCableCollector(BaseCollector):
|
||||||
name = "telegeography_cables"
|
name = "telegeography_cables"
|
||||||
priority = "P1"
|
priority = "P1"
|
||||||
|
|||||||
@@ -30,7 +30,9 @@ COLLECTOR_TO_ID = {
|
|||||||
"telegeography_landing": 10,
|
"telegeography_landing": 10,
|
||||||
"telegeography_systems": 11,
|
"telegeography_systems": 11,
|
||||||
"arcgis_cables": 15,
|
"arcgis_cables": 15,
|
||||||
"fao_landing_points": 16,
|
"arcgis_landing_points": 16,
|
||||||
|
"arcgis_cable_landing_relation": 17,
|
||||||
|
"fao_landing_points": 18,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -128,7 +128,7 @@ export async function loadGeoJSONFromPath(scene, earthObj) {
|
|||||||
console.log('正在加载电缆数据...');
|
console.log('正在加载电缆数据...');
|
||||||
showStatusMessage('正在加载电缆数据...', 'warning');
|
showStatusMessage('正在加载电缆数据...', 'warning');
|
||||||
|
|
||||||
const response = await fetch(PATHS.geoJSON);
|
const response = await fetch(PATHS.cablesApi);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP错误: ${response.status}`);
|
throw new Error(`HTTP错误: ${response.status}`);
|
||||||
}
|
}
|
||||||
@@ -161,7 +161,7 @@ export async function loadGeoJSONFromPath(scene, earthObj) {
|
|||||||
if (!geometry || !geometry.coordinates) continue;
|
if (!geometry || !geometry.coordinates) continue;
|
||||||
|
|
||||||
const color = getCableColor(properties);
|
const color = getCableColor(properties);
|
||||||
console.log('电缆:', properties.Name, '颜色:', color);
|
console.log('电缆 properties:', JSON.stringify(properties));
|
||||||
|
|
||||||
if (geometry.type === 'MultiLineString') {
|
if (geometry.type === 'MultiLineString') {
|
||||||
for (const lineCoords of geometry.coordinates) {
|
for (const lineCoords of geometry.coordinates) {
|
||||||
@@ -239,7 +239,7 @@ export async function loadLandingPoints(scene, earthObj) {
|
|||||||
try {
|
try {
|
||||||
console.log('正在加载登陆点数据...');
|
console.log('正在加载登陆点数据...');
|
||||||
|
|
||||||
const response = await fetch('./landing-point-geo.geojson');
|
const response = await fetch(PATHS.landingPointsApi);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error('HTTP错误:', response.status);
|
console.error('HTTP错误:', response.status);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ export const CONFIG = {
|
|||||||
rotationSpeed: 0.002,
|
rotationSpeed: 0.002,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Paths
|
|
||||||
export const PATHS = {
|
export const PATHS = {
|
||||||
|
cablesApi: '/api/v1/visualization/geo/cables',
|
||||||
|
landingPointsApi: '/api/v1/visualization/geo/landing-points',
|
||||||
geoJSON: './geo.json',
|
geoJSON: './geo.json',
|
||||||
|
landingPointsStatic: './landing-point-geo.geojson',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Cable colors mapping
|
// Cable colors mapping
|
||||||
|
|||||||
@@ -171,7 +171,6 @@ function onMouseMove(event, camera) {
|
|||||||
c.material.opacity = 1;
|
c.material.opacity = 1;
|
||||||
});
|
});
|
||||||
hoveredCable = cable;
|
hoveredCable = cable;
|
||||||
hoveredCable = cable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const userData = cable.userData;
|
const userData = cable.userData;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
function Earth() {
|
function Earth() {
|
||||||
return (
|
return (
|
||||||
<iframe
|
<iframe
|
||||||
src="/earth/3dearthmult.html"
|
src="/earth/index.html"
|
||||||
style={{
|
style={{
|
||||||
width: '100vw',
|
width: '100vw',
|
||||||
height: '100vh',
|
height: '100vh',
|
||||||
|
|||||||
Reference in New Issue
Block a user