93 lines
3.0 KiB
TypeScript
93 lines
3.0 KiB
TypeScript
import { Hono } from "hono"
|
|
import { join } from "node:path"
|
|
import { $ } from "bun"
|
|
import { IndexPage } from "./components/IndexPage"
|
|
import { LogsPage } from "./components/LogsPage"
|
|
import { ConnectingPage } from "./components/ConnectingPage"
|
|
|
|
const app = new Hono()
|
|
|
|
// Ping endpoint for connectivity check
|
|
app.get("/ping", (c) => {
|
|
return c.json({ ok: true })
|
|
})
|
|
|
|
// Serve static CSS
|
|
app.get("/pico.css", async (c) => {
|
|
const cssPath = join(import.meta.dir, "./static/pico.min.css")
|
|
const file = Bun.file(cssPath)
|
|
return new Response(file)
|
|
})
|
|
|
|
// API endpoint to get available WiFi networks
|
|
app.get("/api/networks", async (c) => {
|
|
try {
|
|
const result = await $`nmcli -t -f SSID device wifi list`.text()
|
|
const networks = result
|
|
.trim()
|
|
.split("\n")
|
|
.filter((ssid) => ssid && ssid !== "SSID") // Remove empty and header
|
|
.filter((ssid, index, self) => self.indexOf(ssid) === index) // Remove duplicates
|
|
return c.json({ networks })
|
|
} catch (error) {
|
|
return c.json({ networks: [], error: String(error) }, 500)
|
|
}
|
|
})
|
|
|
|
// Main WiFi configuration page
|
|
app.get("/", (c) => {
|
|
return c.html(<IndexPage />)
|
|
})
|
|
|
|
// Service logs
|
|
app.get("/logs", async (c) => {
|
|
const service = c.req.query("service") || "phone-ap"
|
|
const validServices = ["phone-ap", "phone-web", "phone"]
|
|
|
|
// Default to phone-ap if invalid service
|
|
const selectedService = validServices.includes(service) ? service : "phone-ap"
|
|
|
|
try {
|
|
const logs = await $`journalctl -u ${selectedService}.service -n 200 --no-pager --no-hostname`.text()
|
|
return c.html(<LogsPage service={selectedService} logs={logs} />)
|
|
} catch (error) {
|
|
return c.html(<LogsPage service={selectedService} logs={`Error loading logs: ${error}`} />)
|
|
}
|
|
})
|
|
|
|
// Handle WiFi configuration submission
|
|
app.post("/save", async (c) => {
|
|
const formData = await c.req.parseBody()
|
|
const ssid = formData.ssid as string
|
|
const password = formData.password as string
|
|
|
|
// Return the connecting page immediately
|
|
const response = c.html(<ConnectingPage ssid={ssid} />)
|
|
|
|
// Trigger connection in background after a short delay (allows response to be sent)
|
|
setTimeout(async () => {
|
|
try {
|
|
await $`sudo nmcli device wifi connect ${ssid} password ${password}`
|
|
console.log(`[WiFi] Successfully connected to ${ssid}`)
|
|
} catch (error) {
|
|
console.error(`[WiFi] Failed to connect to ${ssid}:`, error)
|
|
|
|
// Delete the failed connection profile so ap-monitor doesn't try to use it
|
|
try {
|
|
await $`sudo nmcli connection delete ${ssid}`.nothrow()
|
|
console.log(`[WiFi] Deleted failed connection profile for ${ssid}`)
|
|
} catch (deleteError) {
|
|
console.error(`[WiFi] Failed to delete connection profile:`, deleteError)
|
|
}
|
|
}
|
|
}, 1000) // 1 second delay
|
|
|
|
return response
|
|
})
|
|
|
|
const port = process.env.PORT ? Number(process.env.PORT) : 80
|
|
export default { port, fetch: app.fetch }
|
|
|
|
console.log(`Server running on http://0.0.0.0:${port}`)
|
|
console.log("Access via WiFi or AP at http://phone.local")
|