fix: implement Claude API key save RPC, VPN status on home page
- Add system.settings.get/set RPC methods for Claude API key management - Save key to secrets/claude-api-key, restart claude-api-proxy service - Home Network card now fetches VPN status via vpn.status RPC - Shows provider name (nostr-vpn, tailscale) instead of just "Connected" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
362bbb451f
commit
23f17356df
@ -292,6 +292,8 @@ impl RpcHandler {
|
||||
"system.disk-cleanup" => self.handle_system_disk_cleanup().await,
|
||||
"system.reboot" => self.handle_system_reboot(params).await,
|
||||
"system.factory-reset" => self.handle_system_factory_reset(params).await,
|
||||
"system.settings.get" => self.handle_system_settings_get(params).await,
|
||||
"system.settings.set" => self.handle_system_settings_set(params).await,
|
||||
|
||||
// Opt-in anonymous analytics
|
||||
"analytics.get-status" => self.handle_analytics_get_status().await,
|
||||
|
||||
@ -327,4 +327,78 @@ impl RpcHandler {
|
||||
|
||||
Ok(serde_json::json!({ "status": "resetting" }))
|
||||
}
|
||||
|
||||
/// system.settings.get — Read a settings value
|
||||
pub(in crate::api::rpc) async fn handle_system_settings_get(
|
||||
&self,
|
||||
params: Option<serde_json::Value>,
|
||||
) -> Result<serde_json::Value> {
|
||||
let params = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?;
|
||||
let key = params.get("key").and_then(|v| v.as_str())
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing key"))?;
|
||||
|
||||
match key {
|
||||
"claude_api_key_set" => {
|
||||
let key_file = self.config.data_dir.join("secrets/claude-api-key");
|
||||
let has_key = tokio::fs::metadata(&key_file).await.is_ok();
|
||||
Ok(serde_json::json!({ "value": has_key }))
|
||||
}
|
||||
_ => Ok(serde_json::json!({ "value": null })),
|
||||
}
|
||||
}
|
||||
|
||||
/// system.settings.set — Write a settings value
|
||||
pub(in crate::api::rpc) async fn handle_system_settings_set(
|
||||
&self,
|
||||
params: Option<serde_json::Value>,
|
||||
) -> Result<serde_json::Value> {
|
||||
let params = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?;
|
||||
let key = params.get("key").and_then(|v| v.as_str())
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing key"))?;
|
||||
let value = params.get("value").and_then(|v| v.as_str()).unwrap_or("");
|
||||
|
||||
match key {
|
||||
"claude_api_key" => {
|
||||
let secrets_dir = self.config.data_dir.join("secrets");
|
||||
tokio::fs::create_dir_all(&secrets_dir).await
|
||||
.context("Failed to create secrets dir")?;
|
||||
let key_file = secrets_dir.join("claude-api-key");
|
||||
|
||||
if value.is_empty() {
|
||||
// Remove key
|
||||
tokio::fs::remove_file(&key_file).await.ok();
|
||||
info!("Claude API key removed");
|
||||
} else {
|
||||
// Save key
|
||||
tokio::fs::write(&key_file, value).await
|
||||
.context("Failed to write API key")?;
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
std::fs::set_permissions(&key_file, std::fs::Permissions::from_mode(0o600)).ok();
|
||||
}
|
||||
info!("Claude API key saved");
|
||||
}
|
||||
|
||||
// Update the claude-api-proxy environment and restart
|
||||
let env_line = format!("ANTHROPIC_API_KEY={}", value);
|
||||
let env_file = self.config.data_dir.join("secrets/claude-api-proxy.env");
|
||||
tokio::fs::write(&env_file, &env_line).await.ok();
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
std::fs::set_permissions(&env_file, std::fs::Permissions::from_mode(0o600)).ok();
|
||||
}
|
||||
|
||||
// Restart the proxy to pick up the new key
|
||||
let _ = tokio::process::Command::new("sudo")
|
||||
.args(["systemctl", "restart", "claude-api-proxy"])
|
||||
.output()
|
||||
.await;
|
||||
|
||||
Ok(serde_json::json!({ "saved": true }))
|
||||
}
|
||||
_ => anyhow::bail!("Unknown setting: {}", key),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -145,7 +145,7 @@
|
||||
</div>
|
||||
<div class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
|
||||
<div class="flex items-center gap-3"><div class="w-2 h-2 rounded-full" :class="vpnConnected ? 'bg-orange-400' : 'bg-white/40'"></div><span class="text-sm text-white/80">VPN</span></div>
|
||||
<span class="text-sm font-medium" :class="vpnConnected ? 'text-orange-400' : 'text-white/40'">{{ vpnConnected ? 'Connected' : 'Not configured' }}</span>
|
||||
<span class="text-sm font-medium" :class="vpnConnected ? 'text-orange-400' : 'text-white/40'">{{ vpnConnected ? (vpnStatus.provider || 'Connected') : 'Not configured' }}</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between p-3 bg-white/5 rounded-lg">
|
||||
<div class="flex items-center gap-3"><div class="w-2 h-2 rounded-full" :class="systemStats.bitcoinAvailable ? 'bg-orange-400' : 'bg-white/40'"></div><span class="text-sm text-white/80">Bitcoin</span></div>
|
||||
@ -311,10 +311,8 @@ const torConnected = computed(() => {
|
||||
const torAddr = store.data?.['server-info']?.['tor-address']
|
||||
return !!torAddr && torAddr.length > 0
|
||||
})
|
||||
const vpnConnected = computed(() => {
|
||||
const pkg = packages.value['tailscale']
|
||||
return !!pkg && pkg.state === PackageState.Running
|
||||
})
|
||||
const vpnStatus = ref<{ connected: boolean; provider: string | null }>({ connected: false, provider: null })
|
||||
const vpnConnected = computed(() => vpnStatus.value.connected || (!!packages.value['tailscale'] && packages.value['tailscale'].state === PackageState.Running))
|
||||
const bitcoinSyncDisplay = computed(() => {
|
||||
if (!systemStats.bitcoinAvailable) return 'Not running'
|
||||
if (systemStats.bitcoinSyncPercent >= 99.9) return 'Synced'
|
||||
@ -350,6 +348,7 @@ const cloudFolderDisplay = computed(() => cloudFolderCount.value !== null ? Stri
|
||||
onMounted(async () => {
|
||||
try { const usage = await fileBrowserClient.getUsage(); cloudStorageUsed.value = usage.totalSize; cloudFolderCount.value = usage.folderCount } catch { /* not running */ }
|
||||
loadSystemStats(); systemStatsInterval = setInterval(loadSystemStats, 30000); checkUpdateStatus(); loadWeb5Status()
|
||||
rpcClient.vpnStatus().then(s => { vpnStatus.value = s }).catch(() => {})
|
||||
})
|
||||
|
||||
// Wallet modals
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user