diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 560eb6d..1de6e2b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,6 +24,8 @@ jobs: build: name: Build ESP32-S3 Firmware runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} steps: - name: Checkout repository @@ -87,9 +89,9 @@ jobs: run: | if [ -f build/project_description.json ]; then VERSION=$(grep -o '"project_version":[^,]*' build/project_description.json | cut -d'"' -f4) - echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "📦 Build version: $VERSION" fi + echo "version=${VERSION:-0.0.0}" >> $GITHUB_OUTPUT + echo "📦 Build version: ${VERSION:-0.0.0}" - name: Print build size run: | @@ -118,11 +120,11 @@ jobs: build/config/sdkconfig.h retention-days: 7 - # 可选:发布版本时创建 Release + # 编译成功后自动创建 Release:tag 推送 或 main 分支推送 release: name: Create Release needs: build - if: startsWith(github.ref, 'refs/tags/v') + if: startsWith(github.ref, 'refs/tags/v') || (github.ref == 'refs/heads/main' && github.event_name == 'push') runs-on: ubuntu-latest permissions: contents: write # 创建 Release 需要写权限 @@ -134,9 +136,24 @@ jobs: pattern: tianshanos-firmware-* path: firmware + - name: Check if release already exists (main branch) + if: github.ref == 'refs/heads/main' + id: check + run: | + TAG="v${{ needs.build.outputs.version }}" + if gh release view "$TAG" 2>/dev/null; then + echo "skip=true" >> $GITHUB_OUTPUT + echo "⏭️ Release $TAG already exists, skipping" + else + echo "skip=false" >> $GITHUB_OUTPUT + fi + - name: Create Release + if: startsWith(github.ref, 'refs/tags/') || (github.ref == 'refs/heads/main' && steps.check.outputs.skip != 'true') uses: softprops/action-gh-release@v2 with: + tag_name: ${{ startsWith(github.ref, 'refs/tags/') && github.ref_name || format('v{0}', needs.build.outputs.version) }} + name: ${{ startsWith(github.ref, 'refs/tags/') && github.ref_name || format('v{0}', needs.build.outputs.version) }} files: firmware/**/*.bin generate_release_notes: true env: diff --git a/README.md b/README.md index 7251e54..de8a141 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,7 @@ esptool.py --chip esp32s3 -p /dev/ttyACM0 write_flash \ ## 当前状态 -**版本**: 0.4.0 +**版本**: 0.4.4 **阶段**: Phase 38 完成 - WebUI 多语言支持 ### 已完成功能 diff --git a/components/ts_security/src/ts_security.c b/components/ts_security/src/ts_security.c index c97b10c..d3bdb02 100644 --- a/components/ts_security/src/ts_security.c +++ b/components/ts_security/src/ts_security.c @@ -166,6 +166,22 @@ esp_err_t ts_security_store_cert(const char *name, ts_cert_type_t type, return ret; } +/** + * @brief 驱逐指定 client_id 的所有会话(释放槽位) + * @note 仅内部使用,在 create_session 槽位满时按同用户驱逐 + */ +static void destroy_sessions_by_client(const char *client_id) +{ + if (!client_id || client_id[0] == '\0') return; + for (int i = 0; i < MAX_SESSIONS; i++) { + if (s_sessions[i].active && + strcmp(s_sessions[i].session.client_id, client_id) == 0) { + s_sessions[i].active = false; + TS_LOGI(TAG, "Evicted session for client: %s", client_id); + } + } +} + esp_err_t ts_security_create_session(const char *client_id, ts_perm_level_t level, uint32_t *session_id) { @@ -181,8 +197,19 @@ esp_err_t ts_security_create_session(const char *client_id, ts_perm_level_t leve } if (slot < 0) { - TS_LOGW(TAG, "No free session slots"); - return ESP_ERR_NO_MEM; + if (client_id && client_id[0] != '\0') { + destroy_sessions_by_client(client_id); + for (int i = 0; i < MAX_SESSIONS; i++) { + if (!s_sessions[i].active) { + slot = i; + break; + } + } + } + if (slot < 0) { + TS_LOGW(TAG, "No free session slots"); + return ESP_ERR_NO_MEM; + } } // Generate session ID diff --git a/components/ts_webui/src/ts_webui_api.c b/components/ts_webui/src/ts_webui_api.c index ee7e49d..84f94b8 100644 --- a/components/ts_webui/src/ts_webui_api.c +++ b/components/ts_webui/src/ts_webui_api.c @@ -305,6 +305,11 @@ static esp_err_t login_handler(ts_http_request_t *req, void *user_data) cJSON_AddNumberToObject(data, "session_id", session_id); cJSON_AddStringToObject(data, "username", username_copy); cJSON_AddStringToObject(data, "level", level_str); +#ifdef CONFIG_TS_SECURITY_TOKEN_EXPIRE_SEC + cJSON_AddNumberToObject(data, "expires_in", CONFIG_TS_SECURITY_TOKEN_EXPIRE_SEC); +#else + cJSON_AddNumberToObject(data, "expires_in", 86400); /* 24 hours */ +#endif cJSON_AddBoolToObject(data, "password_changed", password_changed); cJSON_AddItemToObject(response, "data", data); diff --git a/components/ts_webui/web/css/style.css b/components/ts_webui/web/css/style.css index ffc3ad4..1642e6c 100644 --- a/components/ts_webui/web/css/style.css +++ b/components/ts_webui/web/css/style.css @@ -7591,6 +7591,20 @@ button.btn-gray:hover, box-shadow: 0 2px 8px var(--blue-100); } +/* 长按拖拽排序:等待中、拖拽中、插入位置指示 */ +.drag-pending { + outline: 2px solid var(--blue-500) !important; + outline-offset: 2px; + opacity: 0.8; + transition: opacity 3s linear; +} +.drag-active-source { + opacity: 0.25 !important; +} +.drag-over-before { + border-top: 2px solid var(--blue-500) !important; +} + .dw-card-header { display: flex; justify-content: space-between; diff --git a/components/ts_webui/web/index.html b/components/ts_webui/web/index.html index 21e5ee1..4f452af 100644 --- a/components/ts_webui/web/index.html +++ b/components/ts_webui/web/index.html @@ -370,6 +370,8 @@