mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-09-18 05:23:57 +08:00
nav
This commit is contained in:
+25
-12
@@ -790,7 +790,7 @@ export function NavDestination() {
|
||||
<section class="keys-required-wrapper">
|
||||
<div class="keys-required-widget">
|
||||
<div class="keys-required-title">Mapbox Keys Required</div>
|
||||
<p class="keys-required-text">You must set both your public and secret Mapbox keys before using navigation features.</p>
|
||||
<p class="keys-required-text">The public key powers destination search and the map. The secret key lets your comma calculate the on-device route and provide navigation turn desires. Add both keys before starting navigation.</p>
|
||||
<a href="/manage_navigation_keys" class="keys-required-button">Go to "Manage Keys"</a>
|
||||
</div>
|
||||
</section>
|
||||
@@ -926,7 +926,7 @@ function NavigationDestination({
|
||||
isFavorited,
|
||||
favoriteRoutes = [],
|
||||
steps = []
|
||||
}) {
|
||||
}) {
|
||||
async function cancelNavigation() {
|
||||
showSnackbar("Navigation cancelled...");
|
||||
removeRouteFromMap(map);
|
||||
@@ -938,19 +938,32 @@ function NavigationDestination({
|
||||
await fetch("/api/navigation", { method: "DELETE" });
|
||||
}
|
||||
async function confirmDestination() {
|
||||
let response;
|
||||
let result = {};
|
||||
try {
|
||||
response = await fetch("/api/navigation", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
name,
|
||||
longitude: destinationCoordinates[0],
|
||||
latitude: destinationCoordinates[1],
|
||||
routeId,
|
||||
})
|
||||
});
|
||||
result = await response.json().catch(() => ({}));
|
||||
} catch {
|
||||
showSnackbar("Could not reach the comma to start navigation.", "error");
|
||||
return;
|
||||
}
|
||||
if (!response.ok) {
|
||||
showSnackbar(result.message || "Failed to start navigation.", "error");
|
||||
return;
|
||||
}
|
||||
|
||||
onConfirm?.();
|
||||
showSnackbar("Navigation set!");
|
||||
localStorage.setItem("activeRouteId", routeId);
|
||||
await fetch("/api/navigation", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
name,
|
||||
longitude: destinationCoordinates[0],
|
||||
latitude: destinationCoordinates[1],
|
||||
routeId,
|
||||
})
|
||||
});
|
||||
await loadFavorites();
|
||||
const searchInputEl = document.getElementById("search-field");
|
||||
if (searchInputEl) searchInputEl.value = "";
|
||||
|
||||
+12
-2
@@ -97,6 +97,7 @@ export const NavigationDestinationPanel = {
|
||||
navigationStarted: false,
|
||||
isMetric: false,
|
||||
mapboxPublic: "",
|
||||
mapboxSecret: "",
|
||||
language: "",
|
||||
lastPosition: null,
|
||||
map: null,
|
||||
@@ -110,6 +111,7 @@ export const NavigationDestinationPanel = {
|
||||
},
|
||||
computed: {
|
||||
hasMapbox() { return !!this.mapboxPublic },
|
||||
hasRoutingKey() { return !!this.mapboxSecret },
|
||||
recentPlaces() {
|
||||
const seen = new Set()
|
||||
return [...this.favorites, ...this.recentDestinations].filter((place) => {
|
||||
@@ -157,6 +159,7 @@ export const NavigationDestinationPanel = {
|
||||
api.getNavigationFavorites().catch(() => ({ favorites: [] })),
|
||||
])
|
||||
this.mapboxPublic = String(nav?.mapboxPublic || "").trim()
|
||||
this.mapboxSecret = String(nav?.mapboxSecret || "").trim()
|
||||
this.language = String(nav?.language || "").trim()
|
||||
this.isMetric = !!nav?.isMetric
|
||||
this.lastPosition = coordinates(nav?.lastPosition)
|
||||
@@ -284,6 +287,10 @@ export const NavigationDestinationPanel = {
|
||||
showSnackbar("Add a Mapbox public key in App Keys first.", "error")
|
||||
return
|
||||
}
|
||||
if (!this.hasRoutingKey) {
|
||||
showSnackbar("Add a Mapbox secret key in App Keys first. It is required for the comma to calculate the on-device route and provide navigation turn desires.", "error")
|
||||
return
|
||||
}
|
||||
this.loadingRoute = true
|
||||
try {
|
||||
this.destination = place || await this.resolveQuery()
|
||||
@@ -419,11 +426,14 @@ export const NavigationDestinationPanel = {
|
||||
<div v-else ref="map" class="gx-navigation-map"></div>
|
||||
|
||||
<div v-if="hasMapbox && !loading" class="gx-navigation-overlay">
|
||||
<section v-if="!hasRoutingKey" class="gx-navigation-error gx-card">
|
||||
The map and destination search only use your public Mapbox key. Add a <a href="#/navigation/keys">secret Mapbox key in App Keys</a> before starting navigation so the comma can calculate the on-device route and provide turn desires.
|
||||
</section>
|
||||
<section class="gx-navigation-search gx-card">
|
||||
<div class="gx-navigation-search__row">
|
||||
<i class="bi bi-search" aria-hidden="true"></i>
|
||||
<input class="gx-field" v-model="query" @input="onInput" @keyup.enter="setDestination()" placeholder="Search here" aria-label="Search for a destination" autocomplete="off" />
|
||||
<button type="button" class="gx-icon-btn gx-navigation-send" :disabled="loadingRoute || searching || !query.trim()" @click="setDestination()" aria-label="Send destination" title="Send destination"><i class="bi bi-send-fill"></i></button>
|
||||
<button type="button" class="gx-icon-btn gx-navigation-send" :disabled="loadingRoute || searching || !query.trim() || !hasRoutingKey" @click="setDestination()" aria-label="Send destination" :title="hasRoutingKey ? 'Send destination' : 'A Mapbox secret key is required to start navigation'"><i class="bi bi-send-fill"></i></button>
|
||||
</div>
|
||||
<div v-if="searching" class="gx-navigation-status">Searching...</div>
|
||||
<div v-if="suggestions.length" class="gx-navigation-suggestions">
|
||||
@@ -453,7 +463,7 @@ export const NavigationDestinationPanel = {
|
||||
</div>
|
||||
<div class="gx-navigation-summary__actions">
|
||||
<button v-if="navigationStarted" type="button" class="gx-btn gx-btn--danger" @click="cancelNavigation"><i class="bi bi-x-lg"></i> Cancel Navigation</button>
|
||||
<button v-else type="button" class="gx-btn gx-btn--success" :disabled="loadingRoute" @click="setDestination(destination)"><i class="bi bi-sign-turn-right"></i> {{ loadingRoute ? 'Calculating...' : 'Start Navigation' }}</button>
|
||||
<button v-else type="button" class="gx-btn gx-btn--success" :disabled="loadingRoute || !hasRoutingKey" :title="hasRoutingKey ? 'Start Navigation' : 'A Mapbox secret key is required to start navigation'" @click="setDestination(destination)"><i class="bi bi-sign-turn-right"></i> {{ loadingRoute ? 'Calculating...' : 'Start Navigation' }}</button>
|
||||
<button type="button" class="gx-btn gx-btn--favorite" :class="{ active: isFavorite }" @click="toggleFavorite"><i class="bi" :class="isFavorite ? 'bi-heart-fill' : 'bi-heart'"></i> {{ isFavorite ? 'Unfavorite' : 'Favorite' }}</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -393,6 +393,7 @@ def _install_server_import_stubs():
|
||||
sys.modules["openpilot.starpilot.navigation.destination_store"] = _simple_module(
|
||||
"openpilot.starpilot.navigation.destination_store",
|
||||
normalize_destination_payload=lambda payload: payload,
|
||||
routing_configured=lambda params: bool(str(params.get("MapboxSecretKey") or "").strip()),
|
||||
update_recent_destinations=lambda *args, **kwargs: [],
|
||||
)
|
||||
sys.modules["openpilot.starpilot.system.the_galaxy.factory_reset"] = _simple_module(
|
||||
|
||||
@@ -462,6 +462,33 @@ def test_navigation_last_position_rejects_stale_persisted_fix(monkeypatch):
|
||||
assert the_galaxy._get_navigation_last_position() is None
|
||||
|
||||
|
||||
def test_navigation_api_rejects_destination_without_secret_key(monkeypatch):
|
||||
client, fake_params = _params_client(monkeypatch, {"MapboxPublicKey": "public"}, "tici")
|
||||
|
||||
response = client.post("/api/navigation", json={
|
||||
"name": "Work",
|
||||
"latitude": 41.0,
|
||||
"longitude": -87.0,
|
||||
})
|
||||
|
||||
assert response.status_code == 400
|
||||
assert "secret key" in response.get_json()["message"]
|
||||
assert fake_params.get("NavDestination") is None
|
||||
|
||||
|
||||
def test_navigation_api_accepts_destination_with_secret_key(monkeypatch):
|
||||
client, fake_params = _params_client(monkeypatch, {"MapboxSecretKey": "secret"}, "tici")
|
||||
|
||||
response = client.post("/api/navigation", json={
|
||||
"name": "Work",
|
||||
"latitude": 41.0,
|
||||
"longitude": -87.0,
|
||||
})
|
||||
|
||||
assert response.status_code == 200
|
||||
assert json.loads(fake_params.get("NavDestination"))["name"] == "Work"
|
||||
|
||||
|
||||
def test_save_longitudinal_maneuver_status_writes_json_param_as_dict(monkeypatch):
|
||||
fake_params = WritableFakeParams()
|
||||
monkeypatch.setattr(the_galaxy, "params", fake_params)
|
||||
|
||||
@@ -504,7 +504,6 @@ def test_ui_all_remaining_classic_tools_native_no_embed():
|
||||
assert "ref=\"map\"" in destination and "setNavigation(this.destination)" in destination
|
||||
assert destination.count("methods: {") == 1 and "secondaryLabel," in destination
|
||||
assert _read("js/components/LateralTuningPanel.js")
|
||||
|
||||
# Shared API surface added for the second batch of ported pages.
|
||||
for method in ["selectTestingGround",
|
||||
"getSentryStatus", "getSentryEvents", "deleteSentryEvent", "sentryPushSubscribe",
|
||||
@@ -536,6 +535,20 @@ def test_ui_all_remaining_classic_tools_native_no_embed():
|
||||
assert lateral.index('>Saved Tunes</span>') < lateral.index('>Local Routes</span>')
|
||||
|
||||
|
||||
def test_navigation_requires_secret_key_before_starting_on_device_route():
|
||||
destination = _read("js/components/NavigationDestinationPanel.js")
|
||||
classic_destination = (REPO_ROOT / "starpilot/system/the_galaxy/assets/components/navigation/navigation_destination.js").read_text(encoding="utf-8")
|
||||
|
||||
assert 'mapboxSecret: ""' in destination
|
||||
assert "hasRoutingKey()" in destination
|
||||
assert "!query.trim() || !hasRoutingKey" in destination
|
||||
assert "loadingRoute || !hasRoutingKey" in destination
|
||||
assert "required for the comma to calculate the on-device route" in destination
|
||||
assert "secret key lets your comma calculate the on-device route" in classic_destination
|
||||
assert "if (!response.ok)" in classic_destination
|
||||
assert 'result.message || "Failed to start navigation."' in classic_destination
|
||||
|
||||
|
||||
def test_ui_cameras_hub_vasm_and_pip_native_no_embed():
|
||||
app = _read("js/app.js")
|
||||
store = _read("js/store.js")
|
||||
|
||||
@@ -168,7 +168,7 @@ from openpilot.starpilot.common.testing_grounds import (
|
||||
TESTING_GROUNDS_SLOT_DEFINITIONS as SHARED_TESTING_GROUNDS_SLOT_DEFINITIONS,
|
||||
TESTING_GROUNDS_STATE_PATH as SHARED_TESTING_GROUNDS_STATE_PATH,
|
||||
)
|
||||
from openpilot.starpilot.navigation.destination_store import normalize_destination_payload, update_recent_destinations
|
||||
from openpilot.starpilot.navigation.destination_store import normalize_destination_payload, routing_configured, update_recent_destinations
|
||||
from openpilot.starpilot.system.the_galaxy.factory_reset import remove_path as _run_factory_reset_delete
|
||||
from openpilot.starpilot.system.the_galaxy import flm_workspace, utilities
|
||||
from openpilot.starpilot.system.the_galaxy.update_recovery import inspect_interrupted_update, public_recovery_status, recover_interrupted_update
|
||||
@@ -5760,6 +5760,11 @@ def setup(app):
|
||||
|
||||
@app.route("/api/navigation", methods=["POST"])
|
||||
def set_navigation():
|
||||
if not routing_configured(params):
|
||||
return {
|
||||
"message": "A Mapbox secret key is required to calculate the on-device route and provide navigation turn desires. Add it in App Keys first."
|
||||
}, 400
|
||||
|
||||
destination = normalize_destination_payload(request.json)
|
||||
if destination is None:
|
||||
return {"message": "Invalid destination payload"}, 400
|
||||
|
||||
Reference in New Issue
Block a user