mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 19:01:39 -04:00
Browser-based ESP32/ESP8266 flasher (esptool-js + Web Serial); nothing is uploaded to WROLPi, the flash is written straight to the device over USB. Firmware sources (themed, tabbed): a saved set, the WROLPi media directory, or a local file. Local files can be dropped anywhere on the page, which jumps to the "Add from computer" dropzone. The media picker searches .bin files by path and can detect the connected chip to filter firmware to matching images. - Indexed file_group.suffix + path filter for fast .bin search (alembic migration backfills via split_path_stem_and_suffix) - modules/flasher: chip detection from ESP image headers, /search endpoint, and flasher.yaml saved configurations (/configs CRUD) - Flash progress modal with ETA, connect timeout, boot-mode hint, auto- scrolling logs Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
55 lines
2.2 KiB
Python
55 lines
2.2 KiB
Python
import asyncio
|
|
from http import HTTPStatus
|
|
from urllib.parse import unquote
|
|
|
|
from sanic import Blueprint, Request, response
|
|
from sanic_ext import validate
|
|
from sanic_ext.extensions.openapi import openapi
|
|
|
|
from modules.flasher import lib, schema
|
|
from modules.flasher.config import get_flasher_config
|
|
from wrolpi.api_utils import json_response
|
|
|
|
flasher_bp = Blueprint('Flasher', '/api/flasher')
|
|
|
|
|
|
@flasher_bp.post('/search')
|
|
@openapi.definition(
|
|
summary='Search .bin firmware and filter to ESP images (optionally for a specific chip)',
|
|
body=schema.FlasherSearchRequest,
|
|
)
|
|
@validate(schema.FlasherSearchRequest)
|
|
async def post_flasher_search(_: Request, body: schema.FlasherSearchRequest):
|
|
# Reading many firmware headers touches the disk; run off the event loop.
|
|
file_groups, total = await asyncio.to_thread(
|
|
lib.search_esp_firmware, body.chip, body.path, body.limit)
|
|
return json_response(dict(file_groups=file_groups, totals=dict(file_groups=total)), HTTPStatus.OK)
|
|
|
|
|
|
@flasher_bp.get('/configs')
|
|
@openapi.definition(summary='List saved firmware flashing configurations')
|
|
async def get_flasher_configs(_: Request):
|
|
return json_response(dict(configurations=get_flasher_config().configurations), HTTPStatus.OK)
|
|
|
|
|
|
@flasher_bp.post('/configs')
|
|
@openapi.definition(
|
|
summary='Save (add or replace by name) a firmware flashing configuration',
|
|
body=schema.FlasherSaveConfigRequest,
|
|
)
|
|
@validate(schema.FlasherSaveConfigRequest)
|
|
async def save_flasher_config(_: Request, body: schema.FlasherSaveConfigRequest):
|
|
try:
|
|
configuration = get_flasher_config().save_configuration(body.name, body.files, body.erase_all)
|
|
except ValueError as e:
|
|
return response.json({'error': str(e)}, HTTPStatus.BAD_REQUEST)
|
|
return json_response(dict(configuration=configuration), HTTPStatus.CREATED)
|
|
|
|
|
|
@flasher_bp.delete('/configs/<name:str>')
|
|
@openapi.definition(summary='Delete a saved firmware flashing configuration by name')
|
|
async def delete_flasher_config(_: Request, name: str):
|
|
name = unquote(name)
|
|
if get_flasher_config().delete_configuration(name):
|
|
return response.empty()
|
|
return response.json({'error': f'No saved configuration named {name!r}'}, HTTPStatus.NOT_FOUND)
|