From 532891fad028b6206aef642cbe3263fab7968a57 Mon Sep 17 00:00:00 2001 From: robin Date: Wed, 4 Mar 2026 17:59:14 +0800 Subject: [PATCH] feat: sync httpdns sdk/platform updates without large binaries --- .claude/settings.local.json | 9 +- .gitignore | 6 + EdgeAPI/build/sql.sh | 7 +- EdgeAPI/cmd/sql-dump/main.go | 2 + EdgeAPI/internal/const/const.go | 4 +- EdgeAPI/internal/const/const_plus.go | 4 +- .../internal/db/models/httpdns_cluster_dao.go | 6 +- .../db/models/httpdns_cluster_model.go | 2 + .../internal/db/models/httpdns_node_dao.go | 74 ++- EdgeAPI/internal/db/models/node_dao.go | 5 + EdgeAPI/internal/db/models/ns_node_dao.go | 23 + .../internal/db/models/ns_node_dao_plus.go | 23 + .../internal/installers/queue_httpdns_node.go | 94 ++++ EdgeAPI/internal/installers/upgrade_queue.go | 25 + .../rpc/services/httpdns/converters.go | 88 ++++ .../services/httpdns/service_httpdns_app.go | 27 +- .../httpdns/service_httpdns_cluster.go | 54 ++- .../services/httpdns/service_httpdns_node.go | 121 +++++ .../services/nameservers/service_ns_node.go | 113 +++++ EdgeAPI/internal/rpc/services/service_node.go | 7 + EdgeAPI/internal/setup/sql_upgrade.go | 22 +- EdgeAPI/internal/setup/upgrade_config.go | 45 ++ .../tasks/httpdns_node_monitor_task.go | 107 ++++ EdgeAdmin/build/build.sh | 28 +- EdgeAdmin/build/generate.sh | 41 +- EdgeAdmin/cmd/edge-admin/main.go | 34 +- .../internal/configloaders/upgrade_config.go | 69 +++ EdgeAdmin/internal/const/const.go | 4 +- .../actions/default/httpdns/apps/sdk_check.go | 17 +- .../actions/default/httpdns/apps/sdk_doc.go | 39 +- .../default/httpdns/apps/sdk_download.go | 4 +- .../default/httpdns/apps/sdk_helpers.go | 313 +++++++----- .../default/httpdns/apps/sdk_upload.go | 235 ++++----- .../default/httpdns/apps/sdk_upload_delete.go | 47 +- .../httpdns/clusters/cluster/node/index.go | 21 +- .../clusters/cluster/node/rpc_helpers.go | 68 +-- .../httpdns/clusters/cluster/node/update.go | 2 +- .../httpdns/clusters/clusterSettings.go | 101 ++-- .../default/httpdns/clusters/create.go | 13 +- .../default/httpdns/clusters/createNode.go | 2 +- .../actions/default/httpdns/clusters/init.go | 4 +- .../default/httpdns/clusters/rpc_helpers.go | 164 +++++-- .../default/httpdns/clusters/upgradeRemote.go | 96 +++- .../default/httpdns/clusters/upgradeStatus.go | 48 ++ .../default/settings/settingutils/helper.go | 1 + .../settings/settingutils/helper_plus.go | 1 + .../web/actions/default/settings/ui/index.go | 11 +- .../settings/upgrade/dns_helper_default.go | 23 + .../settings/upgrade/dns_helper_plus.go | 100 ++++ .../actions/default/settings/upgrade/index.go | 270 ++++++++++- .../actions/default/settings/upgrade/init.go | 4 +- .../default/settings/upgrade/status.go | 80 +++ .../default/settings/upgrade/upgradeNode.go | 150 ++++++ .../web/actions/default/users/createPopup.go | 6 +- EdgeAdmin/web/public/js/components.js | 21 +- EdgeAdmin/web/public/js/components.src.js | 21 +- .../js/components/server/ssl-certs-box.js | 6 +- .../js/components/server/ssl-config-box.js | 16 +- .../js/sweetalert2/dist/sweetalert2.css | 383 ++++++++++++--- EdgeAdmin/web/views/@default/@layout.css | 291 ++++++++++- EdgeAdmin/web/views/@default/@layout.html | 223 +++++---- .../web/views/@default/@layout_popup.css | 141 +++++- .../@default/httpdns/apps/sdkUpload.html | 58 +-- .../views/@default/httpdns/apps/sdkUpload.js | 11 + .../httpdns/clusters/cluster/node/index.html | 2 +- .../httpdns/clusters/clusterSettings.html | 33 +- .../httpdns/clusters/clusterSettings.js | 23 + .../@default/httpdns/clusters/create.html | 4 +- .../@default/httpdns/clusters/createNode.html | 2 +- .../@default/httpdns/clusters/index.html | 8 +- .../httpdns/clusters/upgradeRemote.html | 63 ++- .../httpdns/clusters/upgradeRemote.js | 147 ++++++ .../@default/ns/clusters/cluster/index.html | 4 +- .../@default/settings/authority/index.html | 2 +- .../web/views/@default/settings/ui/index.html | 87 ++-- .../@default/settings/upgrade/index.html | 138 +++++- .../views/@default/settings/upgrade/index.js | 457 ++++++++++++++++++ .../web/views/@default/users/createPopup.html | 2 +- .../views/@default/users/setting/index.css | 3 + .../views/@default/users/setting/index.html | 18 +- EdgeCommon/pkg/iplibrary/reader_maxmind.go | 46 +- .../pkg/rpc/pb/model_httpdns_cluster.pb.go | 8 + .../pkg/rpc/pb/service_httpdns_cluster.pb.go | 16 + .../rpc/pb/service_httpdns_node_grpc.pb.go | 122 ++++- .../pkg/rpc/pb/service_ns_node_grpc.pb.go | 80 +++ .../protos/models/model_httpdns_cluster.proto | 1 + .../rpc/protos/service_httpdns_cluster.proto | 2 + .../pkg/rpc/protos/service_httpdns_node.proto | 36 ++ .../pkg/rpc/protos/service_ns_node.proto | 28 ++ EdgeCommon/pkg/systemconfigs/settings.go | 1 + EdgeCommon/pkg/userconfigs/user_features.go | 6 +- EdgeDNS/internal/const/const.go | 2 +- EdgeHttpDNS/Changelog及升级指南_v1.4.8.txt | 64 --- EdgeHttpDNS/HTTPDNS主计划-V1.2.md | 147 ------ EdgeHttpDNS/HTTPDNS后端开发计划.md | 255 ---------- EdgeHttpDNS/HTTPDNS用户使用手册.md | 98 ---- EdgeHttpDNS/HTTPDNS管理员配置手册.md | 82 ---- EdgeHttpDNS/SNI隐匿开发计划.md | 92 ---- EdgeHttpDNS/internal/const/const.go | 2 +- EdgeHttpDNS/internal/nodes/resolve_server.go | 119 +++-- .../internal/nodes/snapshot_manager.go | 35 +- EdgeHttpDNS/sdk/.tmp_android_bundle/README.md | 77 --- .../alicloud-android-crashdefend-0.0.6.jar | Bin 10833 -> 0 bytes .../alicloud-android-httpdns-2.6.7.aar | Bin 179803 -> 0 bytes .../alicloud-android-ipdetector-1.2.0.aar | Bin 21721 -> 0 bytes .../alicloud-android-logger-1.2.0.aar | Bin 23489 -> 0 bytes .../.tmp_android_bundle/proguard-rules.pro | 68 --- EdgeHttpDNS/sdk/android/.gitignore | 26 - EdgeHttpDNS/sdk/android/README.md | 77 --- EdgeHttpDNS/sdk/android/app/.gitignore | 2 - EdgeHttpDNS/sdk/android/demo/.gitignore | 1 - .../alicloud-android-crashdefend-0.0.6.jar | Bin 10833 -> 0 bytes .../dist/alicloud-android-httpdns-2.6.7.aar | Bin 179803 -> 0 bytes .../alicloud-android-ipdetector-1.2.0.aar | Bin 21721 -> 0 bytes .../dist/alicloud-android-logger-1.2.0.aar | Bin 23489 -> 0 bytes EdgeHttpDNS/sdk/dist/proguard-rules.pro | 68 --- .../sdk/flutter/aliyun_httpdns/.gitignore | 41 -- .../aliyun_httpdns/example/android/.gitignore | 39 -- .../aliyun_httpdns/example/ios/.gitignore | 34 -- EdgeHttpDNS/sdk/ios/.gitignore | 58 --- EdgeHttpDNS/sdk/ios/AlicloudHTTPDNS.podspec | 51 -- .../AlicloudHttpDNSTests/Network/.gitignore | 1 - EdgeHttpDNS/新建文本文档.txt | 50 -- EdgeNode/internal/const/const.go | 2 +- EdgePlus/pkg/utils/edition.go | 2 +- EdgeUser/internal/const/const.go | 2 +- .../actions/default/httpdns/apps/sdk_check.go | 17 +- .../actions/default/httpdns/apps/sdk_doc.go | 39 +- .../default/httpdns/apps/sdk_download.go | 4 +- .../default/httpdns/apps/sdk_helpers.go | 314 ++++++------ EdgeUser/web/views/@default/@layout.css | 270 ++++++++++- EdgeUser/web/views/@default/@layout_popup.css | 138 +++++- HttpDNSSDK/.gitignore | 130 +++++ .../docs}/Flutter SDK集成文档.md | 2 +- .../docs}/HTTPDNS SDK 集成文档(Android).md | 48 +- .../docs}/iOS SDK集成文档.md | 2 +- .../sdk/SOURCE_LOCK.md | 10 +- .../sdk/THIRD_PARTY_NOTICES.md | 14 +- .../.github/ISSUE_TEMPLATE/bug_report.md | 0 .../sdk/android/LICENSE | 0 .../dist => HttpDNSSDK/sdk/android}/README.md | 19 +- .../sdk/android/app/build.gradle | 1 + .../sdk/android/app/proguard-rules.pro | 0 .../android/app/src/main/AndroidManifest.xml | 0 .../ams/httpdns/demo/HttpDnsActivity.java | 5 +- .../ams/httpdns/demo/HttpDnsHolder.java | 18 +- .../com/newsdk/ams/httpdns/demo/MyApp.java | 10 +- .../ams/httpdns/demo/NetworkRequest.java | 0 .../newsdk/ams/httpdns/demo/SDNSActivity.java | 5 +- .../ams/httpdns/demo/WebViewActivity.java | 0 .../ams/httpdns/demo/base/BaseActivity.java | 0 .../demo/http/HttpUrlConnectionRequest.java | 0 .../httpdns/demo/okhttp/OkHttpRequest.java | 0 .../newsdk/ams/httpdns/demo/utils/SpUtil.java | 0 .../ams/httpdns/demo/utils/ThreadUtil.java | 5 +- .../newsdk/ams/httpdns/demo/utils/Util.java | 0 .../app/src/main/res/drawable/new_bg.9.png | Bin .../app/src/main/res/layout/activity_base.xml | 0 .../src/main/res/layout/activity_webview.xml | 0 .../res/layout/item_autocomplete_button.xml | 0 .../item_autocomplete_edittext_button.xml | 0 .../src/main/res/layout/item_edit_button.xml | 0 .../main/res/layout/item_edit_edit_button.xml | 0 .../src/main/res/layout/item_four_button.xml | 0 .../src/main/res/layout/item_one_button.xml | 0 .../src/main/res/layout/item_three_button.xml | 0 .../src/main/res/layout/item_two_button.xml | 0 .../app/src/main/res/mipmap-hdpi/back.png | Bin .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin .../res/mipmap-hdpi/ic_launcher_round.png | Bin .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin .../res/mipmap-mdpi/ic_launcher_round.png | Bin .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin .../res/mipmap-xhdpi/ic_launcher_round.png | Bin .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin .../app/src/main/res/values/colors.xml | 0 .../app/src/main/res/values/strings.xml | 3 +- .../app/src/main/res/values/styles.xml | 0 .../main/res/xml/network_security_config.xml | 0 .../sdk/android/build.gradle | 0 .../sdk/android/demo/build.gradle | 11 +- .../sdk/android/demo/gradle.properties | 0 .../sdk/android/demo/proguard-rules.pro | 0 .../ams/emas/demo/ExampleInstrumentedTest.kt | 0 .../android/demo/src/main/AndroidManifest.xml | 0 .../ams/emas/demo/BatchResolveCacheHolder.kt | 5 +- .../ams/emas/demo/HttpDnsApplication.kt | 0 .../ams/emas/demo/HttpDnsServiceHolder.kt | 0 .../com/newsdk/ams/emas/demo/MainActivity.kt | 0 .../ams/emas/demo/PreResolveCacheHolder.kt | 0 .../newsdk/ams/emas/demo/SingleLiveData.kt | 0 .../newsdk/ams/emas/demo/TtlCacheHolder.kt | 0 .../java/com/newsdk/ams/emas/demo/Util.kt | 0 .../newsdk/ams/emas/demo/constant/Constant.kt | 0 .../emas/demo/net/HttpURLConnectionRequest.kt | 0 .../com/newsdk/ams/emas/demo/net/IRequest.kt | 0 .../emas/demo/net/OkHttpClientSingleton.kt | 0 .../com/newsdk/ams/emas/demo/net/OkHttpLog.kt | 0 .../newsdk/ams/emas/demo/net/OkHttpRequest.kt | 0 .../ams/emas/demo/net/TLSSNISocketFactory.kt | 0 .../demo/ui/basic/BasicSettingFragment.kt | 0 .../demo/ui/basic/BasicSettingViewModel.kt | 13 +- .../emas/demo/ui/basic/IBasicShowDialog.kt | 0 .../ams/emas/demo/ui/info/InfoFragment.kt | 0 .../ams/emas/demo/ui/info/InfoViewModel.kt | 0 .../demo/ui/info/SdnsGlobalSettingActivity.kt | 0 .../emas/demo/ui/info/list/ListActivity.kt | 0 .../ams/emas/demo/ui/info/list/ListAdapter.kt | 0 .../ams/emas/demo/ui/info/list/ListItem.kt | 0 .../emas/demo/ui/info/list/ListItemType.kt | 0 .../emas/demo/ui/info/list/ListViewModel.kt | 0 .../demo/ui/practice/BestPracticeFragment.kt | 0 .../demo/ui/practice/BestPracticeViewModel.kt | 0 .../ui/practice/HttpDnsWebviewGetActivity.kt | 7 +- .../ui/practice/IBestPracticeShowDialog.kt | 0 .../emas/demo/ui/practice/SNISocketFactory.kt | 0 .../demo/ui/resolve/IResolveShowDialog.kt | 0 .../emas/demo/ui/resolve/NetRequestType.kt | 0 .../emas/demo/ui/resolve/ResolveFragment.kt | 0 .../emas/demo/ui/resolve/ResolveViewModel.kt | 0 .../ams/emas/demo/ui/resolve/Response.kt | 0 .../ams/emas/demo/ui/resolve/SchemaType.kt | 0 .../ams/emas/demo/widget/SwipeLayout.kt | 0 .../drawable-v24/ic_launcher_foreground.xml | 0 .../demo/src/main/res/drawable/ic_add.xml | 0 .../src/main/res/drawable/ic_arrow_left.xml | 0 .../src/main/res/drawable/ic_arrow_right.xml | 0 .../demo/src/main/res/drawable/ic_back.xml | 0 .../demo/src/main/res/drawable/ic_dns.xml | 0 .../main/res/drawable/ic_home_black_24dp.xml | 0 .../demo/src/main/res/drawable/ic_httpdns.xml | 0 .../src/main/res/drawable/ic_information.xml | 0 .../src/main/res/drawable/ic_practice.xml | 0 .../res/layout/activity_http_dns_webview.xml | 0 .../src/main/res/layout/activity_list.xml | 0 .../src/main/res/layout/activity_main.xml | 0 .../layout/activity_sdns_global_setting.xml | 0 .../demo/src/main/res/layout/dialog_input.xml | 0 .../src/main/res/layout/dialog_input_2.xml | 0 .../src/main/res/layout/dialog_input_3.xml | 0 .../res/layout/fragment_basic_setting.xml | 0 .../res/layout/fragment_best_practice.xml | 0 .../src/main/res/layout/fragment_info.xml | 0 .../src/main/res/layout/fragment_resolve.xml | 0 .../src/main/res/layout/info_list_item.xml | 9 +- .../src/main/res/menu/bottom_nav_menu.xml | 0 .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin .../main/res/navigation/mobile_navigation.xml | 0 .../demo/src/main/res/values-land/dimens.xml | 0 .../demo/src/main/res/values-night/themes.xml | 0 .../src/main/res/values-w1240dp/dimens.xml | 0 .../src/main/res/values-w600dp/dimens.xml | 0 .../src/main/res/values-zh-rCN/strings.xml | 6 +- .../demo/src/main/res/values/attrs.xml | 0 .../demo/src/main/res/values/colors.xml | 0 .../demo/src/main/res/values/dimens.xml | 0 .../demo/src/main/res/values/strings.xml | 7 +- .../demo/src/main/res/values/styles.xml | 0 .../demo/src/main/res/values/themes.xml | 0 .../main/res/xml/network_security_config.xml | 0 .../newsdk/ams/emas/demo/ExampleUnitTest.kt | 0 .../sdk/android/gradle.properties | 0 .../android/gradle/wrapper/gradle-wrapper.jar | Bin .../gradle/wrapper/gradle-wrapper.properties | 0 .../sdk/android/gradlew | 0 .../sdk/android/gradlew.bat | 0 .../sdk/android/httpdns-sdk/build.gradle | 9 + .../httpdns-sdk/proguard-rules-for-test.pro | 0 .../android/httpdns-sdk/proguard-rules.pro | 0 .../sdk/android/httpdns/ApiForTest.java | 0 .../httpdns/BeforeHttpDnsServiceInit.java | 0 .../newsdk/sdk/android/httpdns/HttpDns.java | 0 .../sdk/android/httpdns/InitManager.java | 0 .../httpdns/impl/HttpDnsServiceTestImpl.java | 0 .../android/httpdns/impl/InstanceCreator.java | 0 .../newsdk/sdk/android/httpdns/HttpDns.java | 0 .../android/httpdns/impl/InstanceCreator.java | 0 .../sdk/android/httpdns/impl/IntlImpl.java | 0 .../httpdns-sdk/src/main/AndroidManifest.xml | 0 .../sdk/android/httpdns/CacheTtlChanger.java | 0 .../android/httpdns/DegradationFilter.java | 0 .../sdk/android/httpdns/HTTPDNSResult.java | 0 .../android/httpdns/HTTPDNSResultWrapper.java | 0 .../sdk/android/httpdns/HttpDnsCallback.java | 0 .../sdk/android/httpdns/HttpDnsService.java | 2 +- .../sdk/android/httpdns/HttpDnsSettings.java | 0 .../sdk/android/httpdns/HttpDnsV1Client.java | 19 + .../newsdk/sdk/android/httpdns/ILogger.java | 0 .../sdk/android/httpdns/InitConfig.java | 43 ++ .../newsdk/sdk/android/httpdns/NetType.java | 0 .../android/httpdns/NotUseHttpDnsFilter.java | 0 .../newsdk/sdk/android/httpdns/Region.java | 0 .../sdk/android/httpdns/RequestIpType.java | 0 .../sdk/android/httpdns/SyncService.java | 0 .../sdk/android/httpdns/cache/HostRecord.java | 0 .../android/httpdns/cache/RecordDBHelper.java | 0 .../httpdns/config/ConfigCacheHelper.java | 0 .../android/httpdns/config/RegionServer.java | 0 .../android/httpdns/config/ServerConfig.java | 0 .../android/httpdns/config/SpCacheItem.java | 0 .../config/region/AmericaRegionServer.java | 0 .../config/region/DefaultRegionServer.java | 0 .../config/region/GermanyRegionServer.java | 0 .../config/region/HongKongRegionServer.java | 0 .../config/region/PreReleaseRegionServer.java | 0 .../config/region/RegionServerManager.java | 0 .../config/region/SingaporeRegionServer.java | 0 .../HttpDnsUncaughtExceptionHandler.java | 0 .../httpdns/exception/InitException.java | 0 .../httpdns/impl/AESEncryptService.java | 0 .../sdk/android/httpdns/impl/ErrorImpl.java | 0 .../httpdns/impl/HostResolveLocker.java | 0 .../httpdns/impl/HostResolveRecorder.java | 0 .../android/httpdns/impl/HttpDnsConfig.java | 0 .../android/httpdns/impl/HttpDnsCreator.java | 0 .../httpdns/impl/HttpDnsInstanceHolder.java | 0 .../httpdns/impl/HttpDnsServiceImpl.java | 0 .../sdk/android/httpdns/impl/SignService.java | 0 .../sdk/android/httpdns/log/HttpDnsLog.java | 0 .../httpdns/net/HttpDnsNetworkDetector.java | 0 .../httpdns/net/NetworkStateManager.java | 0 .../network/HttpDnsAdapterException.java | 0 .../network/HttpDnsAdapterOptions.java | 0 .../network/HttpDnsAdapterRequest.java | 0 .../network/HttpDnsAdapterResponse.java | 0 .../httpdns/network/HttpDnsErrorCode.java | 0 .../httpdns/network/HttpDnsHttpAdapter.java | 48 +- .../httpdns/observable/ObservableConfig.java | 0 .../observable/ObservableConstants.java | 0 .../observable/ObservableHttpRequest.java | 0 .../httpdns/observable/ObservableManager.java | 0 .../event/BatchQueryHttpDnsApiEvent.java | 0 .../observable/event/CallSdkApiEvent.java | 0 .../observable/event/CleanHostCacheEvent.java | 0 .../httpdns/observable/event/GroupEvent.java | 0 .../observable/event/LocalDnsEvent.java | 0 .../observable/event/ObservableEvent.java | 0 .../event/QueryHttpDnsApiEvent.java | 0 .../event/ReportingRateExceptionEvent.java | 0 .../event/UpdateRegionServerIpsEvent.java | 0 .../httpdns/ranking/IPRankingBean.java | 0 .../httpdns/ranking/IPRankingCallback.java | 0 .../httpdns/ranking/IPRankingService.java | 0 .../httpdns/ranking/IPRankingTask.java | 0 .../httpdns/request/AsyncRequestTask.java | 0 .../BatchResolveHttpRequestStatusWatcher.java | 0 .../httpdns/request/HttpException.java | 0 .../android/httpdns/request/HttpRequest.java | 0 .../httpdns/request/HttpRequestConfig.java | 0 .../request/HttpRequestFailWatcher.java | 0 .../httpdns/request/HttpRequestTask.java | 0 .../httpdns/request/HttpRequestWatcher.java | 0 .../httpdns/request/HttpRequestWrapper.java | 0 .../httpdns/request/RequestCallback.java | 0 .../httpdns/request/ResponseParser.java | 0 .../httpdns/request/RetryHttpRequest.java | 0 ...SingleResolveHttpRequestStatusWatcher.java | 0 ...eRegionServerHttpRequestStatusWatcher.java | 0 .../resolve/BatchResolveHostService.java | 0 .../httpdns/resolve/CategoryController.java | 0 .../android/httpdns/resolve/HostFilter.java | 0 .../resolve/NormalResolveCategory.java | 0 .../httpdns/resolve/ResolveHostCache.java | 0 .../resolve/ResolveHostCacheGroup.java | 0 .../httpdns/resolve/ResolveHostCategory.java | 0 .../httpdns/resolve/ResolveHostHelper.java | 0 .../resolve/ResolveHostRequestHandler.java | 0 .../httpdns/resolve/ResolveHostResponse.java | 0 .../resolve/ResolveHostResponseParser.java | 0 .../resolve/ResolveHostResultRepo.java | 0 .../httpdns/resolve/ResolveHostService.java | 0 .../httpdns/resolve/ShiftServerWatcher.java | 0 .../httpdns/resolve/SniffException.java | 0 .../httpdns/resolve/SniffResolveCategory.java | 0 .../httpdns/resolve/StatusControl.java | 0 .../httpdns/serverip/RegionServer.java | 0 .../httpdns/serverip/RegionServerIpData.java | 0 .../httpdns/serverip/RegionServerIpRepo.java | 0 .../serverip/RegionServerScheduleService.java | 0 .../serverip/ShiftRegionServerWatcher.java | 0 .../serverip/UpdateRegionServerLocker.java | 0 .../serverip/UpdateRegionServerResponse.java | 0 .../serverip/UpdateRegionServerTask.java | 0 .../ranking/RegionServerRankingCallback.java | 0 .../ranking/RegionServerRankingService.java | 0 .../ranking/RegionServerRankingTask.java | 0 .../httpdns/track/SessionTrackMgr.java | 0 .../sdk/android/httpdns/utils/CommonUtil.java | 0 .../sdk/android/httpdns/utils/Constants.java | 0 .../android/httpdns/utils/NetworkUtil.java | 0 .../sdk/android/httpdns/utils/ThreadUtil.java | 0 .../newsdk/sdk/android/httpdns/HttpDns.java | 0 .../android/httpdns/impl/InstanceCreator.java | 0 .../sdk/android/httpdns/impl/NormalImpl.java | 0 .../sdk/android/httpdns/HttpDnsE2E.java | 5 +- .../android/httpdns/HttpDnsResultTest.java | 0 .../sdk/android/httpdns/InitConfigTest.java | 0 .../httpdns/cache/RecordDBHelperTest.java | 0 .../httpdns/impl/AESEncryptServiceTest.java | 0 .../httpdns/impl/HostResolveRecorderTest.java | 0 .../httpdns/impl/HttpDnsConfigTest.java | 0 .../impl/HttpDnsInstanceHolderTest.java | 0 .../android/httpdns/impl/SignServiceTest.java | 0 .../android/httpdns/log/HttpdnsLogTest.java | 0 .../httpdns/probe/IPRankingTaskTest.java | 0 .../httpdns/request/HttpExceptionTest.java | 0 .../httpdns/request/HttpRequestTest.java | 0 .../request/HttpRequestWatcherTest.java | 0 .../request/HttpRequestWrapperTest.java | 0 .../httpdns/request/RetryHttpRequestTest.java | 0 .../httpdns/resolve/BaseCategoryTest.java | 0 .../resolve/CategoryControllerTest.java | 0 .../httpdns/resolve/HostFilterTest.java | 0 .../httpdns/resolve/NormalCategoryTest.java | 0 .../resolve/ResolveHostResponseTest.java | 0 .../resolve/ResolveHostResultRepoTest.java | 0 .../resolve/ResolveHostResultRepoTest2.java | 0 .../resolve/ShiftServerWatcherTest.java | 0 .../httpdns/resolve/SniffCategoryTest.java | 0 .../httpdns/serverip/ServerIpRepoTest.java | 0 .../serverip/UpdateServerLockerTest.java | 0 .../serverip/UpdateServerResponseTest.java | 0 .../serverip/UpdateServerTaskTest.java | 0 .../android/httpdns/test/app/BusinessApp.java | 0 .../test/helper/ServerStatusHelper.java | 0 .../test/server/BatchResolveHostServer.java | 0 .../httpdns/test/server/DebugApiServer.java | 0 .../httpdns/test/server/HttpDnsServer.java | 0 .../test/server/MockSpeedTestServer.java | 0 .../test/server/ResolveHostServer.java | 0 .../httpdns/test/server/SecretService.java | 0 .../httpdns/test/server/ServerIpsServer.java | 0 .../test/server/base/BaseDataServer.java | 0 .../test/server/base/RequestHandler.java | 0 .../test/server/base/RequestListener.java | 0 .../test/server/base/RequestRecord.java | 0 .../test/server/base/SdkBusinessServer.java | 0 .../httpdns/test/server/base/ServerApi.java | 0 .../test/utils/CountUpAndDownLatch.java | 0 .../test/utils/MultiThreadTestHelper.java | 0 .../httpdns/test/utils/RandomValue.java | 0 .../httpdns/test/utils/ShadowNetworkInfo.java | 0 .../test/utils/SyncExecutorService.java | 0 .../test/utils/TestExecutorService.java | 0 .../httpdns/test/utils/TestLogger.java | 0 .../httpdns/test/utils/UnitTestUtil.java | 0 .../android/httpdns/utils/CommonUtilTest.java | 0 .../sdk/android/httpdns/utils/HexTest.java | 0 .../sdk/android/httpdns/version/V2_3_0.java | 0 .../httpdns/version/V2_3_0_NetType.java | 0 .../httpdns/version/V2_3_0_config.java | 0 .../sdk/android/httpdns-sdk/version.gradle | 0 .../0.0.6/new-android-crashdefend-0.0.6.jar | Bin .../new-android-crashdefend-0.0.6.jar.md5 | 0 .../new-android-crashdefend-0.0.6.jar.sha1 | 0 .../0.0.6/new-android-crashdefend-0.0.6.pom | 0 .../new-android-crashdefend-0.0.6.pom.md5 | 0 .../new-android-crashdefend-0.0.6.pom.sha1 | 0 .../2.6.6/new-android-httpdns-2.6.6.aar | Bin .../2.6.6/new-android-httpdns-2.6.6.aar.md5 | 0 .../2.6.6/new-android-httpdns-2.6.6.aar.sha1 | 0 .../2.6.6/new-android-httpdns-2.6.6.pom | 0 .../2.6.6/new-android-httpdns-2.6.6.pom.md5 | 0 .../2.6.6/new-android-httpdns-2.6.6.pom.sha1 | 0 .../1.2.0/new-android-ipdetector-1.2.0.aar | Bin .../new-android-ipdetector-1.2.0.aar.md5 | 0 .../new-android-ipdetector-1.2.0.aar.sha1 | 0 .../1.2.0/new-android-ipdetector-1.2.0.pom | 0 .../new-android-ipdetector-1.2.0.pom.md5 | 0 .../new-android-ipdetector-1.2.0.pom.sha1 | 0 .../1.2.0/new-android-logger-1.2.0.aar | Bin .../1.2.0/new-android-logger-1.2.0.aar.md5 | 0 .../1.2.0/new-android-logger-1.2.0.aar.sha1 | 0 .../1.2.0/new-android-logger-1.2.0.pom | 0 .../1.2.0/new-android-logger-1.2.0.pom.md5 | 0 .../1.2.0/new-android-logger-1.2.0.pom.sha1 | 0 .../1.1.0/new-android-tool-1.1.0.aar | Bin .../1.1.0/new-android-tool-1.1.0.aar.md5 | 0 .../1.1.0/new-android-tool-1.1.0.aar.sha1 | 0 .../1.1.0/new-android-tool-1.1.0.pom | 0 .../1.1.0/new-android-tool-1.1.0.pom.md5 | 0 .../1.1.0/new-android-tool-1.1.0.pom.sha1 | 0 .../fastjson-1.1.73.android.jar | Bin .../fastjson-1.1.73.android.jar.md5 | 0 .../fastjson-1.1.73.android.jar.sha1 | 0 .../fastjson-1.1.73.android.pom | 0 .../fastjson-1.1.73.android.pom.md5 | 0 .../fastjson-1.1.73.android.pom.sha1 | 0 .../sdk/android/settings.gradle | 0 .../sdk/build_packages.sh | 62 ++- .../sdk/dist/httpdns-sdk-android-v1.0.0.zip | Bin 0 -> 245267 bytes .../sdk/flutter/new_httpdns}/CHANGELOG.md | 0 .../sdk/flutter/new_httpdns}/LICENSE | 2 +- .../sdk/flutter/new_httpdns}/README.md | 14 +- .../new_httpdns}/analysis_options.yaml | 0 .../flutter/new_httpdns}/android/build.gradle | 4 +- .../new_httpdns}/android/consumer-rules.pro | 0 .../new_httpdns}/android/settings.gradle | 0 .../android/src/main/AndroidManifest.xml | 0 .../com/new/ams/httpdns/NewHttpDnsPlugin.kt | 0 .../flutter/new_httpdns}/example/README.md | 0 .../example/analysis_options.yaml | 0 .../example/android/app/build.gradle.kts | 0 .../android/app/src/debug/AndroidManifest.xml | 0 .../android/app/src/main/AndroidManifest.xml | 0 .../httpdns_flutter_demo/MainActivity.kt | 0 .../res/drawable-v21/launch_background.xml | 0 .../main/res/drawable/launch_background.xml | 0 .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin .../app/src/main/res/values-night/styles.xml | 0 .../app/src/main/res/values/styles.xml | 0 .../app/src/profile/AndroidManifest.xml | 0 .../example/android/build.gradle.kts | 0 .../example/android/gradle.properties | 0 .../gradle/wrapper/gradle-wrapper.properties | 0 .../example/android/settings.gradle.kts | 0 .../ios/Flutter/AppFrameworkInfo.plist | 0 .../example/ios/Flutter/Debug.xcconfig | 0 .../example/ios/Flutter/Release.xcconfig | 0 .../flutter/new_httpdns}/example/ios/Podfile | 2 +- .../ios/Runner.xcodeproj/project.pbxproj | 6 +- .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 0 .../xcshareddata/xcschemes/Runner.xcscheme | 0 .../contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 0 .../example/ios/Runner/AppDelegate.swift | 0 .../AppIcon.appiconset/Contents.json | 0 .../Icon-App-1024x1024@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin .../Icon-App-83.5x83.5@2x.png | Bin .../LaunchImage.imageset/Contents.json | 0 .../LaunchImage.imageset/LaunchImage.png | Bin .../LaunchImage.imageset/LaunchImage@2x.png | Bin .../LaunchImage.imageset/LaunchImage@3x.png | Bin .../LaunchImage.imageset/README.md | 0 .../Runner/Base.lproj/LaunchScreen.storyboard | 0 .../ios/Runner/Base.lproj/Main.storyboard | 0 .../example/ios/Runner/Info.plist | 0 .../ios/Runner/Runner-Bridging-Header.h | 0 .../example/ios/RunnerTests/RunnerTests.swift | 0 .../new_httpdns}/example/lib/main.dart | 0 .../lib/net/httpdns_http_client_adapter.dart | 0 .../flutter/new_httpdns}/example/pubspec.lock | 2 +- .../flutter/new_httpdns}/example/pubspec.yaml | 8 +- .../ios/Classes/HttpdnsPlugin.swift | 12 +- .../new_httpdns/ios/new_httpdns.podspec | 12 +- .../flutter/new_httpdns/lib/new_httpdns.dart | 0 .../sdk/flutter/new_httpdns}/pubspec.lock | 0 .../sdk/flutter/new_httpdns}/pubspec.yaml | 10 +- {EdgeHttpDNS => HttpDNSSDK}/sdk/ios/CLAUDE.md | 38 +- HttpDNSSDK/sdk/ios/NewHTTPDNS.podspec | 46 ++ .../ios/NewHttpDNS.xcodeproj}/project.pbxproj | 182 +++---- .../xcschemes/NewHttpDNS.xcscheme | 18 +- .../xcschemes/NewHttpDNSTestDemo.xcscheme | 18 +- .../xcschemes/NewHttpDNSTests.xcscheme | 20 +- .../Config/HttpdnsInternalConstant.h | 0 .../Config/HttpdnsPublicConstant.h | 0 .../Config/HttpdnsRegionConfigLoader.h | 0 .../Config/HttpdnsRegionConfigLoader.m | 0 .../NewHttpDNS}/HttpdnsDegradationDelegate.h | 0 .../sdk/ios/NewHttpDNS}/HttpdnsEdgeService.h | 0 .../sdk/ios/NewHttpDNS}/HttpdnsEdgeService.m | 0 .../ios/NewHttpDNS}/HttpdnsLocalResolver.h | 0 .../ios/NewHttpDNS}/HttpdnsLocalResolver.m | 0 .../ios/NewHttpDNS}/HttpdnsRemoteResolver.h | 0 .../ios/NewHttpDNS}/HttpdnsRemoteResolver.m | 0 .../ios/NewHttpDNS}/HttpdnsRequestManager.h | 0 .../ios/NewHttpDNS}/HttpdnsRequestManager.m | 0 .../sdk/ios/NewHttpDNS}/HttpdnsService.h | 0 .../sdk/ios/NewHttpDNS}/HttpdnsService.m | 0 .../ios/NewHttpDNS}/HttpdnsService_Internal.h | 0 .../sdk/ios/NewHttpDNS}/Info.plist | 0 .../IpStack/HttpdnsIpStackDetector.h | 0 .../IpStack/HttpdnsIpStackDetector.m | 10 +- .../sdk/ios/NewHttpDNS}/Log/HttpdnsLog.h | 0 .../sdk/ios/NewHttpDNS}/Log/HttpdnsLog.m | 0 .../ios/NewHttpDNS}/Log/HttpdnsLog_Internal.h | 0 .../NewHttpDNS}/Log/HttpdnsLoggerProtocol.h | 0 .../ios/NewHttpDNS}/Model/HttpdnsHostObject.h | 0 .../ios/NewHttpDNS}/Model/HttpdnsHostObject.m | 0 .../ios/NewHttpDNS}/Model/HttpdnsHostRecord.h | 0 .../ios/NewHttpDNS}/Model/HttpdnsHostRecord.m | 0 .../ios/NewHttpDNS}/Model/HttpdnsRequest.h | 0 .../ios/NewHttpDNS}/Model/HttpdnsRequest.m | 0 .../Model/HttpdnsRequest_Internal.h | 0 .../sdk/ios/NewHttpDNS}/Model/HttpdnsResult.h | 0 .../sdk/ios/NewHttpDNS}/Model/HttpdnsResult.m | 0 .../NewHttpDNS}/Network/HttpdnsNWHTTPClient.h | 0 .../NewHttpDNS}/Network/HttpdnsNWHTTPClient.m | 0 .../Network/HttpdnsNWHTTPClient_Internal.h | 0 .../Network/HttpdnsNWReusableConnection.h | 0 .../Network/HttpdnsNWReusableConnection.m | 0 .../sdk/ios/NewHttpDNS/NewHttpDNS.h | 0 .../ios/NewHttpDNS}/Persistent/HttpdnsDB.h | 0 .../ios/NewHttpDNS}/Persistent/HttpdnsDB.m | 8 +- .../Persistent/HttpdnsPersistenceUtils.h | 0 .../Persistent/HttpdnsPersistenceUtils.m | 0 .../Scheduler/HttpdnsScheduleCenter.h | 0 .../Scheduler/HttpdnsScheduleCenter.m | 36 +- .../Scheduler/HttpdnsScheduleExecutor.h | 0 .../Scheduler/HttpdnsScheduleExecutor.m | 0 .../sdk/ios/NewHttpDNS}/Utils/HttpDnsLocker.h | 0 .../sdk/ios/NewHttpDNS}/Utils/HttpDnsLocker.m | 0 .../Utils/HttpdnsHostObjectInMemoryCache.h | 0 .../Utils/HttpdnsHostObjectInMemoryCache.m | 0 .../Utils/HttpdnsIPQualityDetector.h | 0 .../Utils/HttpdnsIPQualityDetector.m | 8 +- .../NewHttpDNS}/Utils/HttpdnsReachability.h | 0 .../NewHttpDNS}/Utils/HttpdnsReachability.m | 0 .../sdk/ios/NewHttpDNS}/Utils/HttpdnsUtil.h | 0 .../sdk/ios/NewHttpDNS}/Utils/HttpdnsUtil.m | 0 .../sdk/ios/NewHttpDNSTestDemo}/AppDelegate.h | 0 .../sdk/ios/NewHttpDNSTestDemo}/AppDelegate.m | 0 .../AppIcon.appiconset/Contents.json | 0 .../Assets.xcassets/Contents.json | 0 .../Base.lproj/LaunchScreen.storyboard | 0 .../ios/NewHttpDNSTestDemo}/DemoConfig.plist | 0 .../NewHttpDNSTestDemo}/DemoConfigLoader.h | 0 .../NewHttpDNSTestDemo}/DemoConfigLoader.m | 0 .../NewHttpDNSTestDemo}/DemoHttpdnsScenario.h | 0 .../NewHttpDNSTestDemo}/DemoHttpdnsScenario.m | 12 +- .../DemoLogViewController.h | 0 .../DemoLogViewController.m | 0 .../NewHttpDNSTestDemo}/DemoResolveModel.h | 0 .../NewHttpDNSTestDemo}/DemoResolveModel.m | 6 +- .../NewHttpDNSTestDemo}/DemoViewController.h | 0 .../NewHttpDNSTestDemo}/DemoViewController.m | 10 +- .../sdk/ios/NewHttpDNSTestDemo}/Info.plist | 0 .../sdk/ios/NewHttpDNSTestDemo}/main.m | 0 .../sdk/ios/NewHttpDNSTests}/DB/DBTest.m | 0 .../HighLevelTest/CacheKeyFunctionTest.m | 0 .../HighLevelTest/CustomTTLTest.m | 0 .../HighLevelTest/EnableReuseExpiredIpTest.m | 0 .../HighLevelTest/HttpdnsHostObjectTest.m | 0 .../HighLevelTest/ManuallyCleanCacheTest.m | 0 .../MultithreadCorrectnessTest.m | 0 .../PresetCacheAndRetrieveTest.m | 0 .../ResolvingEffectiveHostTest.m | 0 .../HighLevelTest/ScheduleCenterV4Test.m | 0 .../HighLevelTest/ScheduleCenterV6Test.m | 0 .../HighLevelTest/SdnsScenarioTest.m | 0 .../IPDetector/IpDetectorTest.m | 0 .../sdk/ios/NewHttpDNSTests}/Info.plist | 0 .../Network/HttpdnsNWHTTPClientTestBase.h | 0 .../Network/HttpdnsNWHTTPClientTestBase.m | 0 .../Network/HttpdnsNWHTTPClientTestHelper.h | 0 .../Network/HttpdnsNWHTTPClientTestHelper.m | 0 .../Network/HttpdnsNWHTTPClientTests.m | 0 ...ttpdnsNWHTTPClient_BasicIntegrationTests.m | 0 .../HttpdnsNWHTTPClient_ConcurrencyTests.m | 0 ...dnsNWHTTPClient_EdgeCasesAndTimeoutTests.m | 0 .../HttpdnsNWHTTPClient_PoolManagementTests.m | 0 .../HttpdnsNWHTTPClient_StateMachineTests.m | 0 .../NewHttpDNSTests}/Network/MOCK_SERVER.md | 0 .../ios/NewHttpDNSTests}/Network/README.md | 0 .../Network/STATE_MACHINE_ANALYSIS.md | 0 .../Network/TIMEOUT_ANALYSIS.md | 0 .../NewHttpDNSTests}/Network/mock_server.py | 0 .../OutdatedTest/NetworkManager.h | 0 .../OutdatedTest/NetworkManager.m | 0 .../ios/NewHttpDNSTests}/Testbase/TestBase.h | 0 .../ios/NewHttpDNSTests}/Testbase/TestBase.m | 0 .../Testbase/XCTestCase+AsyncTesting.h | 0 .../Testbase/XCTestCase+AsyncTesting.m | 0 {EdgeHttpDNS => HttpDNSSDK}/sdk/ios/Podfile | 6 +- {EdgeHttpDNS => HttpDNSSDK}/sdk/ios/README.md | 0 .../sdk/ios/build_xc_framework.sh | 2 +- .../sdk/ios/resource/PrivacyInfo.xcprivacy | 0 HttpDNSSDK/spec/error-codes.md | 13 + HttpDNSSDK/spec/resolve-api.md | 17 + HttpDNSSDK/tools/release/httpdns/README.md | 5 + .../tools/release/httpdns/release_android.ps1 | 100 ++++ .../tools/release/httpdns/release_flutter.ps1 | 4 + .../tools/release/httpdns/release_ios.sh | 8 + 700 files changed, 6096 insertions(+), 2712 deletions(-) create mode 100644 EdgeAPI/internal/installers/upgrade_queue.go create mode 100644 EdgeAPI/internal/setup/upgrade_config.go create mode 100644 EdgeAPI/internal/tasks/httpdns_node_monitor_task.go create mode 100644 EdgeAdmin/internal/configloaders/upgrade_config.go create mode 100644 EdgeAdmin/internal/web/actions/default/httpdns/clusters/upgradeStatus.go create mode 100644 EdgeAdmin/internal/web/actions/default/settings/upgrade/dns_helper_default.go create mode 100644 EdgeAdmin/internal/web/actions/default/settings/upgrade/dns_helper_plus.go create mode 100644 EdgeAdmin/internal/web/actions/default/settings/upgrade/status.go create mode 100644 EdgeAdmin/internal/web/actions/default/settings/upgrade/upgradeNode.go create mode 100644 EdgeAdmin/web/views/@default/httpdns/clusters/upgradeRemote.js create mode 100644 EdgeAdmin/web/views/@default/settings/upgrade/index.js delete mode 100644 EdgeHttpDNS/Changelog及升级指南_v1.4.8.txt delete mode 100644 EdgeHttpDNS/HTTPDNS主计划-V1.2.md delete mode 100644 EdgeHttpDNS/HTTPDNS后端开发计划.md delete mode 100644 EdgeHttpDNS/HTTPDNS用户使用手册.md delete mode 100644 EdgeHttpDNS/HTTPDNS管理员配置手册.md delete mode 100644 EdgeHttpDNS/SNI隐匿开发计划.md delete mode 100644 EdgeHttpDNS/sdk/.tmp_android_bundle/README.md delete mode 100644 EdgeHttpDNS/sdk/.tmp_android_bundle/alicloud-android-crashdefend-0.0.6.jar delete mode 100644 EdgeHttpDNS/sdk/.tmp_android_bundle/alicloud-android-httpdns-2.6.7.aar delete mode 100644 EdgeHttpDNS/sdk/.tmp_android_bundle/alicloud-android-ipdetector-1.2.0.aar delete mode 100644 EdgeHttpDNS/sdk/.tmp_android_bundle/alicloud-android-logger-1.2.0.aar delete mode 100644 EdgeHttpDNS/sdk/.tmp_android_bundle/proguard-rules.pro delete mode 100644 EdgeHttpDNS/sdk/android/.gitignore delete mode 100644 EdgeHttpDNS/sdk/android/README.md delete mode 100644 EdgeHttpDNS/sdk/android/app/.gitignore delete mode 100644 EdgeHttpDNS/sdk/android/demo/.gitignore delete mode 100644 EdgeHttpDNS/sdk/dist/alicloud-android-crashdefend-0.0.6.jar delete mode 100644 EdgeHttpDNS/sdk/dist/alicloud-android-httpdns-2.6.7.aar delete mode 100644 EdgeHttpDNS/sdk/dist/alicloud-android-ipdetector-1.2.0.aar delete mode 100644 EdgeHttpDNS/sdk/dist/alicloud-android-logger-1.2.0.aar delete mode 100644 EdgeHttpDNS/sdk/dist/proguard-rules.pro delete mode 100644 EdgeHttpDNS/sdk/flutter/aliyun_httpdns/.gitignore delete mode 100644 EdgeHttpDNS/sdk/flutter/aliyun_httpdns/example/android/.gitignore delete mode 100644 EdgeHttpDNS/sdk/flutter/aliyun_httpdns/example/ios/.gitignore delete mode 100644 EdgeHttpDNS/sdk/ios/.gitignore delete mode 100644 EdgeHttpDNS/sdk/ios/AlicloudHTTPDNS.podspec delete mode 100644 EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests/Network/.gitignore delete mode 100644 EdgeHttpDNS/新建文本文档.txt create mode 100644 HttpDNSSDK/.gitignore rename {EdgeHttpDNS => HttpDNSSDK/docs}/Flutter SDK集成文档.md (98%) rename {EdgeHttpDNS => HttpDNSSDK/docs}/HTTPDNS SDK 集成文档(Android).md (70%) rename {EdgeHttpDNS => HttpDNSSDK/docs}/iOS SDK集成文档.md (98%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/SOURCE_LOCK.md (71%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/THIRD_PARTY_NOTICES.md (72%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/.github/ISSUE_TEMPLATE/bug_report.md (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/LICENSE (100%) rename {EdgeHttpDNS/sdk/dist => HttpDNSSDK/sdk/android}/README.md (70%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/build.gradle (97%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/proguard-rules.pro (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/AndroidManifest.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/java/com/newsdk/ams/httpdns/demo/HttpDnsActivity.java (98%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/java/com/newsdk/ams/httpdns/demo/HttpDnsHolder.java (96%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/java/com/newsdk/ams/httpdns/demo/MyApp.java (78%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/java/com/newsdk/ams/httpdns/demo/NetworkRequest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/java/com/newsdk/ams/httpdns/demo/SDNSActivity.java (98%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/java/com/newsdk/ams/httpdns/demo/WebViewActivity.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/java/com/newsdk/ams/httpdns/demo/base/BaseActivity.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/java/com/newsdk/ams/httpdns/demo/http/HttpUrlConnectionRequest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/java/com/newsdk/ams/httpdns/demo/okhttp/OkHttpRequest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/java/com/newsdk/ams/httpdns/demo/utils/SpUtil.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/java/com/newsdk/ams/httpdns/demo/utils/ThreadUtil.java (97%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/java/com/newsdk/ams/httpdns/demo/utils/Util.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/res/drawable/new_bg.9.png (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/res/layout/activity_base.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/res/layout/activity_webview.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/res/layout/item_autocomplete_button.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/res/layout/item_autocomplete_edittext_button.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/res/layout/item_edit_button.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/res/layout/item_edit_edit_button.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/res/layout/item_four_button.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/res/layout/item_one_button.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/res/layout/item_three_button.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/res/layout/item_two_button.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/res/mipmap-hdpi/back.png (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/res/mipmap-hdpi/ic_launcher.png (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/res/mipmap-mdpi/ic_launcher.png (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/res/values/colors.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/res/values/strings.xml (98%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/res/values/styles.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/app/src/main/res/xml/network_security_config.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/build.gradle (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/build.gradle (82%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/gradle.properties (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/proguard-rules.pro (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/androidTest/java/com/newsdk/ams/emas/demo/ExampleInstrumentedTest.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/AndroidManifest.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/BatchResolveCacheHolder.kt (97%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/HttpDnsApplication.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/HttpDnsServiceHolder.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/MainActivity.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/PreResolveCacheHolder.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/SingleLiveData.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/TtlCacheHolder.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/Util.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/constant/Constant.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/net/HttpURLConnectionRequest.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/net/IRequest.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/net/OkHttpClientSingleton.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/net/OkHttpLog.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/net/OkHttpRequest.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/net/TLSSNISocketFactory.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/ui/basic/BasicSettingFragment.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/ui/basic/BasicSettingViewModel.kt (96%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/ui/basic/IBasicShowDialog.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/ui/info/InfoFragment.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/ui/info/InfoViewModel.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/ui/info/SdnsGlobalSettingActivity.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/ui/info/list/ListActivity.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/ui/info/list/ListAdapter.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/ui/info/list/ListItem.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/ui/info/list/ListItemType.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/ui/info/list/ListViewModel.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/ui/practice/BestPracticeFragment.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/ui/practice/BestPracticeViewModel.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/ui/practice/HttpDnsWebviewGetActivity.kt (98%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/ui/practice/IBestPracticeShowDialog.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/ui/practice/SNISocketFactory.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/ui/resolve/IResolveShowDialog.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/ui/resolve/NetRequestType.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/ui/resolve/ResolveFragment.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/ui/resolve/ResolveViewModel.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/ui/resolve/Response.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/ui/resolve/SchemaType.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/java/com/newsdk/ams/emas/demo/widget/SwipeLayout.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/drawable-v24/ic_launcher_foreground.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/drawable/ic_add.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/drawable/ic_arrow_left.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/drawable/ic_arrow_right.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/drawable/ic_back.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/drawable/ic_dns.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/drawable/ic_home_black_24dp.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/drawable/ic_httpdns.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/drawable/ic_information.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/drawable/ic_practice.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/layout/activity_http_dns_webview.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/layout/activity_list.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/layout/activity_main.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/layout/activity_sdns_global_setting.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/layout/dialog_input.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/layout/dialog_input_2.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/layout/dialog_input_3.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/layout/fragment_basic_setting.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/layout/fragment_best_practice.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/layout/fragment_info.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/layout/fragment_resolve.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/layout/info_list_item.xml (97%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/menu/bottom_nav_menu.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/mipmap-hdpi/ic_launcher.png (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/mipmap-mdpi/ic_launcher.png (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/mipmap-xhdpi/ic_launcher.png (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/navigation/mobile_navigation.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/values-land/dimens.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/values-night/themes.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/values-w1240dp/dimens.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/values-w600dp/dimens.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/values-zh-rCN/strings.xml (99%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/values/attrs.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/values/colors.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/values/dimens.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/values/strings.xml (98%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/values/styles.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/values/themes.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/main/res/xml/network_security_config.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/demo/src/test/java/com/newsdk/ams/emas/demo/ExampleUnitTest.kt (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/gradle.properties (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/gradle/wrapper/gradle-wrapper.jar (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/gradle/wrapper/gradle-wrapper.properties (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/gradlew (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/gradlew.bat (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/build.gradle (92%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/proguard-rules-for-test.pro (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/proguard-rules.pro (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/end2end/java/com/newsdk/sdk/android/httpdns/ApiForTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/end2end/java/com/newsdk/sdk/android/httpdns/BeforeHttpDnsServiceInit.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/end2end/java/com/newsdk/sdk/android/httpdns/HttpDns.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/end2end/java/com/newsdk/sdk/android/httpdns/InitManager.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/end2end/java/com/newsdk/sdk/android/httpdns/impl/HttpDnsServiceTestImpl.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/end2end/java/com/newsdk/sdk/android/httpdns/impl/InstanceCreator.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/intl/java/com/newsdk/sdk/android/httpdns/HttpDns.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/intl/java/com/newsdk/sdk/android/httpdns/impl/InstanceCreator.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/intl/java/com/newsdk/sdk/android/httpdns/impl/IntlImpl.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/AndroidManifest.xml (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/CacheTtlChanger.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/DegradationFilter.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/HTTPDNSResult.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/HTTPDNSResultWrapper.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/HttpDnsCallback.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/HttpDnsService.java (99%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/HttpDnsSettings.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/HttpDnsV1Client.java (79%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/ILogger.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/InitConfig.java (92%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/NetType.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/NotUseHttpDnsFilter.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/Region.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/RequestIpType.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/SyncService.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/cache/HostRecord.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/cache/RecordDBHelper.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/config/ConfigCacheHelper.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/config/RegionServer.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/config/ServerConfig.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/config/SpCacheItem.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/config/region/AmericaRegionServer.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/config/region/DefaultRegionServer.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/config/region/GermanyRegionServer.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/config/region/HongKongRegionServer.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/config/region/PreReleaseRegionServer.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/config/region/RegionServerManager.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/config/region/SingaporeRegionServer.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/exception/HttpDnsUncaughtExceptionHandler.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/exception/InitException.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/impl/AESEncryptService.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/impl/ErrorImpl.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/impl/HostResolveLocker.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/impl/HostResolveRecorder.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/impl/HttpDnsConfig.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/impl/HttpDnsCreator.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/impl/HttpDnsInstanceHolder.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/impl/HttpDnsServiceImpl.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/impl/SignService.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/log/HttpDnsLog.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/net/HttpDnsNetworkDetector.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/net/NetworkStateManager.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/network/HttpDnsAdapterException.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/network/HttpDnsAdapterOptions.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/network/HttpDnsAdapterRequest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/network/HttpDnsAdapterResponse.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/network/HttpDnsErrorCode.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/network/HttpDnsHttpAdapter.java (92%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/observable/ObservableConfig.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/observable/ObservableConstants.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/observable/ObservableHttpRequest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/observable/ObservableManager.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/observable/event/BatchQueryHttpDnsApiEvent.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/observable/event/CallSdkApiEvent.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/observable/event/CleanHostCacheEvent.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/observable/event/GroupEvent.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/observable/event/LocalDnsEvent.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/observable/event/ObservableEvent.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/observable/event/QueryHttpDnsApiEvent.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/observable/event/ReportingRateExceptionEvent.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/observable/event/UpdateRegionServerIpsEvent.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/ranking/IPRankingBean.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/ranking/IPRankingCallback.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/ranking/IPRankingService.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/ranking/IPRankingTask.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/request/AsyncRequestTask.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/request/BatchResolveHttpRequestStatusWatcher.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/request/HttpException.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/request/HttpRequest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/request/HttpRequestConfig.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/request/HttpRequestFailWatcher.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/request/HttpRequestTask.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/request/HttpRequestWatcher.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/request/HttpRequestWrapper.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/request/RequestCallback.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/request/ResponseParser.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/request/RetryHttpRequest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/request/SingleResolveHttpRequestStatusWatcher.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/request/UpdateRegionServerHttpRequestStatusWatcher.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/resolve/BatchResolveHostService.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/resolve/CategoryController.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/resolve/HostFilter.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/resolve/NormalResolveCategory.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/resolve/ResolveHostCache.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/resolve/ResolveHostCacheGroup.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/resolve/ResolveHostCategory.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/resolve/ResolveHostHelper.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/resolve/ResolveHostRequestHandler.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/resolve/ResolveHostResponse.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/resolve/ResolveHostResponseParser.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/resolve/ResolveHostResultRepo.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/resolve/ResolveHostService.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/resolve/ShiftServerWatcher.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/resolve/SniffException.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/resolve/SniffResolveCategory.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/resolve/StatusControl.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/serverip/RegionServer.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/serverip/RegionServerIpData.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/serverip/RegionServerIpRepo.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/serverip/RegionServerScheduleService.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/serverip/ShiftRegionServerWatcher.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/serverip/UpdateRegionServerLocker.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/serverip/UpdateRegionServerResponse.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/serverip/UpdateRegionServerTask.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/serverip/ranking/RegionServerRankingCallback.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/serverip/ranking/RegionServerRankingService.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/serverip/ranking/RegionServerRankingTask.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/track/SessionTrackMgr.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/utils/CommonUtil.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/utils/Constants.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/utils/NetworkUtil.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/main/java/com/newsdk/sdk/android/httpdns/utils/ThreadUtil.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/normal/java/com/newsdk/sdk/android/httpdns/HttpDns.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/normal/java/com/newsdk/sdk/android/httpdns/impl/InstanceCreator.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/normal/java/com/newsdk/sdk/android/httpdns/impl/NormalImpl.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/HttpDnsE2E.java (99%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/HttpDnsResultTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/InitConfigTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/cache/RecordDBHelperTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/impl/AESEncryptServiceTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/impl/HostResolveRecorderTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/impl/HttpDnsConfigTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/impl/HttpDnsInstanceHolderTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/impl/SignServiceTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/log/HttpdnsLogTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/probe/IPRankingTaskTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/request/HttpExceptionTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/request/HttpRequestTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/request/HttpRequestWatcherTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/request/HttpRequestWrapperTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/request/RetryHttpRequestTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/resolve/BaseCategoryTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/resolve/CategoryControllerTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/resolve/HostFilterTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/resolve/NormalCategoryTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/resolve/ResolveHostResponseTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/resolve/ResolveHostResultRepoTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/resolve/ResolveHostResultRepoTest2.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/resolve/ShiftServerWatcherTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/resolve/SniffCategoryTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/serverip/ServerIpRepoTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/serverip/UpdateServerLockerTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/serverip/UpdateServerResponseTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/serverip/UpdateServerTaskTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/test/app/BusinessApp.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/test/helper/ServerStatusHelper.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/test/server/BatchResolveHostServer.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/test/server/DebugApiServer.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/test/server/HttpDnsServer.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/test/server/MockSpeedTestServer.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/test/server/ResolveHostServer.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/test/server/SecretService.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/test/server/ServerIpsServer.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/test/server/base/BaseDataServer.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/test/server/base/RequestHandler.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/test/server/base/RequestListener.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/test/server/base/RequestRecord.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/test/server/base/SdkBusinessServer.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/test/server/base/ServerApi.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/test/utils/CountUpAndDownLatch.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/test/utils/MultiThreadTestHelper.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/test/utils/RandomValue.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/test/utils/ShadowNetworkInfo.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/test/utils/SyncExecutorService.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/test/utils/TestExecutorService.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/test/utils/TestLogger.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/test/utils/UnitTestUtil.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/utils/CommonUtilTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/utils/HexTest.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/version/V2_3_0.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/version/V2_3_0_NetType.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/src/testEnd2end/java/com/newsdk/sdk/android/httpdns/version/V2_3_0_config.java (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/httpdns-sdk/version.gradle (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-crashdefend/0.0.6/new-android-crashdefend-0.0.6.jar (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-crashdefend/0.0.6/new-android-crashdefend-0.0.6.jar.md5 (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-crashdefend/0.0.6/new-android-crashdefend-0.0.6.jar.sha1 (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-crashdefend/0.0.6/new-android-crashdefend-0.0.6.pom (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-crashdefend/0.0.6/new-android-crashdefend-0.0.6.pom.md5 (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-crashdefend/0.0.6/new-android-crashdefend-0.0.6.pom.sha1 (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-httpdns/2.6.6/new-android-httpdns-2.6.6.aar (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-httpdns/2.6.6/new-android-httpdns-2.6.6.aar.md5 (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-httpdns/2.6.6/new-android-httpdns-2.6.6.aar.sha1 (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-httpdns/2.6.6/new-android-httpdns-2.6.6.pom (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-httpdns/2.6.6/new-android-httpdns-2.6.6.pom.md5 (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-httpdns/2.6.6/new-android-httpdns-2.6.6.pom.sha1 (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-ipdetector/1.2.0/new-android-ipdetector-1.2.0.aar (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-ipdetector/1.2.0/new-android-ipdetector-1.2.0.aar.md5 (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-ipdetector/1.2.0/new-android-ipdetector-1.2.0.aar.sha1 (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-ipdetector/1.2.0/new-android-ipdetector-1.2.0.pom (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-ipdetector/1.2.0/new-android-ipdetector-1.2.0.pom.md5 (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-ipdetector/1.2.0/new-android-ipdetector-1.2.0.pom.sha1 (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-logger/1.2.0/new-android-logger-1.2.0.aar (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-logger/1.2.0/new-android-logger-1.2.0.aar.md5 (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-logger/1.2.0/new-android-logger-1.2.0.aar.sha1 (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-logger/1.2.0/new-android-logger-1.2.0.pom (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-logger/1.2.0/new-android-logger-1.2.0.pom.md5 (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-logger/1.2.0/new-android-logger-1.2.0.pom.sha1 (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-tool/1.1.0/new-android-tool-1.1.0.aar (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-tool/1.1.0/new-android-tool-1.1.0.aar.md5 (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-tool/1.1.0/new-android-tool-1.1.0.aar.sha1 (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-tool/1.1.0/new-android-tool-1.1.0.pom (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-tool/1.1.0/new-android-tool-1.1.0.pom.md5 (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/ams/new-android-tool/1.1.0/new-android-tool-1.1.0.pom.sha1 (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/fastjson/1.1.73.android/fastjson-1.1.73.android.jar (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/fastjson/1.1.73.android/fastjson-1.1.73.android.jar.md5 (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/fastjson/1.1.73.android/fastjson-1.1.73.android.jar.sha1 (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/fastjson/1.1.73.android/fastjson-1.1.73.android.pom (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/fastjson/1.1.73.android/fastjson-1.1.73.android.pom.md5 (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/local-maven/com/newsdk/fastjson/1.1.73.android/fastjson-1.1.73.android.pom.sha1 (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/android/settings.gradle (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/build_packages.sh (55%) create mode 100644 HttpDNSSDK/sdk/dist/httpdns-sdk-android-v1.0.0.zip rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/CHANGELOG.md (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/LICENSE (96%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/README.md (79%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/analysis_options.yaml (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/android/build.gradle (83%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/android/consumer-rules.pro (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/android/settings.gradle (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/android/src/main/AndroidManifest.xml (100%) rename EdgeHttpDNS/sdk/flutter/aliyun_httpdns/android/src/main/kotlin/com/aliyun/ams/httpdns/AliyunHttpDnsPlugin.kt => HttpDNSSDK/sdk/flutter/new_httpdns/android/src/main/kotlin/com/new/ams/httpdns/NewHttpDnsPlugin.kt (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/README.md (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/analysis_options.yaml (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/android/app/build.gradle.kts (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/android/app/src/debug/AndroidManifest.xml (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/android/app/src/main/AndroidManifest.xml (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/android/app/src/main/kotlin/com/example/httpdns_flutter_demo/MainActivity.kt (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/android/app/src/main/res/drawable-v21/launch_background.xml (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/android/app/src/main/res/drawable/launch_background.xml (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/android/app/src/main/res/values-night/styles.xml (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/android/app/src/main/res/values/styles.xml (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/android/app/src/profile/AndroidManifest.xml (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/android/build.gradle.kts (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/android/gradle.properties (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/android/gradle/wrapper/gradle-wrapper.properties (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/android/settings.gradle.kts (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Flutter/AppFrameworkInfo.plist (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Flutter/Debug.xcconfig (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Flutter/Release.xcconfig (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Podfile (97%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner.xcodeproj/project.pbxproj (99%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner.xcworkspace/contents.xcworkspacedata (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner/AppDelegate.swift (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner/Base.lproj/LaunchScreen.storyboard (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner/Base.lproj/Main.storyboard (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner/Info.plist (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/Runner/Runner-Bridging-Header.h (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/ios/RunnerTests/RunnerTests.swift (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/lib/main.dart (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/lib/net/httpdns_http_client_adapter.dart (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/pubspec.lock (99%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/example/pubspec.yaml (78%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/ios/Classes/HttpdnsPlugin.swift (95%) rename EdgeHttpDNS/sdk/flutter/aliyun_httpdns/ios/aliyun_httpdns.podspec => HttpDNSSDK/sdk/flutter/new_httpdns/ios/new_httpdns.podspec (73%) rename EdgeHttpDNS/sdk/flutter/aliyun_httpdns/lib/aliyun_httpdns.dart => HttpDNSSDK/sdk/flutter/new_httpdns/lib/new_httpdns.dart (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/pubspec.lock (100%) rename {EdgeHttpDNS/sdk/flutter/aliyun_httpdns => HttpDNSSDK/sdk/flutter/new_httpdns}/pubspec.yaml (92%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/ios/CLAUDE.md (85%) create mode 100644 HttpDNSSDK/sdk/ios/NewHTTPDNS.podspec rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS.xcodeproj => HttpDNSSDK/sdk/ios/NewHttpDNS.xcodeproj}/project.pbxproj (91%) rename EdgeHttpDNS/sdk/ios/AlicloudHttpDNS.xcodeproj/xcshareddata/xcschemes/AlicloudHttpDNS.xcscheme => HttpDNSSDK/sdk/ios/NewHttpDNS.xcodeproj/xcshareddata/xcschemes/NewHttpDNS.xcscheme (81%) rename EdgeHttpDNS/sdk/ios/AlicloudHttpDNS.xcodeproj/xcshareddata/xcschemes/AlicloudHttpDNSTestDemo.xcscheme => HttpDNSSDK/sdk/ios/NewHttpDNS.xcodeproj/xcshareddata/xcschemes/NewHttpDNSTestDemo.xcscheme (80%) rename EdgeHttpDNS/sdk/ios/AlicloudHttpDNS.xcodeproj/xcshareddata/xcschemes/AlicloudHttpDNSTests.xcscheme => HttpDNSSDK/sdk/ios/NewHttpDNS.xcodeproj/xcshareddata/xcschemes/NewHttpDNSTests.xcscheme (81%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Config/HttpdnsInternalConstant.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Config/HttpdnsPublicConstant.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Config/HttpdnsRegionConfigLoader.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Config/HttpdnsRegionConfigLoader.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/HttpdnsDegradationDelegate.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/HttpdnsEdgeService.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/HttpdnsEdgeService.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/HttpdnsLocalResolver.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/HttpdnsLocalResolver.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/HttpdnsRemoteResolver.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/HttpdnsRemoteResolver.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/HttpdnsRequestManager.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/HttpdnsRequestManager.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/HttpdnsService.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/HttpdnsService.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/HttpdnsService_Internal.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Info.plist (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/IpStack/HttpdnsIpStackDetector.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/IpStack/HttpdnsIpStackDetector.m (94%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Log/HttpdnsLog.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Log/HttpdnsLog.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Log/HttpdnsLog_Internal.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Log/HttpdnsLoggerProtocol.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Model/HttpdnsHostObject.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Model/HttpdnsHostObject.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Model/HttpdnsHostRecord.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Model/HttpdnsHostRecord.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Model/HttpdnsRequest.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Model/HttpdnsRequest.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Model/HttpdnsRequest_Internal.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Model/HttpdnsResult.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Model/HttpdnsResult.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Network/HttpdnsNWHTTPClient.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Network/HttpdnsNWHTTPClient.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Network/HttpdnsNWHTTPClient_Internal.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Network/HttpdnsNWReusableConnection.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Network/HttpdnsNWReusableConnection.m (100%) rename EdgeHttpDNS/sdk/ios/AlicloudHttpDNS/AlicloudHttpDNS.h => HttpDNSSDK/sdk/ios/NewHttpDNS/NewHttpDNS.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Persistent/HttpdnsDB.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Persistent/HttpdnsDB.m (99%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Persistent/HttpdnsPersistenceUtils.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Persistent/HttpdnsPersistenceUtils.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Scheduler/HttpdnsScheduleCenter.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Scheduler/HttpdnsScheduleCenter.m (93%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Scheduler/HttpdnsScheduleExecutor.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Scheduler/HttpdnsScheduleExecutor.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Utils/HttpDnsLocker.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Utils/HttpDnsLocker.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Utils/HttpdnsHostObjectInMemoryCache.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Utils/HttpdnsHostObjectInMemoryCache.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Utils/HttpdnsIPQualityDetector.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Utils/HttpdnsIPQualityDetector.m (97%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Utils/HttpdnsReachability.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Utils/HttpdnsReachability.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Utils/HttpdnsUtil.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNS => HttpDNSSDK/sdk/ios/NewHttpDNS}/Utils/HttpdnsUtil.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTestDemo => HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo}/AppDelegate.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTestDemo => HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo}/AppDelegate.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTestDemo => HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo}/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTestDemo => HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo}/Assets.xcassets/Contents.json (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTestDemo => HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo}/Base.lproj/LaunchScreen.storyboard (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTestDemo => HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo}/DemoConfig.plist (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTestDemo => HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo}/DemoConfigLoader.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTestDemo => HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo}/DemoConfigLoader.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTestDemo => HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo}/DemoHttpdnsScenario.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTestDemo => HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo}/DemoHttpdnsScenario.m (94%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTestDemo => HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo}/DemoLogViewController.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTestDemo => HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo}/DemoLogViewController.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTestDemo => HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo}/DemoResolveModel.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTestDemo => HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo}/DemoResolveModel.m (92%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTestDemo => HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo}/DemoViewController.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTestDemo => HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo}/DemoViewController.m (98%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTestDemo => HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo}/Info.plist (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTestDemo => HttpDNSSDK/sdk/ios/NewHttpDNSTestDemo}/main.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/DB/DBTest.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/HighLevelTest/CacheKeyFunctionTest.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/HighLevelTest/CustomTTLTest.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/HighLevelTest/EnableReuseExpiredIpTest.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/HighLevelTest/HttpdnsHostObjectTest.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/HighLevelTest/ManuallyCleanCacheTest.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/HighLevelTest/MultithreadCorrectnessTest.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/HighLevelTest/PresetCacheAndRetrieveTest.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/HighLevelTest/ResolvingEffectiveHostTest.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/HighLevelTest/ScheduleCenterV4Test.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/HighLevelTest/ScheduleCenterV6Test.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/HighLevelTest/SdnsScenarioTest.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/IPDetector/IpDetectorTest.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/Info.plist (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/Network/HttpdnsNWHTTPClientTestBase.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/Network/HttpdnsNWHTTPClientTestBase.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/Network/HttpdnsNWHTTPClientTestHelper.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/Network/HttpdnsNWHTTPClientTestHelper.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/Network/HttpdnsNWHTTPClientTests.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/Network/HttpdnsNWHTTPClient_BasicIntegrationTests.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/Network/HttpdnsNWHTTPClient_ConcurrencyTests.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/Network/HttpdnsNWHTTPClient_EdgeCasesAndTimeoutTests.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/Network/HttpdnsNWHTTPClient_PoolManagementTests.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/Network/HttpdnsNWHTTPClient_StateMachineTests.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/Network/MOCK_SERVER.md (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/Network/README.md (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/Network/STATE_MACHINE_ANALYSIS.md (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/Network/TIMEOUT_ANALYSIS.md (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/Network/mock_server.py (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/OutdatedTest/NetworkManager.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/OutdatedTest/NetworkManager.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/Testbase/TestBase.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/Testbase/TestBase.m (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/Testbase/XCTestCase+AsyncTesting.h (100%) rename {EdgeHttpDNS/sdk/ios/AlicloudHttpDNSTests => HttpDNSSDK/sdk/ios/NewHttpDNSTests}/Testbase/XCTestCase+AsyncTesting.m (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/ios/Podfile (75%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/ios/README.md (100%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/ios/build_xc_framework.sh (93%) rename {EdgeHttpDNS => HttpDNSSDK}/sdk/ios/resource/PrivacyInfo.xcprivacy (100%) create mode 100644 HttpDNSSDK/spec/error-codes.md create mode 100644 HttpDNSSDK/spec/resolve-api.md create mode 100644 HttpDNSSDK/tools/release/httpdns/README.md create mode 100644 HttpDNSSDK/tools/release/httpdns/release_android.ps1 create mode 100644 HttpDNSSDK/tools/release/httpdns/release_flutter.ps1 create mode 100644 HttpDNSSDK/tools/release/httpdns/release_ios.sh diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 065620b..3fbf7a9 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -22,7 +22,14 @@ "Bash(where protoc:*)", "Bash(/c/Users/robin/AppData/Local/Temp/protoc-install/bin/protoc.exe:*)", "Bash(protoc-gen-go:*)", - "Bash(cp:*)" + "Bash(cp:*)", + "Bash(ls:*)", + "Bash(choco list --local-only)", + "Bash(scoop list)", + "Bash(go install:*)", + "Bash(go env:*)", + "Bash(xargs:*)", + "Bash(sed:*)" ] } } diff --git a/.gitignore b/.gitignore index eb9f4ce..05fc878 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,9 @@ deploy/fluent-bit/logs.db deploy/fluent-bit/logs.db-shm deploy/fluent-bit/logs.db-wal deploy/fluent-bit/storage/ +pkg/ +.claude + +# Local large build artifacts +EdgeAdmin/edge-admin.exe +EdgeAPI/deploy/edge-node-linux-amd64-v1.4.9.zip diff --git a/EdgeAPI/build/sql.sh b/EdgeAPI/build/sql.sh index 347659a..76fd238 100644 --- a/EdgeAPI/build/sql.sh +++ b/EdgeAPI/build/sql.sh @@ -3,5 +3,10 @@ # generate 'internal/setup/sql.json' file CWD="$(dirname "$0")" +SQL_JSON="${CWD}/../internal/setup/sql.json" -go run "${CWD}"/../cmd/sql-dump/main.go -dir="${CWD}" \ No newline at end of file +if [ -f "$SQL_JSON" ]; then + echo "sql.json already exists, skipping sql-dump (delete it manually to regenerate)" +else + go run "${CWD}"/../cmd/sql-dump/main.go -dir="${CWD}" +fi \ No newline at end of file diff --git a/EdgeAPI/cmd/sql-dump/main.go b/EdgeAPI/cmd/sql-dump/main.go index d201b43..0b819d0 100644 --- a/EdgeAPI/cmd/sql-dump/main.go +++ b/EdgeAPI/cmd/sql-dump/main.go @@ -12,6 +12,8 @@ import ( ) func main() { + Tea.Env = "prod" + db, err := dbs.Default() if err != nil { fmt.Println("[ERROR]" + err.Error()) diff --git a/EdgeAPI/internal/const/const.go b/EdgeAPI/internal/const/const.go index 14d8d93..c3b3d66 100644 --- a/EdgeAPI/internal/const/const.go +++ b/EdgeAPI/internal/const/const.go @@ -1,7 +1,7 @@ package teaconst const ( - Version = "1.4.8" //1.3.9 + Version = "1.4.9" //1.3.9 ProductName = "Edge API" ProcessName = "edge-api" @@ -17,6 +17,6 @@ const ( // 其他节点版本号,用来检测是否有需要升级的节点 - NodeVersion = "1.4.8" //1.3.8.2 + NodeVersion = "1.4.9" //1.3.8.2 ) diff --git a/EdgeAPI/internal/const/const_plus.go b/EdgeAPI/internal/const/const_plus.go index 56e5324..5b679bd 100644 --- a/EdgeAPI/internal/const/const_plus.go +++ b/EdgeAPI/internal/const/const_plus.go @@ -4,8 +4,8 @@ package teaconst const ( - DNSNodeVersion = "1.4.8" //1.3.8.2 - UserNodeVersion = "1.4.8" //1.3.8.2 + DNSNodeVersion = "1.4.9" //1.3.8.2 + UserNodeVersion = "1.4.9" //1.3.8.2 ReportNodeVersion = "0.1.5" DefaultMaxNodes int32 = 50 diff --git a/EdgeAPI/internal/db/models/httpdns_cluster_dao.go b/EdgeAPI/internal/db/models/httpdns_cluster_dao.go index a184b36..9a5af68 100644 --- a/EdgeAPI/internal/db/models/httpdns_cluster_dao.go +++ b/EdgeAPI/internal/db/models/httpdns_cluster_dao.go @@ -34,7 +34,7 @@ func init() { }) } -func (this *HTTPDNSClusterDAO) CreateCluster(tx *dbs.Tx, name string, serviceDomain string, defaultTTL int32, fallbackTimeoutMs int32, installDir string, tlsPolicyJSON []byte, isOn bool, isDefault bool, autoRemoteStart bool, accessLogIsOn bool) (int64, error) { +func (this *HTTPDNSClusterDAO) CreateCluster(tx *dbs.Tx, name string, serviceDomain string, defaultTTL int32, fallbackTimeoutMs int32, installDir string, tlsPolicyJSON []byte, isOn bool, isDefault bool, autoRemoteStart bool, accessLogIsOn bool, timeZone string) (int64, error) { if isDefault { err := this.Query(tx). State(HTTPDNSClusterStateEnabled). @@ -55,6 +55,7 @@ func (this *HTTPDNSClusterDAO) CreateCluster(tx *dbs.Tx, name string, serviceDom op.IsDefault = isDefault op.AutoRemoteStart = autoRemoteStart op.AccessLogIsOn = accessLogIsOn + op.TimeZone = timeZone op.CreatedAt = time.Now().Unix() op.UpdatedAt = time.Now().Unix() op.State = HTTPDNSClusterStateEnabled @@ -68,7 +69,7 @@ func (this *HTTPDNSClusterDAO) CreateCluster(tx *dbs.Tx, name string, serviceDom return types.Int64(op.Id), nil } -func (this *HTTPDNSClusterDAO) UpdateCluster(tx *dbs.Tx, clusterId int64, name string, serviceDomain string, defaultTTL int32, fallbackTimeoutMs int32, installDir string, tlsPolicyJSON []byte, isOn bool, isDefault bool, autoRemoteStart bool, accessLogIsOn bool) error { +func (this *HTTPDNSClusterDAO) UpdateCluster(tx *dbs.Tx, clusterId int64, name string, serviceDomain string, defaultTTL int32, fallbackTimeoutMs int32, installDir string, tlsPolicyJSON []byte, isOn bool, isDefault bool, autoRemoteStart bool, accessLogIsOn bool, timeZone string) error { if isDefault { err := this.Query(tx). State(HTTPDNSClusterStateEnabled). @@ -91,6 +92,7 @@ func (this *HTTPDNSClusterDAO) UpdateCluster(tx *dbs.Tx, clusterId int64, name s op.IsDefault = isDefault op.AutoRemoteStart = autoRemoteStart op.AccessLogIsOn = accessLogIsOn + op.TimeZone = timeZone op.UpdatedAt = time.Now().Unix() if len(tlsPolicyJSON) > 0 { op.TLSPolicy = tlsPolicyJSON diff --git a/EdgeAPI/internal/db/models/httpdns_cluster_model.go b/EdgeAPI/internal/db/models/httpdns_cluster_model.go index 1fc8517..917c562 100644 --- a/EdgeAPI/internal/db/models/httpdns_cluster_model.go +++ b/EdgeAPI/internal/db/models/httpdns_cluster_model.go @@ -15,6 +15,7 @@ type HTTPDNSCluster struct { TLSPolicy dbs.JSON `field:"tlsPolicy"` // TLS策略 AutoRemoteStart bool `field:"autoRemoteStart"` // 自动远程启动 AccessLogIsOn bool `field:"accessLogIsOn"` // 访问日志是否开启 + TimeZone string `field:"timeZone"` // 时区 CreatedAt uint64 `field:"createdAt"` // 创建时间 UpdatedAt uint64 `field:"updatedAt"` // 修改时间 State uint8 `field:"state"` // 记录状态 @@ -32,6 +33,7 @@ type HTTPDNSClusterOperator struct { TLSPolicy any // TLS策略 AutoRemoteStart any // 自动远程启动 AccessLogIsOn any // 访问日志是否开启 + TimeZone any // 时区 CreatedAt any // 创建时间 UpdatedAt any // 修改时间 State any // 记录状态 diff --git a/EdgeAPI/internal/db/models/httpdns_node_dao.go b/EdgeAPI/internal/db/models/httpdns_node_dao.go index a5bf0d3..140cc03 100644 --- a/EdgeAPI/internal/db/models/httpdns_node_dao.go +++ b/EdgeAPI/internal/db/models/httpdns_node_dao.go @@ -2,6 +2,7 @@ package models import ( "encoding/json" + "github.com/TeaOSLab/EdgeAPI/internal/utils" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" _ "github.com/go-sql-driver/mysql" "github.com/iwind/TeaGo/Tea" @@ -12,8 +13,8 @@ import ( ) const ( - HTTPDNSNodeStateEnabled = 1 // 已启用 - HTTPDNSNodeStateDisabled = 0 // 已禁用 + HTTPDNSNodeStateEnabled = 1 + HTTPDNSNodeStateDisabled = 0 ) type HTTPDNSNodeDAO dbs.DAO @@ -37,7 +38,7 @@ func init() { }) } -// FindEnabledNodeIdWithUniqueId 根据唯一ID获取启用中的HTTPDNS节点ID +// FindEnabledNodeIdWithUniqueId 鏍规嵁鍞竴ID鑾峰彇鍚敤涓殑HTTPDNS鑺傜偣ID func (this *HTTPDNSNodeDAO) FindEnabledNodeIdWithUniqueId(tx *dbs.Tx, uniqueId string) (int64, error) { return this.Query(tx). Attr("uniqueId", uniqueId). @@ -46,7 +47,7 @@ func (this *HTTPDNSNodeDAO) FindEnabledNodeIdWithUniqueId(tx *dbs.Tx, uniqueId s FindInt64Col(0) } -// CreateNode 创建节点 +// CreateNode 鍒涘缓鑺傜偣 func (this *HTTPDNSNodeDAO) CreateNode(tx *dbs.Tx, clusterId int64, name string, installDir string, isOn bool) (int64, error) { uniqueId := rands.HexString(32) secret := rands.String(32) @@ -75,7 +76,7 @@ func (this *HTTPDNSNodeDAO) CreateNode(tx *dbs.Tx, clusterId int64, name string, return types.Int64(op.Id), nil } -// UpdateNode 更新节点 +// UpdateNode 鏇存柊鑺傜偣 func (this *HTTPDNSNodeDAO) UpdateNode(tx *dbs.Tx, nodeId int64, name string, installDir string, isOn bool) error { var op = NewHTTPDNSNodeOperator() op.Id = nodeId @@ -86,7 +87,7 @@ func (this *HTTPDNSNodeDAO) UpdateNode(tx *dbs.Tx, nodeId int64, name string, in return this.Save(tx, op) } -// DisableNode 禁用节点 +// DisableNode 绂佺敤鑺傜偣 func (this *HTTPDNSNodeDAO) DisableNode(tx *dbs.Tx, nodeId int64) error { node, err := this.FindEnabledNode(tx, nodeId) if err != nil { @@ -112,7 +113,7 @@ func (this *HTTPDNSNodeDAO) DisableNode(tx *dbs.Tx, nodeId int64) error { return err } -// FindEnabledNode 查找启用节点 +// FindEnabledNode 鏌ユ壘鍚敤鑺傜偣 func (this *HTTPDNSNodeDAO) FindEnabledNode(tx *dbs.Tx, nodeId int64) (*HTTPDNSNode, error) { one, err := this.Query(tx). Pk(nodeId). @@ -124,7 +125,7 @@ func (this *HTTPDNSNodeDAO) FindEnabledNode(tx *dbs.Tx, nodeId int64) (*HTTPDNSN return one.(*HTTPDNSNode), nil } -// FindNodeClusterId 查询节点所属集群ID +// FindNodeClusterId 鏌ヨ鑺傜偣鎵€灞為泦缇D func (this *HTTPDNSNodeDAO) FindNodeClusterId(tx *dbs.Tx, nodeId int64) (int64, error) { return this.Query(tx). Pk(nodeId). @@ -133,7 +134,7 @@ func (this *HTTPDNSNodeDAO) FindNodeClusterId(tx *dbs.Tx, nodeId int64) (int64, FindInt64Col(0) } -// ListEnabledNodes 列出节点 +// ListEnabledNodes 鍒楀嚭鑺傜偣 func (this *HTTPDNSNodeDAO) ListEnabledNodes(tx *dbs.Tx, clusterId int64) (result []*HTTPDNSNode, err error) { query := this.Query(tx). State(HTTPDNSNodeStateEnabled). @@ -145,6 +146,20 @@ func (this *HTTPDNSNodeDAO) ListEnabledNodes(tx *dbs.Tx, clusterId int64) (resul return } +// FindAllInactiveNodesWithClusterId 取得一个集群离线的HTTPDNS节点 +func (this *HTTPDNSNodeDAO) FindAllInactiveNodesWithClusterId(tx *dbs.Tx, clusterId int64) (result []*HTTPDNSNode, err error) { + _, err = this.Query(tx). + State(HTTPDNSNodeStateEnabled). + Attr("clusterId", clusterId). + Attr("isOn", true). // 只监控启用的节点 + Attr("isInstalled", true). // 只监控已经安装的节点 + Attr("isActive", false). // 当前处于离线状态 + Result("id", "name"). + Slice(&result). + FindAll() + return +} + // UpdateNodeStatus 更新节点状态 func (this *HTTPDNSNodeDAO) UpdateNodeStatus(tx *dbs.Tx, nodeId int64, isUp bool, isInstalled bool, isActive bool, statusJSON []byte, installStatusJSON []byte) error { var op = NewHTTPDNSNodeOperator() @@ -261,7 +276,46 @@ func (this *HTTPDNSNodeDAO) FindNodeInstallStatus(tx *dbs.Tx, nodeId int64) (*No return installStatus, nil } -// UpdateNodeIsInstalled 更新节点安装状态位 +// CountAllLowerVersionNodesWithClusterId 璁$畻鍗曚釜闆嗙兢涓墍鏈変綆浜庢煇涓増鏈殑鑺傜偣鏁伴噺 +func (this *HTTPDNSNodeDAO) CountAllLowerVersionNodesWithClusterId(tx *dbs.Tx, clusterId int64, os string, arch string, version string) (int64, error) { + return this.Query(tx). + State(HTTPDNSNodeStateEnabled). + Attr("clusterId", clusterId). + Attr("isOn", true). + Attr("isUp", true). + Attr("isActive", true). + Where("status IS NOT NULL"). + Where("JSON_EXTRACT(status, '$.os')=:os"). + Where("JSON_EXTRACT(status, '$.arch')=:arch"). + Where("(JSON_EXTRACT(status, '$.buildVersionCode') IS NULL OR JSON_EXTRACT(status, '$.buildVersionCode')<:version)"). + Param("os", os). + Param("arch", arch). + Param("version", utils.VersionToLong(version)). + Count() +} + +// FindAllLowerVersionNodesWithClusterId 鏌ユ壘鍗曚釜闆嗙兢涓墍鏈変綆浜庢煇涓増鏈殑鑺傜偣 +func (this *HTTPDNSNodeDAO) FindAllLowerVersionNodesWithClusterId(tx *dbs.Tx, clusterId int64, os string, arch string, version string) (result []*HTTPDNSNode, err error) { + _, err = this.Query(tx). + State(HTTPDNSNodeStateEnabled). + Attr("clusterId", clusterId). + Attr("isOn", true). + Attr("isUp", true). + Attr("isActive", true). + Where("status IS NOT NULL"). + Where("JSON_EXTRACT(status, '$.os')=:os"). + Where("JSON_EXTRACT(status, '$.arch')=:arch"). + Where("(JSON_EXTRACT(status, '$.buildVersionCode') IS NULL OR JSON_EXTRACT(status, '$.buildVersionCode')<:version)"). + Param("os", os). + Param("arch", arch). + Param("version", utils.VersionToLong(version)). + DescPk(). + Slice(&result). + FindAll() + return +} + +// UpdateNodeIsInstalled 鏇存柊鑺傜偣瀹夎鐘舵€佷綅 func (this *HTTPDNSNodeDAO) UpdateNodeIsInstalled(tx *dbs.Tx, nodeId int64, isInstalled bool) error { _, err := this.Query(tx). Pk(nodeId). diff --git a/EdgeAPI/internal/db/models/node_dao.go b/EdgeAPI/internal/db/models/node_dao.go index e032c6b..8f58027 100644 --- a/EdgeAPI/internal/db/models/node_dao.go +++ b/EdgeAPI/internal/db/models/node_dao.go @@ -1521,6 +1521,8 @@ func (this *NodeDAO) CountAllLowerVersionNodesWithClusterId(tx *dbs.Tx, clusterI return this.Query(tx). State(NodeStateEnabled). Attr("isOn", true). + Attr("isUp", true). + Attr("isActive", true). Attr("clusterId", clusterId). Where("status IS NOT NULL"). Where("JSON_EXTRACT(status, '$.os')=:os"). @@ -1536,6 +1538,9 @@ func (this *NodeDAO) CountAllLowerVersionNodesWithClusterId(tx *dbs.Tx, clusterI func (this *NodeDAO) FindAllLowerVersionNodesWithClusterId(tx *dbs.Tx, clusterId int64, os string, arch string, version string) (result []*Node, err error) { _, err = this.Query(tx). State(NodeStateEnabled). + Attr("isOn", true). + Attr("isUp", true). + Attr("isActive", true). Attr("clusterId", clusterId). Where("status IS NOT NULL"). Where("JSON_EXTRACT(status, '$.os')=:os"). diff --git a/EdgeAPI/internal/db/models/ns_node_dao.go b/EdgeAPI/internal/db/models/ns_node_dao.go index 278b8ad..bea3054 100644 --- a/EdgeAPI/internal/db/models/ns_node_dao.go +++ b/EdgeAPI/internal/db/models/ns_node_dao.go @@ -94,6 +94,8 @@ func (this *NSNodeDAO) CountAllLowerVersionNodesWithClusterId(tx *dbs.Tx, cluste State(NSNodeStateEnabled). Attr("clusterId", clusterId). Attr("isOn", true). + Attr("isUp", true). + Attr("isActive", true). Where("status IS NOT NULL"). Where("JSON_EXTRACT(status, '$.os')=:os"). Where("JSON_EXTRACT(status, '$.arch')=:arch"). @@ -104,6 +106,27 @@ func (this *NSNodeDAO) CountAllLowerVersionNodesWithClusterId(tx *dbs.Tx, cluste Count() } +// FindAllLowerVersionNodesWithClusterId 查找单个集群中所有低于某个版本的节点 +func (this *NSNodeDAO) FindAllLowerVersionNodesWithClusterId(tx *dbs.Tx, clusterId int64, os string, arch string, version string) (result []*NSNode, err error) { + _, err = this.Query(tx). + State(NSNodeStateEnabled). + Attr("clusterId", clusterId). + Attr("isOn", true). + Attr("isUp", true). + Attr("isActive", true). + Where("status IS NOT NULL"). + Where("JSON_EXTRACT(status, '$.os')=:os"). + Where("JSON_EXTRACT(status, '$.arch')=:arch"). + Where("(JSON_EXTRACT(status, '$.buildVersionCode') IS NULL OR JSON_EXTRACT(status, '$.buildVersionCode')<:version)"). + Param("os", os). + Param("arch", arch). + Param("version", utils.VersionToLong(version)). + DescPk(). + Slice(&result). + FindAll() + return +} + // FindEnabledNodeIdWithUniqueId 根据唯一ID获取节点ID func (this *NSNodeDAO) FindEnabledNodeIdWithUniqueId(tx *dbs.Tx, uniqueId string) (int64, error) { return this.Query(tx). diff --git a/EdgeAPI/internal/db/models/ns_node_dao_plus.go b/EdgeAPI/internal/db/models/ns_node_dao_plus.go index d74fd32..4a34786 100644 --- a/EdgeAPI/internal/db/models/ns_node_dao_plus.go +++ b/EdgeAPI/internal/db/models/ns_node_dao_plus.go @@ -209,6 +209,8 @@ func (this *NSNodeDAO) CountAllLowerVersionNodesWithClusterId(tx *dbs.Tx, cluste return this.Query(tx). State(NSNodeStateEnabled). Attr("isOn", true). + Attr("isUp", true). + Attr("isActive", true). Attr("clusterId", clusterId). Where("status IS NOT NULL"). Where("JSON_EXTRACT(status, '$.os')=:os"). @@ -412,6 +414,27 @@ func (this *NSNodeDAO) CountAllLowerVersionNodes(tx *dbs.Tx, version string) (in Count() } +// FindAllLowerVersionNodesWithClusterId 查找单个集群中所有低于某个版本的节点 +func (this *NSNodeDAO) FindAllLowerVersionNodesWithClusterId(tx *dbs.Tx, clusterId int64, os string, arch string, version string) (result []*NSNode, err error) { + _, err = this.Query(tx). + State(NSNodeStateEnabled). + Attr("clusterId", clusterId). + Attr("isOn", true). + Attr("isUp", true). + Attr("isActive", true). + Where("status IS NOT NULL"). + Where("JSON_EXTRACT(status, '$.os')=:os"). + Where("JSON_EXTRACT(status, '$.arch')=:arch"). + Where("(JSON_EXTRACT(status, '$.buildVersionCode') IS NULL OR JSON_EXTRACT(status, '$.buildVersionCode')<:version)"). + Param("os", os). + Param("arch", arch). + Param("version", utils.VersionToLong(version)). + DescPk(). + Slice(&result). + FindAll() + return +} + // ComposeNodeConfig 组合节点配置 func (this *NSNodeDAO) ComposeNodeConfig(tx *dbs.Tx, nodeId int64) (*dnsconfigs.NSNodeConfig, error) { if nodeId <= 0 { diff --git a/EdgeAPI/internal/installers/queue_httpdns_node.go b/EdgeAPI/internal/installers/queue_httpdns_node.go index 197eb0f..ba64cae 100644 --- a/EdgeAPI/internal/installers/queue_httpdns_node.go +++ b/EdgeAPI/internal/installers/queue_httpdns_node.go @@ -187,6 +187,100 @@ func (q *HTTPDNSNodeQueue) InstallNode(nodeId int64, installStatus *models.NodeI return installer.Install(installDir, params, installStatus) } +// StartNode 启动HTTPDNS节点 +func (q *HTTPDNSNodeQueue) StartNode(nodeId int64) error { + node, err := models.SharedHTTPDNSNodeDAO.FindEnabledNode(nil, nodeId) + if err != nil { + return err + } + if node == nil { + return errors.New("can not find node, ID '" + numberutils.FormatInt64(nodeId) + "'") + } + + // 登录信息 + login, err := models.SharedNodeLoginDAO.FindEnabledNodeLoginWithNodeId(nil, nodeconfigs.NodeRoleHTTPDNS, nodeId) + if err != nil { + return err + } + if login == nil { + return newGrantError("can not find node login information") + } + loginParams, err := login.DecodeSSHParams() + if err != nil { + return newGrantError(err.Error()) + } + if len(strings.TrimSpace(loginParams.Host)) == 0 { + return newGrantError("ssh host should not be empty") + } + if loginParams.Port <= 0 { + loginParams.Port = 22 + } + if loginParams.GrantId <= 0 { + return newGrantError("can not find node grant") + } + + grant, err := models.SharedNodeGrantDAO.FindEnabledNodeGrant(nil, loginParams.GrantId) + if err != nil { + return err + } + if grant == nil { + return newGrantError("can not find user grant with id '" + numberutils.FormatInt64(loginParams.GrantId) + "'") + } + + installer := &HTTPDNSNodeInstaller{} + err = installer.Login(&Credentials{ + Host: strings.TrimSpace(loginParams.Host), + Port: loginParams.Port, + Username: grant.Username, + Password: grant.Password, + PrivateKey: grant.PrivateKey, + Passphrase: grant.Passphrase, + Method: grant.Method, + Sudo: grant.Su == 1, + }) + if err != nil { + return err + } + defer func() { + _ = installer.Close() + }() + + installDir := strings.TrimSpace(node.InstallDir) + if len(installDir) == 0 { + cluster, err := models.SharedHTTPDNSClusterDAO.FindEnabledCluster(nil, int64(node.ClusterId)) + if err != nil { + return err + } + if cluster == nil { + return errors.New("can not find cluster, ID '" + numberutils.FormatInt64(int64(node.ClusterId)) + "'") + } + installDir = strings.TrimSpace(cluster.InstallDir) + if len(installDir) == 0 { + installDir = installer.client.UserHome() + "/edge-httpdns" + } + } + _, appDir := resolveHTTPDNSInstallPaths(installDir) + exeFile := appDir + "/bin/edge-httpdns" + + _, err = installer.client.Stat(exeFile) + if err != nil { + return errors.New("httpdns node is not installed correctly, can not find executable file: " + exeFile) + } + + // 先尝试 systemd 拉起 + _, _, _ = installer.client.Exec("/usr/bin/systemctl start edge-httpdns") + + _, stderr, err := installer.client.Exec(exeFile + " start") + if err != nil { + return fmt.Errorf("start failed: %w", err) + } + if len(strings.TrimSpace(stderr)) > 0 { + return errors.New("start failed: " + strings.TrimSpace(stderr)) + } + + return nil +} + func (q *HTTPDNSNodeQueue) resolveClusterTLSCertPair(cluster *models.HTTPDNSCluster) ([]byte, []byte, error) { if cluster == nil { return nil, nil, errors.New("cluster not found") diff --git a/EdgeAPI/internal/installers/upgrade_queue.go b/EdgeAPI/internal/installers/upgrade_queue.go new file mode 100644 index 0000000..ca08654 --- /dev/null +++ b/EdgeAPI/internal/installers/upgrade_queue.go @@ -0,0 +1,25 @@ +package installers + +// UpgradeQueue 升级队列,控制并发数 +type UpgradeQueue struct { + sem chan struct{} +} + +// SharedUpgradeQueue 全局升级队列,最多5个并发 +var SharedUpgradeQueue = NewUpgradeQueue(5) + +// NewUpgradeQueue 创建升级队列 +func NewUpgradeQueue(maxConcurrent int) *UpgradeQueue { + return &UpgradeQueue{ + sem: make(chan struct{}, maxConcurrent), + } +} + +// SubmitNodeUpgrade 提交节点升级任务(异步执行,超过并发限制自动排队) +func (q *UpgradeQueue) SubmitNodeUpgrade(nodeId int64, upgradeFunc func(int64) error) { + go func() { + q.sem <- struct{}{} + defer func() { <-q.sem }() + _ = upgradeFunc(nodeId) + }() +} diff --git a/EdgeAPI/internal/rpc/services/httpdns/converters.go b/EdgeAPI/internal/rpc/services/httpdns/converters.go index d1e82fc..d8c70c9 100644 --- a/EdgeAPI/internal/rpc/services/httpdns/converters.go +++ b/EdgeAPI/internal/rpc/services/httpdns/converters.go @@ -2,9 +2,12 @@ package httpdns import ( "encoding/json" + "log" "github.com/TeaOSLab/EdgeAPI/internal/db/models" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" + "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs" + "github.com/iwind/TeaGo/dbs" ) func toPBCluster(cluster *models.HTTPDNSCluster) *pb.HTTPDNSCluster { @@ -25,9 +28,94 @@ func toPBCluster(cluster *models.HTTPDNSCluster) *pb.HTTPDNSCluster { UpdatedAt: int64(cluster.UpdatedAt), AutoRemoteStart: cluster.AutoRemoteStart, AccessLogIsOn: cluster.AccessLogIsOn, + TimeZone: cluster.TimeZone, } } +// toPBClusterWithResolvedCerts 转换集群并解析证书引用为实际 PEM 数据 +// 供节点调用的 RPC 使用,确保节点能拿到完整的证书内容 +func toPBClusterWithResolvedCerts(tx *dbs.Tx, cluster *models.HTTPDNSCluster) *pb.HTTPDNSCluster { + pbCluster := toPBCluster(cluster) + if pbCluster == nil { + return nil + } + resolved := resolveTLSPolicyCerts(tx, cluster.TLSPolicy) + if resolved != nil { + pbCluster.TlsPolicyJSON = resolved + } + return pbCluster +} + +// resolveTLSPolicyCerts 将 tlsPolicyJSON 中的 certRefs 解析为带实际 PEM 数据的 certs +func resolveTLSPolicyCerts(tx *dbs.Tx, tlsPolicyJSON []byte) []byte { + if len(tlsPolicyJSON) == 0 { + return nil + } + + // 解析外层结构: {"listen": [...], "sslPolicy": {...}} + var tlsConfig map[string]json.RawMessage + if err := json.Unmarshal(tlsPolicyJSON, &tlsConfig); err != nil { + return nil + } + + sslPolicyData, ok := tlsConfig["sslPolicy"] + if !ok || len(sslPolicyData) == 0 { + return nil + } + + var sslPolicy sslconfigs.SSLPolicy + if err := json.Unmarshal(sslPolicyData, &sslPolicy); err != nil { + return nil + } + + // 检查 certs 是否已经有实际数据 + for _, cert := range sslPolicy.Certs { + if cert != nil && len(cert.CertData) > 128 && len(cert.KeyData) > 128 { + return nil // 已有完整 PEM 数据,无需处理 + } + } + + // 从 certRefs 解析实际证书数据 + if len(sslPolicy.CertRefs) == 0 { + return nil + } + + var resolvedCerts []*sslconfigs.SSLCertConfig + for _, ref := range sslPolicy.CertRefs { + if ref == nil || ref.CertId <= 0 { + continue + } + certConfig, err := models.SharedSSLCertDAO.ComposeCertConfig(tx, ref.CertId, false, nil, nil) + if err != nil { + log.Println("[HTTPDNS]resolve cert", ref.CertId, "failed:", err.Error()) + continue + } + if certConfig == nil || len(certConfig.CertData) == 0 || len(certConfig.KeyData) == 0 { + continue + } + resolvedCerts = append(resolvedCerts, certConfig) + } + + if len(resolvedCerts) == 0 { + return nil + } + + // 把解析后的证书写回 sslPolicy.Certs + sslPolicy.Certs = resolvedCerts + + newPolicyData, err := json.Marshal(&sslPolicy) + if err != nil { + return nil + } + tlsConfig["sslPolicy"] = newPolicyData + + result, err := json.Marshal(tlsConfig) + if err != nil { + return nil + } + return result +} + func toPBNode(node *models.HTTPDNSNode) *pb.HTTPDNSNode { if node == nil { return nil diff --git a/EdgeAPI/internal/rpc/services/httpdns/service_httpdns_app.go b/EdgeAPI/internal/rpc/services/httpdns/service_httpdns_app.go index 79a9b70..6b64a1f 100644 --- a/EdgeAPI/internal/rpc/services/httpdns/service_httpdns_app.go +++ b/EdgeAPI/internal/rpc/services/httpdns/service_httpdns_app.go @@ -67,10 +67,10 @@ func (this *HTTPDNSAppService) CreateHTTPDNSApp(ctx context.Context, req *pb.Cre return errors.New("appId already exists") } - // 使用 clusterIdsJSON;若为空则优先从用户关联集群获取,再 fallback 到全局默认 + // 使用 clusterIdsJSON;若为空则从用户关联集群获取 clusterIdsJSON := req.ClusterIdsJSON if len(clusterIdsJSON) == 0 || string(clusterIdsJSON) == "[]" || string(clusterIdsJSON) == "null" { - // 优先读取用户关联的 HTTPDNS 集群 + // 读取用户关联的 HTTPDNS 集群 if req.UserId > 0 { user, userErr := models.SharedUserDAO.FindEnabledUser(tx, req.UserId, nil) if userErr != nil { @@ -83,16 +83,11 @@ func (this *HTTPDNSAppService) CreateHTTPDNSApp(ctx context.Context, req *pb.Cre } } } - // fallback 到全局默认 - if len(clusterIdsJSON) == 0 || string(clusterIdsJSON) == "[]" || string(clusterIdsJSON) == "null" { - defaultClusterIds, defaultErr := readHTTPDNSDefaultClusterIdList(tx) - if defaultErr != nil { - return defaultErr - } - if len(defaultClusterIds) > 0 { - clusterIdsJSON, _ = json.Marshal(defaultClusterIds) - } - } + } + + // 如果仍然没有集群,则不允许创建 + if len(clusterIdsJSON) == 0 || string(clusterIdsJSON) == "[]" || string(clusterIdsJSON) == "null" { + return errors.New("用户尚未分配 HTTPDNS 集群,无法创建应用") } appDbId, err = models.SharedHTTPDNSAppDAO.CreateApp(tx, appName, appId, clusterIdsJSON, req.IsOn, req.UserId) @@ -143,14 +138,6 @@ func readHTTPDNSDefaultClusterIdList(tx *dbs.Tx) ([]int64, error) { } } - // fallback:默认主集群 - primaryClusterId, err := models.SharedHTTPDNSClusterDAO.FindDefaultPrimaryClusterId(tx) - if err != nil { - return nil, err - } - if primaryClusterId > 0 { - return []int64{primaryClusterId}, nil - } return nil, nil } diff --git a/EdgeAPI/internal/rpc/services/httpdns/service_httpdns_cluster.go b/EdgeAPI/internal/rpc/services/httpdns/service_httpdns_cluster.go index dcab940..94da75f 100644 --- a/EdgeAPI/internal/rpc/services/httpdns/service_httpdns_cluster.go +++ b/EdgeAPI/internal/rpc/services/httpdns/service_httpdns_cluster.go @@ -3,10 +3,14 @@ package httpdns import ( "context" "errors" + "fmt" + "strings" "github.com/TeaOSLab/EdgeAPI/internal/db/models" "github.com/TeaOSLab/EdgeAPI/internal/rpc/services" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/iwind/TeaGo/dbs" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" ) // HTTPDNSClusterService HTTPDNS集群服务 @@ -25,7 +29,7 @@ func (this *HTTPDNSClusterService) CreateHTTPDNSCluster(ctx context.Context, req } var clusterId int64 err = this.RunTx(func(tx *dbs.Tx) error { - clusterId, err = models.SharedHTTPDNSClusterDAO.CreateCluster(tx, req.Name, req.ServiceDomain, req.DefaultTTL, req.FallbackTimeoutMs, req.InstallDir, req.TlsPolicyJSON, req.IsOn, req.IsDefault, req.AutoRemoteStart, req.AccessLogIsOn) + clusterId, err = models.SharedHTTPDNSClusterDAO.CreateCluster(tx, req.Name, req.ServiceDomain, req.DefaultTTL, req.FallbackTimeoutMs, req.InstallDir, req.TlsPolicyJSON, req.IsOn, req.IsDefault, req.AutoRemoteStart, req.AccessLogIsOn, req.TimeZone) if err != nil { return err } @@ -42,13 +46,41 @@ func (this *HTTPDNSClusterService) UpdateHTTPDNSCluster(ctx context.Context, req if err != nil { return nil, err } + + // Compatibility fallback: + // If protobuf schemas between edge-admin and edge-api are inconsistent, + // these newly-added fields may be lost on the wire. Read gRPC metadata as fallback. + if md, ok := metadata.FromIncomingContext(ctx); ok { + if values := md.Get("x-httpdns-auto-remote-start"); len(values) > 0 { + raw := strings.ToLower(strings.TrimSpace(values[0])) + req.AutoRemoteStart = raw == "1" || raw == "true" || raw == "on" || raw == "yes" || raw == "enabled" + } + if values := md.Get("x-httpdns-access-log-is-on"); len(values) > 0 { + raw := strings.ToLower(strings.TrimSpace(values[0])) + req.AccessLogIsOn = raw == "1" || raw == "true" || raw == "on" || raw == "yes" || raw == "enabled" + } + if values := md.Get("x-httpdns-time-zone"); len(values) > 0 { + raw := strings.TrimSpace(values[0]) + if len(raw) > 0 { + req.TimeZone = raw + } + } + } + err = this.RunTx(func(tx *dbs.Tx) error { - err = models.SharedHTTPDNSClusterDAO.UpdateCluster(tx, req.ClusterId, req.Name, req.ServiceDomain, req.DefaultTTL, req.FallbackTimeoutMs, req.InstallDir, req.TlsPolicyJSON, req.IsOn, req.IsDefault, req.AutoRemoteStart, req.AccessLogIsOn) + // 先读取旧的 TLS 配置,用于判断是否真正发生了变化 + var oldTLSJSON string + oldCluster, findErr := models.SharedHTTPDNSClusterDAO.FindEnabledCluster(tx, req.ClusterId) + if findErr == nil && oldCluster != nil { + oldTLSJSON = string(oldCluster.TLSPolicy) + } + + err = models.SharedHTTPDNSClusterDAO.UpdateCluster(tx, req.ClusterId, req.Name, req.ServiceDomain, req.DefaultTTL, req.FallbackTimeoutMs, req.InstallDir, req.TlsPolicyJSON, req.IsOn, req.IsDefault, req.AutoRemoteStart, req.AccessLogIsOn, req.TimeZone) if err != nil { return err } taskType := models.HTTPDNSNodeTaskTypeConfigChanged - if len(req.TlsPolicyJSON) > 0 { + if len(req.TlsPolicyJSON) > 0 && string(req.TlsPolicyJSON) != oldTLSJSON { taskType = models.HTTPDNSNodeTaskTypeTLSChanged } return notifyHTTPDNSClusterTask(tx, req.ClusterId, taskType) @@ -86,6 +118,13 @@ func (this *HTTPDNSClusterService) FindHTTPDNSCluster(ctx context.Context, req * if err != nil { return nil, err } + if cluster != nil { + _ = grpc.SetHeader(ctx, metadata.Pairs( + "x-httpdns-auto-remote-start", fmt.Sprintf("%t", cluster.AutoRemoteStart), + "x-httpdns-access-log-is-on", fmt.Sprintf("%t", cluster.AccessLogIsOn), + "x-httpdns-time-zone", cluster.TimeZone, + )) + } return &pb.FindHTTPDNSClusterResponse{Cluster: toPBCluster(cluster)}, nil } @@ -107,10 +146,12 @@ func (this *HTTPDNSClusterService) ListHTTPDNSClusters(ctx context.Context, req func (this *HTTPDNSClusterService) FindAllHTTPDNSClusters(ctx context.Context, req *pb.FindAllHTTPDNSClustersRequest) (*pb.FindAllHTTPDNSClustersResponse, error) { _, _, validateErr := this.ValidateAdminAndUser(ctx, true) + isNode := false if validateErr != nil { if _, nodeErr := this.ValidateHTTPDNSNode(ctx); nodeErr != nil { return nil, validateErr } + isNode = true } clusters, err := models.SharedHTTPDNSClusterDAO.FindAllEnabledClusters(this.NullTx()) if err != nil { @@ -118,7 +159,12 @@ func (this *HTTPDNSClusterService) FindAllHTTPDNSClusters(ctx context.Context, r } var pbClusters []*pb.HTTPDNSCluster for _, cluster := range clusters { - pbClusters = append(pbClusters, toPBCluster(cluster)) + if isNode { + // 节点调用时解析证书引用,嵌入实际 PEM 数据 + pbClusters = append(pbClusters, toPBClusterWithResolvedCerts(this.NullTx(), cluster)) + } else { + pbClusters = append(pbClusters, toPBCluster(cluster)) + } } return &pb.FindAllHTTPDNSClustersResponse{Clusters: pbClusters}, nil } diff --git a/EdgeAPI/internal/rpc/services/httpdns/service_httpdns_node.go b/EdgeAPI/internal/rpc/services/httpdns/service_httpdns_node.go index d1ea6a0..a12ce8e 100644 --- a/EdgeAPI/internal/rpc/services/httpdns/service_httpdns_node.go +++ b/EdgeAPI/internal/rpc/services/httpdns/service_httpdns_node.go @@ -11,6 +11,7 @@ import ( "github.com/TeaOSLab/EdgeAPI/internal/goman" "github.com/TeaOSLab/EdgeAPI/internal/installers" "github.com/TeaOSLab/EdgeAPI/internal/rpc/services" + "github.com/TeaOSLab/EdgeAPI/internal/setup" rpcutils "github.com/TeaOSLab/EdgeAPI/internal/rpc/utils" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" @@ -247,6 +248,12 @@ func (this *HTTPDNSNodeService) DownloadHTTPDNSNodeInstallationFile(ctx context. return nil, err } + // 检查自动升级开关 + upgradeConfig, _ := setup.LoadUpgradeConfig() + if upgradeConfig != nil && !upgradeConfig.AutoUpgrade { + return &pb.DownloadHTTPDNSNodeInstallationFileResponse{}, nil + } + var file = installers.SharedDeployManager.FindHTTPDNSNodeFile(req.Os, req.Arch) if file == nil { return &pb.DownloadHTTPDNSNodeInstallationFileResponse{}, nil @@ -274,6 +281,120 @@ func (this *HTTPDNSNodeService) DownloadHTTPDNSNodeInstallationFile(ctx context. }, nil } +// CountAllUpgradeHTTPDNSNodesWithClusterId 计算需要升级的HTTPDNS节点数量 +func (this *HTTPDNSNodeService) CountAllUpgradeHTTPDNSNodesWithClusterId(ctx context.Context, req *pb.CountAllUpgradeHTTPDNSNodesWithClusterIdRequest) (*pb.RPCCountResponse, error) { + _, err := this.ValidateAdmin(ctx) + if err != nil { + return nil, err + } + + var tx = this.NullTx() + deployFiles := installers.SharedDeployManager.LoadHTTPDNSNodeFiles() + total := int64(0) + for _, deployFile := range deployFiles { + count, err := models.SharedHTTPDNSNodeDAO.CountAllLowerVersionNodesWithClusterId(tx, req.ClusterId, deployFile.OS, deployFile.Arch, deployFile.Version) + if err != nil { + return nil, err + } + total += count + } + return this.SuccessCount(total) +} + +// FindAllUpgradeHTTPDNSNodesWithClusterId 列出所有需要升级的HTTPDNS节点 +func (this *HTTPDNSNodeService) FindAllUpgradeHTTPDNSNodesWithClusterId(ctx context.Context, req *pb.FindAllUpgradeHTTPDNSNodesWithClusterIdRequest) (*pb.FindAllUpgradeHTTPDNSNodesWithClusterIdResponse, error) { + _, err := this.ValidateAdmin(ctx) + if err != nil { + return nil, err + } + + var tx = this.NullTx() + deployFiles := installers.SharedDeployManager.LoadHTTPDNSNodeFiles() + var result []*pb.FindAllUpgradeHTTPDNSNodesWithClusterIdResponse_HTTPDNSNodeUpgrade + for _, deployFile := range deployFiles { + nodes, err := models.SharedHTTPDNSNodeDAO.FindAllLowerVersionNodesWithClusterId(tx, req.ClusterId, deployFile.OS, deployFile.Arch, deployFile.Version) + if err != nil { + return nil, err + } + for _, node := range nodes { + // 解析状态获取当前版本 + var oldVersion string + if len(node.Status) > 0 { + var statusMap map[string]interface{} + if json.Unmarshal(node.Status, &statusMap) == nil { + if v, ok := statusMap["buildVersion"]; ok { + oldVersion, _ = v.(string) + } + } + } + + pbNode := toPBNode(node) + + // 认证信息 + login, loginErr := models.SharedNodeLoginDAO.FindEnabledNodeLoginWithNodeId(tx, nodeconfigs.NodeRoleHTTPDNS, int64(node.Id)) + if loginErr != nil { + return nil, loginErr + } + if login != nil && pbNode != nil { + pbNode.NodeLogin = &pb.NodeLogin{ + Id: int64(login.Id), + Name: login.Name, + Type: login.Type, + Params: login.Params, + } + } + + result = append(result, &pb.FindAllUpgradeHTTPDNSNodesWithClusterIdResponse_HTTPDNSNodeUpgrade{ + Node: pbNode, + Os: deployFile.OS, + Arch: deployFile.Arch, + OldVersion: oldVersion, + NewVersion: deployFile.Version, + }) + } + } + return &pb.FindAllUpgradeHTTPDNSNodesWithClusterIdResponse{Nodes: result}, nil +} + +// UpgradeHTTPDNSNode 升级单个HTTPDNS节点 +func (this *HTTPDNSNodeService) UpgradeHTTPDNSNode(ctx context.Context, req *pb.UpgradeHTTPDNSNodeRequest) (*pb.RPCSuccess, error) { + _, err := this.ValidateAdmin(ctx) + if err != nil { + return nil, err + } + + var tx = this.NullTx() + + err = models.SharedHTTPDNSNodeDAO.UpdateNodeIsInstalled(tx, req.NodeId, false) + if err != nil { + return nil, err + } + + // 重置安装状态 + installStatus, err := models.SharedHTTPDNSNodeDAO.FindNodeInstallStatus(tx, req.NodeId) + if err != nil { + return nil, err + } + if installStatus == nil { + installStatus = &models.NodeInstallStatus{} + } + installStatus.IsOk = false + installStatus.IsFinished = false + err = models.SharedHTTPDNSNodeDAO.UpdateNodeInstallStatus(tx, req.NodeId, installStatus) + if err != nil { + return nil, err + } + + goman.New(func() { + installErr := installers.SharedHTTPDNSNodeQueue().InstallNodeProcess(req.NodeId, true) + if installErr != nil { + logs.Println("[RPC][HTTPDNS]upgrade node failed:", installErr.Error()) + } + }) + + return this.Success() +} + func shouldTriggerHTTPDNSInstall(installStatusJSON []byte) bool { if len(installStatusJSON) == 0 { return false diff --git a/EdgeAPI/internal/rpc/services/nameservers/service_ns_node.go b/EdgeAPI/internal/rpc/services/nameservers/service_ns_node.go index 3cb4a33..ecbbeb6 100644 --- a/EdgeAPI/internal/rpc/services/nameservers/service_ns_node.go +++ b/EdgeAPI/internal/rpc/services/nameservers/service_ns_node.go @@ -12,6 +12,7 @@ import ( "github.com/TeaOSLab/EdgeAPI/internal/goman" "github.com/TeaOSLab/EdgeAPI/internal/installers" "github.com/TeaOSLab/EdgeAPI/internal/rpc/services" + "github.com/TeaOSLab/EdgeAPI/internal/setup" rpcutils "github.com/TeaOSLab/EdgeAPI/internal/rpc/utils" "github.com/TeaOSLab/EdgeCommon/pkg/configutils" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" @@ -484,6 +485,12 @@ func (this *NSNodeService) DownloadNSNodeInstallationFile(ctx context.Context, r return nil, err } + // 检查自动升级开关 + upgradeConfig, _ := setup.LoadUpgradeConfig() + if upgradeConfig != nil && !upgradeConfig.AutoUpgrade { + return &pb.DownloadNSNodeInstallationFileResponse{}, nil + } + var file = installers.SharedDeployManager.FindNSNodeFile(req.Os, req.Arch) if file == nil { return &pb.DownloadNSNodeInstallationFileResponse{}, nil @@ -738,3 +745,109 @@ func (this *NSNodeService) UpdateNSNodeAPIConfig(ctx context.Context, req *pb.Up return this.Success() } + +// FindAllUpgradeNSNodesWithNSClusterId 列出所有需要升级的NS节点 +func (this *NSNodeService) FindAllUpgradeNSNodesWithNSClusterId(ctx context.Context, req *pb.FindAllUpgradeNSNodesWithNSClusterIdRequest) (*pb.FindAllUpgradeNSNodesWithNSClusterIdResponse, error) { + _, err := this.ValidateAdmin(ctx) + if err != nil { + return nil, err + } + + var tx = this.NullTx() + deployFiles := installers.SharedDeployManager.LoadNSNodeFiles() + var result []*pb.FindAllUpgradeNSNodesWithNSClusterIdResponse_NSNodeUpgrade + for _, deployFile := range deployFiles { + nodes, err := models.SharedNSNodeDAO.FindAllLowerVersionNodesWithClusterId(tx, req.NsClusterId, deployFile.OS, deployFile.Arch, deployFile.Version) + if err != nil { + return nil, err + } + for _, node := range nodes { + // 解析状态获取当前版本 + var oldVersion string + if len(node.Status) > 0 { + var statusMap map[string]interface{} + if json.Unmarshal(node.Status, &statusMap) == nil { + if v, ok := statusMap["buildVersion"]; ok { + oldVersion, _ = v.(string) + } + } + } + + // 安装信息 + installStatus, installErr := node.DecodeInstallStatus() + if installErr != nil { + return nil, installErr + } + pbInstallStatus := &pb.NodeInstallStatus{} + if installStatus != nil { + pbInstallStatus = &pb.NodeInstallStatus{ + IsRunning: installStatus.IsRunning, + IsFinished: installStatus.IsFinished, + IsOk: installStatus.IsOk, + Error: installStatus.Error, + ErrorCode: installStatus.ErrorCode, + UpdatedAt: installStatus.UpdatedAt, + } + } + + // 认证信息 + login, loginErr := models.SharedNodeLoginDAO.FindEnabledNodeLoginWithNodeId(tx, nodeconfigs.NodeRoleDNS, int64(node.Id)) + if loginErr != nil { + return nil, loginErr + } + var pbLogin *pb.NodeLogin + if login != nil { + pbLogin = &pb.NodeLogin{ + Id: int64(login.Id), + Name: login.Name, + Type: login.Type, + Params: login.Params, + } + } + + result = append(result, &pb.FindAllUpgradeNSNodesWithNSClusterIdResponse_NSNodeUpgrade{ + NsNode: &pb.NSNode{ + Id: int64(node.Id), + Name: node.Name, + IsOn: node.IsOn, + UniqueId: node.UniqueId, + IsInstalled: node.IsInstalled, + IsUp: node.IsUp, + IsActive: node.IsActive, + StatusJSON: node.Status, + InstallStatus: pbInstallStatus, + NodeLogin: pbLogin, + }, + Os: deployFile.OS, + Arch: deployFile.Arch, + OldVersion: oldVersion, + NewVersion: deployFile.Version, + }) + } + } + return &pb.FindAllUpgradeNSNodesWithNSClusterIdResponse{Nodes: result}, nil +} + +// UpgradeNSNode 升级单个NS节点 +func (this *NSNodeService) UpgradeNSNode(ctx context.Context, req *pb.UpgradeNSNodeRequest) (*pb.RPCSuccess, error) { + _, err := this.ValidateAdmin(ctx) + if err != nil { + return nil, err + } + + var tx = this.NullTx() + + err = models.SharedNSNodeDAO.UpdateNodeIsInstalled(tx, req.NsNodeId, false) + if err != nil { + return nil, err + } + + goman.New(func() { + installErr := installers.SharedNSNodeQueue().InstallNodeProcess(req.NsNodeId, true) + if installErr != nil { + logs.Println("[RPC]upgrade dns node:" + installErr.Error()) + } + }) + + return this.Success() +} diff --git a/EdgeAPI/internal/rpc/services/service_node.go b/EdgeAPI/internal/rpc/services/service_node.go index 723f76b..25b82b2 100644 --- a/EdgeAPI/internal/rpc/services/service_node.go +++ b/EdgeAPI/internal/rpc/services/service_node.go @@ -12,6 +12,7 @@ import ( "github.com/TeaOSLab/EdgeAPI/internal/installers" "github.com/TeaOSLab/EdgeAPI/internal/remotelogs" rpcutils "github.com/TeaOSLab/EdgeAPI/internal/rpc/utils" + "github.com/TeaOSLab/EdgeAPI/internal/setup" "github.com/TeaOSLab/EdgeAPI/internal/utils" "github.com/TeaOSLab/EdgeAPI/internal/utils/numberutils" "github.com/TeaOSLab/EdgeCommon/pkg/configutils" @@ -1716,6 +1717,12 @@ func (this *NodeService) DownloadNodeInstallationFile(ctx context.Context, req * return nil, err } + // 检查自动升级开关 + upgradeConfig, _ := setup.LoadUpgradeConfig() + if upgradeConfig != nil && !upgradeConfig.AutoUpgrade { + return &pb.DownloadNodeInstallationFileResponse{}, nil + } + var file = installers.SharedDeployManager.FindNodeFile(req.Os, req.Arch) if file == nil { return &pb.DownloadNodeInstallationFileResponse{}, nil diff --git a/EdgeAPI/internal/setup/sql_upgrade.go b/EdgeAPI/internal/setup/sql_upgrade.go index 2b986b9..40b789f 100644 --- a/EdgeAPI/internal/setup/sql_upgrade.go +++ b/EdgeAPI/internal/setup/sql_upgrade.go @@ -113,6 +113,9 @@ var upgradeFuncs = []*upgradeVersion{ { "1.4.8", upgradeV1_4_8, }, + { + "1.4.9", upgradeV1_4_9, + }, } // UpgradeSQLData 升级SQL数据 @@ -1274,10 +1277,25 @@ func upgradeV1_4_8(db *dbs.DB) error { return nil } +// 1.4.9 +func upgradeV1_4_9(db *dbs.DB) error { + _, err := db.Exec("ALTER TABLE `edgeHTTPDNSClusters` ALTER COLUMN `installDir` SET DEFAULT '/root/edge-httpdns'") + if err != nil { + return err + } + + _, err = db.Exec("ALTER TABLE `edgeHTTPDNSNodes` ALTER COLUMN `installDir` SET DEFAULT '/root/edge-httpdns'") + if err != nil { + return err + } + + return nil +} + func createHTTPDNSTables(db *dbs.DB) error { sqls := []string{ - "CREATE TABLE IF NOT EXISTS `edgeHTTPDNSClusters` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`name` varchar(255) DEFAULT NULL,`isOn` tinyint unsigned DEFAULT '1',`isDefault` tinyint unsigned DEFAULT '0',`serviceDomain` varchar(255) DEFAULT NULL,`defaultTTL` int unsigned DEFAULT '30',`fallbackTimeoutMs` int unsigned DEFAULT '300',`installDir` varchar(255) DEFAULT '/opt/edge-httpdns',`tlsPolicy` json DEFAULT NULL,`createdAt` bigint unsigned DEFAULT '0',`updatedAt` bigint unsigned DEFAULT '0',`state` tinyint unsigned DEFAULT '1',PRIMARY KEY (`id`),KEY `name` (`name`),KEY `isDefault` (`isDefault`),KEY `state` (`state`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HTTPDNS集群配置表(默认TTL、回退超时、服务域名等)'", - "CREATE TABLE IF NOT EXISTS `edgeHTTPDNSNodes` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`clusterId` bigint unsigned DEFAULT '0',`name` varchar(255) DEFAULT NULL,`isOn` tinyint unsigned DEFAULT '1',`isUp` tinyint unsigned DEFAULT '0',`isInstalled` tinyint unsigned DEFAULT '0',`isActive` tinyint unsigned DEFAULT '0',`uniqueId` varchar(64) DEFAULT NULL,`secret` varchar(64) DEFAULT NULL,`installDir` varchar(255) DEFAULT '/opt/edge-httpdns',`status` json DEFAULT NULL,`installStatus` json DEFAULT NULL,`createdAt` bigint unsigned DEFAULT '0',`updatedAt` bigint unsigned DEFAULT '0',`state` tinyint unsigned DEFAULT '1',PRIMARY KEY (`id`),UNIQUE KEY `uniqueId` (`uniqueId`),KEY `clusterId` (`clusterId`),KEY `state` (`state`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HTTPDNS节点表(节点基础信息与运行状态)'", + "CREATE TABLE IF NOT EXISTS `edgeHTTPDNSClusters` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`name` varchar(255) DEFAULT NULL,`isOn` tinyint unsigned DEFAULT '1',`isDefault` tinyint unsigned DEFAULT '0',`serviceDomain` varchar(255) DEFAULT NULL,`defaultTTL` int unsigned DEFAULT '30',`fallbackTimeoutMs` int unsigned DEFAULT '300',`installDir` varchar(255) DEFAULT '/root/edge-httpdns',`tlsPolicy` json DEFAULT NULL,`autoRemoteStart` tinyint unsigned DEFAULT '0',`accessLogIsOn` tinyint unsigned DEFAULT '0',`timeZone` varchar(128) NOT NULL DEFAULT '',`createdAt` bigint unsigned DEFAULT '0',`updatedAt` bigint unsigned DEFAULT '0',`state` tinyint unsigned DEFAULT '1',PRIMARY KEY (`id`),KEY `name` (`name`),KEY `isDefault` (`isDefault`),KEY `state` (`state`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HTTPDNS集群配置表(默认TTL、回退超时、服务域名等)'", + "CREATE TABLE IF NOT EXISTS `edgeHTTPDNSNodes` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`clusterId` bigint unsigned DEFAULT '0',`name` varchar(255) DEFAULT NULL,`isOn` tinyint unsigned DEFAULT '1',`isUp` tinyint unsigned DEFAULT '0',`isInstalled` tinyint unsigned DEFAULT '0',`isActive` tinyint unsigned DEFAULT '0',`uniqueId` varchar(64) DEFAULT NULL,`secret` varchar(64) DEFAULT NULL,`installDir` varchar(255) DEFAULT '/root/edge-httpdns',`status` json DEFAULT NULL,`installStatus` json DEFAULT NULL,`createdAt` bigint unsigned DEFAULT '0',`updatedAt` bigint unsigned DEFAULT '0',`state` tinyint unsigned DEFAULT '1',PRIMARY KEY (`id`),UNIQUE KEY `uniqueId` (`uniqueId`),KEY `clusterId` (`clusterId`),KEY `state` (`state`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HTTPDNS节点表(节点基础信息与运行状态)'", "CREATE TABLE IF NOT EXISTS `edgeHTTPDNSApps` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`name` varchar(255) DEFAULT NULL,`appId` varchar(64) DEFAULT NULL,`isOn` tinyint unsigned DEFAULT '1',`clusterIdsJSON` text DEFAULT NULL,`sniMode` varchar(64) DEFAULT 'fixed_hide',`userId` bigint unsigned DEFAULT '0',`createdAt` bigint unsigned DEFAULT '0',`updatedAt` bigint unsigned DEFAULT '0',`state` tinyint unsigned DEFAULT '1',PRIMARY KEY (`id`),UNIQUE KEY `appId` (`appId`),KEY `name` (`name`),KEY `userId` (`userId`),KEY `state` (`state`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HTTPDNS应用表(应用与集群绑定关系)'", "CREATE TABLE IF NOT EXISTS `edgeHTTPDNSAppSecrets` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`appId` bigint unsigned DEFAULT '0',`signEnabled` tinyint unsigned DEFAULT '0',`signSecret` varchar(255) DEFAULT NULL,`signUpdatedAt` bigint unsigned DEFAULT '0',`updatedAt` bigint unsigned DEFAULT '0',`state` tinyint unsigned DEFAULT '1',PRIMARY KEY (`id`),UNIQUE KEY `appId` (`appId`),KEY `state` (`state`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HTTPDNS应用密钥表(请求验签开关与加签Secret)'", "CREATE TABLE IF NOT EXISTS `edgeHTTPDNSDomains` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`appId` bigint unsigned DEFAULT '0',`domain` varchar(255) DEFAULT NULL,`isOn` tinyint unsigned DEFAULT '1',`createdAt` bigint unsigned DEFAULT '0',`updatedAt` bigint unsigned DEFAULT '0',`state` tinyint unsigned DEFAULT '1',PRIMARY KEY (`id`),UNIQUE KEY `appId_domain` (`appId`,`domain`),KEY `domain` (`domain`),KEY `state` (`state`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HTTPDNS应用域名表(应用绑定的业务域名)'", diff --git a/EdgeAPI/internal/setup/upgrade_config.go b/EdgeAPI/internal/setup/upgrade_config.go new file mode 100644 index 0000000..99ef559 --- /dev/null +++ b/EdgeAPI/internal/setup/upgrade_config.go @@ -0,0 +1,45 @@ +package setup + +import ( + "encoding/json" + "sync" + "time" + + "github.com/TeaOSLab/EdgeAPI/internal/db/models" + "github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs" +) + +var ( + sharedUpgradeConfig *systemconfigs.UpgradeConfig + sharedUpgradeConfigTime time.Time + sharedUpgradeConfigMu sync.Mutex +) + +const upgradeConfigTTL = 5 * time.Minute + +// LoadUpgradeConfig 读取升级配置(带5分钟内存缓存) +func LoadUpgradeConfig() (*systemconfigs.UpgradeConfig, error) { + sharedUpgradeConfigMu.Lock() + defer sharedUpgradeConfigMu.Unlock() + + if sharedUpgradeConfig != nil && time.Since(sharedUpgradeConfigTime) < upgradeConfigTTL { + return sharedUpgradeConfig, nil + } + + valueJSON, err := models.SharedSysSettingDAO.ReadSetting(nil, systemconfigs.SettingCodeUpgradeConfig) + if err != nil { + return nil, err + } + + config := systemconfigs.NewUpgradeConfig() + if len(valueJSON) > 0 { + err = json.Unmarshal(valueJSON, config) + if err != nil { + return config, nil + } + } + + sharedUpgradeConfig = config + sharedUpgradeConfigTime = time.Now() + return config, nil +} diff --git a/EdgeAPI/internal/tasks/httpdns_node_monitor_task.go b/EdgeAPI/internal/tasks/httpdns_node_monitor_task.go new file mode 100644 index 0000000..949d91b --- /dev/null +++ b/EdgeAPI/internal/tasks/httpdns_node_monitor_task.go @@ -0,0 +1,107 @@ +package tasks + +import ( + "time" + + "github.com/TeaOSLab/EdgeAPI/internal/db/models" + "github.com/TeaOSLab/EdgeAPI/internal/goman" + "github.com/TeaOSLab/EdgeAPI/internal/installers" + "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" + "github.com/iwind/TeaGo/dbs" +) + +func init() { + dbs.OnReadyDone(func() { + goman.New(func() { + NewHTTPDNSNodeMonitorTask(1 * time.Minute).Start() + }) + }) +} + +type httpdnsNodeStartingTry struct { + count int + timestamp int64 +} + +// HTTPDNSNodeMonitorTask monitors HTTPDNS node activity and optionally tries to start offline nodes. +type HTTPDNSNodeMonitorTask struct { + BaseTask + + ticker *time.Ticker + + recoverMap map[int64]*httpdnsNodeStartingTry // nodeId => retry info +} + +func NewHTTPDNSNodeMonitorTask(duration time.Duration) *HTTPDNSNodeMonitorTask { + return &HTTPDNSNodeMonitorTask{ + ticker: time.NewTicker(duration), + recoverMap: map[int64]*httpdnsNodeStartingTry{}, + } +} + +func (t *HTTPDNSNodeMonitorTask) Start() { + for range t.ticker.C { + if err := t.Loop(); err != nil { + t.logErr("HTTPDNS_NODE_MONITOR", err.Error()) + } + } +} + +func (t *HTTPDNSNodeMonitorTask) Loop() error { + // only run on primary api node + if !t.IsPrimaryNode() { + return nil + } + + clusters, err := models.SharedHTTPDNSClusterDAO.FindAllEnabledClusters(nil) + if err != nil { + return err + } + + for _, cluster := range clusters { + if cluster == nil || !cluster.IsOn || !cluster.AutoRemoteStart { + continue + } + clusterID := int64(cluster.Id) + inactiveNodes, err := models.SharedHTTPDNSNodeDAO.FindAllInactiveNodesWithClusterId(nil, clusterID) + if err != nil { + return err + } + if len(inactiveNodes) == 0 { + continue + } + + nodeQueue := installers.NewHTTPDNSNodeQueue() + for _, node := range inactiveNodes { + nodeID := int64(node.Id) + tryInfo, ok := t.recoverMap[nodeID] + if !ok { + tryInfo = &httpdnsNodeStartingTry{ + count: 1, + timestamp: time.Now().Unix(), + } + t.recoverMap[nodeID] = tryInfo + } else { + if tryInfo.count >= 3 { + if tryInfo.timestamp+10*60 > time.Now().Unix() { + continue + } + tryInfo.timestamp = time.Now().Unix() + tryInfo.count = 0 + } + tryInfo.count++ + } + + err = nodeQueue.StartNode(nodeID) + if err != nil { + if !installers.IsGrantError(err) { + _ = models.SharedNodeLogDAO.CreateLog(nil, nodeconfigs.NodeRoleHTTPDNS, nodeID, 0, 0, models.LevelError, "NODE", "start node from remote API failed: "+err.Error(), time.Now().Unix(), "", nil) + } + continue + } + _ = models.SharedNodeLogDAO.CreateLog(nil, nodeconfigs.NodeRoleHTTPDNS, nodeID, 0, 0, models.LevelSuccess, "NODE", "start node from remote API successfully", time.Now().Unix(), "", nil) + } + } + + return nil +} diff --git a/EdgeAdmin/build/build.sh b/EdgeAdmin/build/build.sh index 56c1c96..e3eb1ac 100644 --- a/EdgeAdmin/build/build.sh +++ b/EdgeAdmin/build/build.sh @@ -1,4 +1,28 @@ #!/usr/bin/env bash +set -e + +function verify_components_bundle() { + local file_path="$1" + if [ ! -f "$file_path" ]; then + echo "[error] components.js not found: $file_path" + return 1 + fi + + local file_size + file_size=$(wc -c < "$file_path") + if [ "$file_size" -lt 100000 ]; then + echo "[error] components.js looks too small ($file_size bytes), generate likely failed" + return 1 + fi + + if ! grep -q 'Vue.component("csrf-token"' "$file_path"; then + echo "[error] components.js missing csrf-token component, generate likely failed" + return 1 + fi + + echo "verify components.js: ok ($file_size bytes)" + return 0 +} function build() { ROOT=$(dirname "$0") @@ -58,7 +82,7 @@ function build() { # generate files echo "generating files ..." - env CGO_ENABLED=0 go run -tags $TAG "$ROOT"/../cmd/edge-admin/main.go generate + env TEAROOT="$ROOT" CGO_ENABLED=0 go run -tags "$TAG" "$ROOT"/../cmd/edge-admin/main.go generate if [ "$(which uglifyjs)" ]; then echo "compress to component.js ..." uglifyjs --compress --mangle -- "${JS_ROOT}"/components.src.js > "${JS_ROOT}"/components.js @@ -69,6 +93,8 @@ function build() { cp "${JS_ROOT}"/utils.js "${JS_ROOT}"/utils.min.js fi + verify_components_bundle "${JS_ROOT}/components.js" + # create dir & copy files echo "copying ..." if [ ! -d "$DIST" ]; then diff --git a/EdgeAdmin/build/generate.sh b/EdgeAdmin/build/generate.sh index 248bfa2..99fbdba 100644 --- a/EdgeAdmin/build/generate.sh +++ b/EdgeAdmin/build/generate.sh @@ -1,22 +1,49 @@ #!/usr/bin/env bash +set -e -JS_ROOT=../web/public/js +ROOT=$(cd "$(dirname "$0")" && pwd) +JS_ROOT="$ROOT"/../web/public/js + +function verify_components_bundle() { + local file_path="$1" + if [ ! -f "$file_path" ]; then + echo "[error] components.js not found: $file_path" + return 1 + fi + + local file_size + file_size=$(wc -c < "$file_path") + if [ "$file_size" -lt 100000 ]; then + echo "[error] components.js looks too small ($file_size bytes), generate likely failed" + return 1 + fi + + if ! grep -q 'Vue.component("csrf-token"' "$file_path"; then + echo "[error] components.js missing csrf-token component, generate likely failed" + return 1 + fi + + echo "verify components.js: ok ($file_size bytes)" + return 0 +} echo "generating component.src.js ..." -env CGO_ENABLED=0 go run -tags=community ../cmd/edge-admin/main.go generate +env TEAROOT="$ROOT" CGO_ENABLED=0 go run -tags=community "$ROOT"/../cmd/edge-admin/main.go generate if [ "$(which uglifyjs)" ]; then echo "compress to component.js ..." - uglifyjs --compress --mangle -- ${JS_ROOT}/components.src.js > ${JS_ROOT}/components.js + uglifyjs --compress --mangle -- "${JS_ROOT}"/components.src.js > "${JS_ROOT}"/components.js echo "compress to utils.min.js ..." - uglifyjs --compress --mangle -- ${JS_ROOT}/utils.js > ${JS_ROOT}/utils.min.js + uglifyjs --compress --mangle -- "${JS_ROOT}"/utils.js > "${JS_ROOT}"/utils.min.js else echo "copy to component.js ..." - cp ${JS_ROOT}/components.src.js ${JS_ROOT}/components.js + cp "${JS_ROOT}"/components.src.js "${JS_ROOT}"/components.js echo "copy to utils.min.js ..." - cp ${JS_ROOT}/utils.js ${JS_ROOT}/utils.min.js + cp "${JS_ROOT}"/utils.js "${JS_ROOT}"/utils.min.js fi -echo "ok" \ No newline at end of file +verify_components_bundle "${JS_ROOT}/components.js" + +echo "ok" diff --git a/EdgeAdmin/cmd/edge-admin/main.go b/EdgeAdmin/cmd/edge-admin/main.go index 2e4eaa2..52fd816 100644 --- a/EdgeAdmin/cmd/edge-admin/main.go +++ b/EdgeAdmin/cmd/edge-admin/main.go @@ -22,6 +22,7 @@ import ( "log" "os" "os/exec" + "path/filepath" "time" ) @@ -112,10 +113,12 @@ func main() { } }) app.On("generate", func() { + prepareGenerateRoot() + err := gen.Generate() if err != nil { fmt.Println("generate failed: " + err.Error()) - return + os.Exit(1) } }) app.On("dev", func() { @@ -214,3 +217,32 @@ func main() { adminNode.Run() }) } + +func prepareGenerateRoot() { + wd, err := os.Getwd() + if err != nil { + return + } + + candidates := []string{ + wd, + filepath.Clean(filepath.Join(wd, "..")), + } + + for _, root := range candidates { + componentsDir := filepath.Join(root, "web", "public", "js", "components") + stat, statErr := os.Stat(componentsDir) + if statErr != nil || !stat.IsDir() { + continue + } + + // In testing mode, generator reads from Tea.Root + "/../web/...", + // so keep Root under build dir to make relative path stable. + buildRoot := filepath.Join(root, "build") + Tea.UpdateRoot(buildRoot) + Tea.SetPublicDir(filepath.Join(root, "web", "public")) + Tea.SetViewsDir(filepath.Join(root, "web", "views")) + Tea.SetTmpDir(filepath.Join(root, "web", "tmp")) + return + } +} diff --git a/EdgeAdmin/internal/configloaders/upgrade_config.go b/EdgeAdmin/internal/configloaders/upgrade_config.go new file mode 100644 index 0000000..cb299d1 --- /dev/null +++ b/EdgeAdmin/internal/configloaders/upgrade_config.go @@ -0,0 +1,69 @@ +package configloaders + +import ( + "encoding/json" + + "github.com/TeaOSLab/EdgeAdmin/internal/rpc" + "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" + "github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs" +) + +const UpgradeSettingName = "upgradeConfig" + +var sharedUpgradeConfig *systemconfigs.UpgradeConfig + +func LoadUpgradeConfig() (*systemconfigs.UpgradeConfig, error) { + locker.Lock() + defer locker.Unlock() + + if sharedUpgradeConfig != nil { + return sharedUpgradeConfig, nil + } + + rpcClient, err := rpc.SharedRPC() + if err != nil { + return nil, err + } + resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{ + Code: UpgradeSettingName, + }) + if err != nil { + return nil, err + } + if len(resp.ValueJSON) == 0 { + sharedUpgradeConfig = systemconfigs.NewUpgradeConfig() + return sharedUpgradeConfig, nil + } + + config := systemconfigs.NewUpgradeConfig() + err = json.Unmarshal(resp.ValueJSON, config) + if err != nil { + sharedUpgradeConfig = systemconfigs.NewUpgradeConfig() + return sharedUpgradeConfig, nil + } + sharedUpgradeConfig = config + return sharedUpgradeConfig, nil +} + +func UpdateUpgradeConfig(config *systemconfigs.UpgradeConfig) error { + locker.Lock() + defer locker.Unlock() + + rpcClient, err := rpc.SharedRPC() + if err != nil { + return err + } + valueJSON, err := json.Marshal(config) + if err != nil { + return err + } + _, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{ + Code: UpgradeSettingName, + ValueJSON: valueJSON, + }) + if err != nil { + return err + } + sharedUpgradeConfig = config + return nil +} diff --git a/EdgeAdmin/internal/const/const.go b/EdgeAdmin/internal/const/const.go index 2d7b7e8..55a74ae 100644 --- a/EdgeAdmin/internal/const/const.go +++ b/EdgeAdmin/internal/const/const.go @@ -1,9 +1,9 @@ package teaconst const ( - Version = "1.4.8" //1.3.9 + Version = "1.4.9" //1.3.9 - APINodeVersion = "1.4.8" //1.3.9 + APINodeVersion = "1.4.9" //1.3.9 ProductName = "Edge Admin" ProcessName = "edge-admin" diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk_check.go b/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk_check.go index a40db86..a8f2a3c 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk_check.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk_check.go @@ -28,19 +28,20 @@ func (this *SdkCheckAction) RunGet(params struct { return } + version := strings.TrimSpace(params.Version) t := strings.ToLower(strings.TrimSpace(params.Type)) if t == "doc" { - docPath := findUploadedSDKDocPath(platform, params.Version) + docPath := findUploadedSDKDocPath(platform, version) if len(docPath) == 0 { this.Data["exists"] = false - this.Data["message"] = "Documentation is unavailable, please upload first" + this.Data["message"] = "当前平台/版本尚未上传集成文档" this.Success() return } downloadURL := "/httpdns/apps/sdk/doc?platform=" + url.QueryEscape(platform) - if len(strings.TrimSpace(params.Version)) > 0 { - downloadURL += "&version=" + url.QueryEscape(strings.TrimSpace(params.Version)) + if len(version) > 0 { + downloadURL += "&version=" + url.QueryEscape(version) } this.Data["exists"] = true this.Data["url"] = downloadURL @@ -48,17 +49,17 @@ func (this *SdkCheckAction) RunGet(params struct { return } - archivePath := findSDKArchivePath(filename, params.Version) + archivePath := findSDKArchivePath(filename, version) if len(archivePath) == 0 { this.Data["exists"] = false - this.Data["message"] = "SDK package is unavailable, please upload first" + this.Data["message"] = "当前平台/版本尚未上传 SDK 安装包" this.Success() return } downloadURL := "/httpdns/apps/sdk/download?platform=" + url.QueryEscape(platform) - if len(strings.TrimSpace(params.Version)) > 0 { - downloadURL += "&version=" + url.QueryEscape(strings.TrimSpace(params.Version)) + if len(version) > 0 { + downloadURL += "&version=" + url.QueryEscape(version) } downloadURL += "&raw=1" this.Data["exists"] = true diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk_doc.go b/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk_doc.go index 95ea910..d02cd0c 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk_doc.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk_doc.go @@ -3,7 +3,6 @@ package apps import ( "os" "path/filepath" - "strings" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" ) @@ -20,7 +19,7 @@ func (this *SdkDocAction) RunGet(params struct { Platform string Version string }) { - platform, _, readmeRelativePath, _, err := resolveSDKPlatform(params.Platform) + platform, _, _, _, err := resolveSDKPlatform(params.Platform) if err != nil { this.Data["exists"] = false this.Data["message"] = err.Error() @@ -28,35 +27,25 @@ func (this *SdkDocAction) RunGet(params struct { return } - var data []byte - uploadedDocPath := findUploadedSDKDocPath(platform, params.Version) - if len(uploadedDocPath) > 0 { - data, err = os.ReadFile(uploadedDocPath) - } - - sdkRoot, sdkRootErr := findSDKRoot() - if len(data) == 0 && sdkRootErr == nil { - readmePath := filepath.Join(sdkRoot, readmeRelativePath) - data, err = os.ReadFile(readmePath) - } - - if len(data) == 0 { - localDocPath := findLocalSDKDocPath(platform) - if len(localDocPath) > 0 { - data, err = os.ReadFile(localDocPath) - } - } - - if len(data) == 0 || err != nil { + docPath := findUploadedSDKDocPath(platform, params.Version) + if len(docPath) == 0 { this.Data["exists"] = false - this.Data["message"] = "SDK documentation is not found on server, please upload first" + this.Data["message"] = "当前平台/版本尚未上传集成文档" this.Success() return } - downloadName := filepath.Base(uploadedDocPath) + data, err := os.ReadFile(docPath) + if err != nil || len(data) == 0 { + this.Data["exists"] = false + this.Data["message"] = "读取集成文档失败" + this.Success() + return + } + + downloadName := filepath.Base(docPath) if len(downloadName) == 0 || downloadName == "." || downloadName == string(filepath.Separator) { - downloadName = "httpdns-sdk-" + strings.ToLower(platform) + ".md" + downloadName = "sdk-doc.md" } this.AddHeader("Content-Type", "text/markdown; charset=utf-8") diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk_download.go b/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk_download.go index 45a7066..09e41d2 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk_download.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk_download.go @@ -32,7 +32,7 @@ func (this *SdkDownloadAction) RunGet(params struct { archivePath := findSDKArchivePath(filename, params.Version) if len(archivePath) == 0 { this.Data["exists"] = false - this.Data["message"] = "SDK archive not found on server, please upload first: " + filename + this.Data["message"] = "当前平台/版本尚未上传 SDK 安装包" this.Success() return } @@ -40,7 +40,7 @@ func (this *SdkDownloadAction) RunGet(params struct { fp, err := os.Open(archivePath) if err != nil { this.Data["exists"] = false - this.Data["message"] = "failed to open SDK archive: " + err.Error() + this.Data["message"] = "打开 SDK 安装包失败: " + err.Error() this.Success() return } diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk_helpers.go b/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk_helpers.go index e16d05f..236d5c9 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk_helpers.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk_helpers.go @@ -1,16 +1,183 @@ package apps import ( + "encoding/json" "errors" "os" "path/filepath" - "sort" "strings" "time" "github.com/iwind/TeaGo/Tea" ) +type sdkUploadMeta struct { + Platform string `json:"platform"` + Version string `json:"version"` + FileType string `json:"fileType"` // sdk | doc + Filename string `json:"filename"` + UpdatedAt int64 `json:"updatedAt"` +} + +type sdkUploadMetaRecord struct { + Meta sdkUploadMeta + Dir string + FilePath string +} + +func sdkUploadMetaFilename(platform string, version string, fileType string) string { + platform = strings.ToLower(strings.TrimSpace(platform)) + version = strings.TrimSpace(version) + fileType = strings.ToLower(strings.TrimSpace(fileType)) + return ".httpdns-sdk-meta-" + platform + "-v" + version + "-" + fileType + ".json" +} + +func isSDKUploadMetaFile(name string) bool { + name = strings.ToLower(strings.TrimSpace(name)) + return strings.HasPrefix(name, ".httpdns-sdk-meta-") && strings.HasSuffix(name, ".json") +} + +func parseSDKPlatformFromDownloadFilename(downloadFilename string) string { + name := strings.ToLower(strings.TrimSpace(downloadFilename)) + if !strings.HasPrefix(name, "httpdns-sdk-") || !strings.HasSuffix(name, ".zip") { + return "" + } + + platform := strings.TrimSuffix(strings.TrimPrefix(name, "httpdns-sdk-"), ".zip") + switch platform { + case "android", "ios", "flutter": + return platform + default: + return "" + } +} + +func listSDKUploadMetaRecords() []sdkUploadMetaRecord { + type wrapped struct { + record sdkUploadMetaRecord + modTime time.Time + } + + byKey := map[string]wrapped{} + for _, dir := range sdkUploadSearchDirs() { + entries, err := os.ReadDir(dir) + if err != nil { + continue + } + + for _, entry := range entries { + if entry.IsDir() { + continue + } + + name := entry.Name() + if !isSDKUploadMetaFile(name) { + continue + } + + metaPath := filepath.Join(dir, name) + data, err := os.ReadFile(metaPath) + if err != nil || len(data) == 0 { + continue + } + + var meta sdkUploadMeta + if err = json.Unmarshal(data, &meta); err != nil { + continue + } + + meta.Platform = strings.ToLower(strings.TrimSpace(meta.Platform)) + meta.Version = strings.TrimSpace(meta.Version) + meta.FileType = strings.ToLower(strings.TrimSpace(meta.FileType)) + meta.Filename = filepath.Base(strings.TrimSpace(meta.Filename)) + if len(meta.Platform) == 0 || len(meta.Version) == 0 || len(meta.Filename) == 0 { + continue + } + if meta.FileType != "sdk" && meta.FileType != "doc" { + continue + } + if strings.Contains(meta.Filename, "..") || strings.Contains(meta.Filename, "/") || strings.Contains(meta.Filename, "\\") { + continue + } + + filePath := filepath.Join(dir, meta.Filename) + fileStat, err := os.Stat(filePath) + if err != nil || fileStat.IsDir() || fileStat.Size() <= 0 { + continue + } + + metaStat, err := os.Stat(metaPath) + if err != nil { + continue + } + if meta.UpdatedAt <= 0 { + meta.UpdatedAt = metaStat.ModTime().Unix() + } + + key := meta.Platform + "|" + meta.Version + "|" + meta.FileType + current := wrapped{ + record: sdkUploadMetaRecord{ + Meta: meta, + Dir: dir, + FilePath: filePath, + }, + modTime: metaStat.ModTime(), + } + old, ok := byKey[key] + if !ok || + current.record.Meta.UpdatedAt > old.record.Meta.UpdatedAt || + (current.record.Meta.UpdatedAt == old.record.Meta.UpdatedAt && current.modTime.After(old.modTime)) || + (current.record.Meta.UpdatedAt == old.record.Meta.UpdatedAt && current.modTime.Equal(old.modTime) && current.record.FilePath > old.record.FilePath) { + byKey[key] = current + } + } + } + + result := make([]sdkUploadMetaRecord, 0, len(byKey)) + for _, item := range byKey { + result = append(result, item.record) + } + return result +} + +func findSDKUploadFileByMeta(platform string, version string, fileType string) string { + platform = strings.ToLower(strings.TrimSpace(platform)) + version = strings.TrimSpace(version) + fileType = strings.ToLower(strings.TrimSpace(fileType)) + if len(platform) == 0 || len(version) == 0 { + return "" + } + + for _, record := range listSDKUploadMetaRecords() { + if record.Meta.Platform == platform && record.Meta.Version == version && record.Meta.FileType == fileType { + return record.FilePath + } + } + return "" +} + +func findNewestSDKUploadFileByMeta(platform string, fileType string) string { + platform = strings.ToLower(strings.TrimSpace(platform)) + fileType = strings.ToLower(strings.TrimSpace(fileType)) + if len(platform) == 0 { + return "" + } + + var foundPath string + var foundUpdatedAt int64 + for _, record := range listSDKUploadMetaRecords() { + if record.Meta.Platform != platform || record.Meta.FileType != fileType { + continue + } + + if len(foundPath) == 0 || record.Meta.UpdatedAt > foundUpdatedAt || (record.Meta.UpdatedAt == foundUpdatedAt && record.FilePath > foundPath) { + foundPath = record.FilePath + foundUpdatedAt = record.Meta.UpdatedAt + } + } + return foundPath +} + func sdkUploadDir() string { dirs := sdkUploadDirs() if len(dirs) > 0 { @@ -27,6 +194,7 @@ func sdkUploadDirs() []string { filepath.Clean(Tea.Root + "/../edge-user/data/httpdns/sdk"), filepath.Clean(Tea.Root + "/../../data/httpdns/sdk"), } + results := make([]string, 0, len(candidates)) seen := map[string]bool{} for _, dir := range candidates { @@ -54,67 +222,6 @@ func sdkUploadSearchDirs() []string { return results } -func findFirstExistingDir(paths []string) string { - for _, path := range paths { - stat, err := os.Stat(path) - if err == nil && stat.IsDir() { - return path - } - } - return "" -} - -func findFirstExistingFile(paths []string) string { - for _, path := range paths { - stat, err := os.Stat(path) - if err == nil && !stat.IsDir() && stat.Size() > 0 { - return path - } - } - return "" -} - -func findNewestExistingFile(paths []string) string { - type fileInfo struct { - path string - modTime time.Time - } - result := fileInfo{} - for _, path := range paths { - stat, err := os.Stat(path) - if err != nil || stat.IsDir() { - continue - } - if stat.Size() <= 0 { - continue - } - if len(result.path) == 0 || stat.ModTime().After(result.modTime) || (stat.ModTime().Equal(result.modTime) && path > result.path) { - result.path = path - result.modTime = stat.ModTime() - } - } - return result.path -} - -func findSDKRoot() (string, error) { - candidates := []string{ - filepath.Clean(Tea.Root + "/EdgeHttpDNS/sdk"), - filepath.Clean(Tea.Root + "/edge-httpdns/sdk"), - filepath.Clean(Tea.Root + "/edge-httpdns/edge-httpdns/sdk"), - filepath.Clean(Tea.Root + "/../EdgeHttpDNS/sdk"), - filepath.Clean(Tea.Root + "/../../EdgeHttpDNS/sdk"), - filepath.Clean(Tea.Root + "/../edge-httpdns/sdk"), - filepath.Clean(Tea.Root + "/../../edge-httpdns/sdk"), - } - - dir := findFirstExistingDir(candidates) - if len(dir) > 0 { - return dir, nil - } - - return "", errors.New("SDK files are not found on current server") -} - func resolveSDKPlatform(platform string) (key string, relativeDir string, readmeRelativePath string, downloadFilename string, err error) { switch strings.ToLower(strings.TrimSpace(platform)) { case "android": @@ -122,52 +229,23 @@ func resolveSDKPlatform(platform string) (key string, relativeDir string, readme case "ios": return "ios", "ios", "ios/README.md", "httpdns-sdk-ios.zip", nil case "flutter": - return "flutter", "flutter/aliyun_httpdns", "flutter/aliyun_httpdns/README.md", "httpdns-sdk-flutter.zip", nil + return "flutter", "flutter/new_httpdns", "flutter/new_httpdns/README.md", "httpdns-sdk-flutter.zip", nil default: - return "", "", "", "", errors.New("invalid platform, expected one of: android, ios, flutter") + return "", "", "", "", errors.New("不支持的平台,可选值:android、ios、flutter") } } func findSDKArchivePath(downloadFilename string, version string) string { - searchDirs := sdkUploadSearchDirs() - - normalizedVersion := strings.TrimSpace(version) - base := strings.TrimSuffix(downloadFilename, ".zip") - if len(normalizedVersion) > 0 { - versionFiles := []string{} - for _, dir := range searchDirs { - versionFiles = append(versionFiles, filepath.Join(dir, base+"-v"+normalizedVersion+".zip")) - } - if path := findFirstExistingFile(versionFiles); len(path) > 0 { - return path - } + platform := parseSDKPlatformFromDownloadFilename(downloadFilename) + if len(platform) == 0 { return "" } - patternName := base + "-v*.zip" - matches := []string{} - for _, dir := range searchDirs { - found, _ := filepath.Glob(filepath.Join(dir, patternName)) - for _, file := range found { - stat, err := os.Stat(file) - if err == nil && !stat.IsDir() { - matches = append(matches, file) - } - } + normalizedVersion := strings.TrimSpace(version) + if len(normalizedVersion) > 0 { + return findSDKUploadFileByMeta(platform, normalizedVersion, "sdk") } - if len(matches) > 0 { - return findNewestExistingFile(matches) - } - - exactFiles := []string{} - for _, dir := range searchDirs { - exactFiles = append(exactFiles, filepath.Join(dir, downloadFilename)) - } - if path := findFirstExistingFile(exactFiles); len(path) > 0 { - return path - } - - return "" + return findNewestSDKUploadFileByMeta(platform, "sdk") } func findUploadedSDKDocPath(platform string, version string) string { @@ -176,42 +254,9 @@ func findUploadedSDKDocPath(platform string, version string) string { return "" } - searchDirs := sdkUploadSearchDirs() normalizedVersion := strings.TrimSpace(version) if len(normalizedVersion) > 0 { - exactVersion := []string{} - for _, dir := range searchDirs { - exactVersion = append(exactVersion, filepath.Join(dir, "httpdns-sdk-"+platform+"-v"+normalizedVersion+".md")) - } - if file := findFirstExistingFile(exactVersion); len(file) > 0 { - return file - } - return "" + return findSDKUploadFileByMeta(platform, normalizedVersion, "doc") } - - matches := []string{} - for _, dir := range searchDirs { - pattern := filepath.Join(dir, "httpdns-sdk-"+platform+"-v*.md") - found, _ := filepath.Glob(pattern) - matches = append(matches, found...) - } - if len(matches) > 0 { - sort.Strings(matches) - return findNewestExistingFile(matches) - } - - exact := []string{} - for _, dir := range searchDirs { - exact = append(exact, filepath.Join(dir, "httpdns-sdk-"+platform+".md")) - } - return findFirstExistingFile(exact) -} - -func findLocalSDKDocPath(platform string) string { - filename := strings.ToLower(strings.TrimSpace(platform)) + ".md" - candidates := []string{ - filepath.Clean(Tea.Root + "/edge-admin/web/views/@default/httpdns/apps/docs/" + filename), - filepath.Clean(Tea.Root + "/EdgeAdmin/web/views/@default/httpdns/apps/docs/" + filename), - } - return findFirstExistingFile(candidates) + return findNewestSDKUploadFileByMeta(platform, "doc") } diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk_upload.go b/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk_upload.go index 44bffd2..df5f2cb 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk_upload.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk_upload.go @@ -1,6 +1,7 @@ package apps import ( + "encoding/json" "errors" "os" "path/filepath" @@ -14,6 +15,8 @@ import ( "github.com/iwind/TeaGo/actions" ) +const sdkUploadMaxFileSize = 20 * 1024 * 1024 // 20MB + type SdkUploadAction struct { actionutils.ParentAction } @@ -52,7 +55,7 @@ func (this *SdkUploadAction) RunPost(params struct { }) { params.Must.Field("appId", params.AppId).Gt(0, "请选择应用") - platform, _, _, downloadFilename, err := resolveSDKPlatform(params.Platform) + platform, _, _, _, err := resolveSDKPlatform(params.Platform) if err != nil { this.Fail(err.Error()) return @@ -70,57 +73,21 @@ func (this *SdkUploadAction) RunPost(params struct { } uploadDir := sdkUploadDir() - err = os.MkdirAll(uploadDir, 0755) - if err != nil { + if err = os.MkdirAll(uploadDir, 0755); err != nil { this.Fail("创建上传目录失败: " + err.Error()) return } if params.SdkFile != nil { - filename := strings.ToLower(strings.TrimSpace(params.SdkFile.Filename)) - if !strings.HasSuffix(filename, ".zip") { - this.Fail("SDK 包仅支持 .zip 文件") - return - } - - sdkData, readErr := params.SdkFile.Read() - if readErr != nil { - this.Fail("读取 SDK 包失败: " + readErr.Error()) - return - } - if len(sdkData) == 0 { - this.Fail("SDK 包文件为空,请重新上传") - return - } - - baseName := strings.TrimSuffix(downloadFilename, ".zip") - err = saveSDKUploadFile(uploadDir, baseName+"-v"+version+".zip", sdkData) - if err != nil { - this.Fail("保存 SDK 包失败: " + err.Error()) + if err = this.saveUploadedItem(uploadDir, platform, version, "sdk", params.SdkFile); err != nil { + this.Fail(err.Error()) return } } if params.DocFile != nil { - docName := strings.ToLower(strings.TrimSpace(params.DocFile.Filename)) - if !strings.HasSuffix(docName, ".md") { - this.Fail("集成文档仅支持 .md 文件") - return - } - - docData, readErr := params.DocFile.Read() - if readErr != nil { - this.Fail("读取集成文档失败: " + readErr.Error()) - return - } - if len(docData) == 0 { - this.Fail("集成文档文件为空,请重新上传") - return - } - - err = saveSDKUploadFile(uploadDir, "httpdns-sdk-"+platform+"-v"+version+".md", docData) - if err != nil { - this.Fail("保存集成文档失败: " + err.Error()) + if err = this.saveUploadedItem(uploadDir, platform, version, "doc", params.DocFile); err != nil { + this.Fail(err.Error()) return } } @@ -128,6 +95,52 @@ func (this *SdkUploadAction) RunPost(params struct { this.Success() } +func (this *SdkUploadAction) saveUploadedItem(uploadDir string, platform string, version string, fileType string, file *actions.File) error { + expectedExt := ".md" + displayType := "集成文档" + if fileType == "sdk" { + expectedExt = ".zip" + displayType = "SDK 包" + } + + filename, err := normalizeUploadedFilename(file.Filename, expectedExt) + if err != nil { + return err + } + + if file.Size > sdkUploadMaxFileSize { + return errors.New(displayType + "文件不能超过 20MB") + } + + data, err := file.Read() + if err != nil { + return errors.New("读取" + displayType + "失败: " + err.Error()) + } + if len(data) == 0 { + return errors.New(displayType + "文件为空,请重新上传") + } + if len(data) > sdkUploadMaxFileSize { + return errors.New(displayType + "文件不能超过 20MB") + } + + if err = saveSDKUploadFile(uploadDir, filename, data); err != nil { + return errors.New("保存" + displayType + "失败: " + err.Error()) + } + + err = saveSDKUploadMetaRecord(uploadDir, sdkUploadMeta{ + Platform: platform, + Version: version, + FileType: fileType, + Filename: filename, + UpdatedAt: time.Now().Unix(), + }) + if err != nil { + return errors.New("保存上传元信息失败: " + err.Error()) + } + + return nil +} + func normalizeSDKVersion(version string) (string, error) { version = strings.TrimSpace(version) if len(version) == 0 { @@ -142,6 +155,26 @@ func normalizeSDKVersion(version string) (string, error) { return version, nil } +func normalizeUploadedFilename(raw string, expectedExt string) (string, error) { + filename := filepath.Base(strings.TrimSpace(raw)) + if len(filename) == 0 || filename == "." || filename == string(filepath.Separator) { + return "", errors.New("文件名不能为空") + } + if strings.Contains(filename, "/") || strings.Contains(filename, "\\") || strings.Contains(filename, "..") { + return "", errors.New("文件名不合法") + } + + actualExt := strings.ToLower(filepath.Ext(filename)) + if actualExt != strings.ToLower(expectedExt) { + if expectedExt == ".zip" { + return "", errors.New("SDK 包仅支持 .zip 文件") + } + return "", errors.New("集成文档仅支持 .md 文件") + } + + return filename, nil +} + func saveSDKUploadFile(baseDir string, filename string, data []byte) error { targetPath := filepath.Join(baseDir, filename) tmpPath := targetPath + ".tmp" @@ -152,6 +185,35 @@ func saveSDKUploadFile(baseDir string, filename string, data []byte) error { return os.Rename(tmpPath, targetPath) } +func saveSDKUploadMetaRecord(baseDir string, meta sdkUploadMeta) error { + meta.Platform = strings.ToLower(strings.TrimSpace(meta.Platform)) + meta.Version = strings.TrimSpace(meta.Version) + meta.FileType = strings.ToLower(strings.TrimSpace(meta.FileType)) + meta.Filename = filepath.Base(strings.TrimSpace(meta.Filename)) + if len(meta.Platform) == 0 || len(meta.Version) == 0 || len(meta.FileType) == 0 || len(meta.Filename) == 0 { + return errors.New("上传元信息不完整") + } + + metaFilename := sdkUploadMetaFilename(meta.Platform, meta.Version, meta.FileType) + metaPath := filepath.Join(baseDir, metaFilename) + if data, err := os.ReadFile(metaPath); err == nil && len(data) > 0 { + var oldMeta sdkUploadMeta + if json.Unmarshal(data, &oldMeta) == nil { + oldFile := filepath.Base(strings.TrimSpace(oldMeta.Filename)) + if len(oldFile) > 0 && oldFile != meta.Filename { + _ = os.Remove(filepath.Join(baseDir, oldFile)) + } + } + } + + data, err := json.Marshal(meta) + if err != nil { + return err + } + + return saveSDKUploadFile(baseDir, metaFilename, data) +} + func listUploadedSDKFiles() []map[string]interface{} { type item struct { Name string @@ -162,45 +224,26 @@ func listUploadedSDKFiles() []map[string]interface{} { UpdatedAt int64 } - byName := map[string]item{} - for _, dir := range sdkUploadSearchDirs() { - entries, err := os.ReadDir(dir) - if err != nil { + items := make([]item, 0) + for _, record := range listSDKUploadMetaRecords() { + stat, err := os.Stat(record.FilePath) + if err != nil || stat.IsDir() { continue } - for _, entry := range entries { - if entry.IsDir() { - continue - } - name := entry.Name() - platform, version, fileType, ok := parseSDKUploadFilename(name) - if !ok { - continue - } - info, statErr := entry.Info() - if statErr != nil { - continue - } - - current := item{ - Name: name, - Platform: platform, - FileType: fileType, - Version: version, - SizeBytes: info.Size(), - UpdatedAt: info.ModTime().Unix(), - } - old, exists := byName[name] - if !exists || current.UpdatedAt >= old.UpdatedAt { - byName[name] = current - } + fileType := "SDK 包" + if record.Meta.FileType == "doc" { + fileType = "集成文档" } - } - items := make([]item, 0, len(byName)) - for _, it := range byName { - items = append(items, it) + items = append(items, item{ + Name: filepath.Base(record.FilePath), + Platform: record.Meta.Platform, + FileType: fileType, + Version: record.Meta.Version, + SizeBytes: stat.Size(), + UpdatedAt: stat.ModTime().Unix(), + }) } sort.Slice(items, func(i, j int) bool { @@ -224,55 +267,21 @@ func listUploadedSDKFiles() []map[string]interface{} { return result } -func parseSDKUploadFilename(filename string) (platform string, version string, fileType string, ok bool) { - if !strings.HasPrefix(filename, "httpdns-sdk-") { - return "", "", "", false - } - - ext := "" - switch { - case strings.HasSuffix(filename, ".zip"): - ext = ".zip" - fileType = "SDK包" - case strings.HasSuffix(filename, ".md"): - ext = ".md" - fileType = "集成文档" - default: - return "", "", "", false - } - - main := strings.TrimSuffix(strings.TrimPrefix(filename, "httpdns-sdk-"), ext) - version = "" - if idx := strings.Index(main, "-v"); idx > 0 && idx+2 < len(main) { - version = main[idx+2:] - main = main[:idx] - } - - main = strings.ToLower(strings.TrimSpace(main)) - switch main { - case "android", "ios", "flutter": - platform = main - if len(version) == 0 { - version = "-" - } - return platform, version, fileType, true - default: - return "", "", "", false - } -} - func formatSDKFileSize(size int64) string { if size < 1024 { return strconv.FormatInt(size, 10) + " B" } + sizeKB := float64(size) / 1024 if sizeKB < 1024 { return strconv.FormatFloat(sizeKB, 'f', 1, 64) + " KB" } + sizeMB := sizeKB / 1024 if sizeMB < 1024 { return strconv.FormatFloat(sizeMB, 'f', 1, 64) + " MB" } + sizeGB := sizeMB / 1024 return strconv.FormatFloat(sizeGB, 'f', 1, 64) + " GB" } diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk_upload_delete.go b/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk_upload_delete.go index 0e39379..4809474 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk_upload_delete.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/apps/sdk_upload_delete.go @@ -1,6 +1,7 @@ package apps import ( + "encoding/json" "os" "path/filepath" "strings" @@ -34,19 +35,16 @@ func (this *SdkUploadDeleteAction) RunPost(params struct { this.Fail("文件名不合法") return } - if !strings.HasPrefix(filename, "httpdns-sdk-") { - this.Fail("不允许删除该文件") - return - } - if !(strings.HasSuffix(filename, ".zip") || strings.HasSuffix(filename, ".md")) { - this.Fail("不允许删除该文件") + lowName := strings.ToLower(filename) + if !strings.HasSuffix(lowName, ".zip") && !strings.HasSuffix(lowName, ".md") { + this.Fail("仅允许删除 .zip 或 .md 文件") return } for _, dir := range sdkUploadDirs() { fullPath := filepath.Join(dir, filename) - _, err := os.Stat(fullPath) - if err != nil { + stat, err := os.Stat(fullPath) + if err != nil || stat.IsDir() { continue } if err = os.Remove(fullPath); err != nil { @@ -55,5 +53,38 @@ func (this *SdkUploadDeleteAction) RunPost(params struct { } } + // 删除引用该文件的元数据 + for _, dir := range sdkUploadDirs() { + entries, err := os.ReadDir(dir) + if err != nil { + continue + } + + for _, entry := range entries { + if entry.IsDir() { + continue + } + if !isSDKUploadMetaFile(entry.Name()) { + continue + } + + metaPath := filepath.Join(dir, entry.Name()) + data, err := os.ReadFile(metaPath) + if err != nil || len(data) == 0 { + continue + } + + var meta sdkUploadMeta + if err = json.Unmarshal(data, &meta); err != nil { + continue + } + if filepath.Base(strings.TrimSpace(meta.Filename)) != filename { + continue + } + + _ = os.Remove(metaPath) + } + } + this.Success() } diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/cluster/node/index.go b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/cluster/node/index.go index 5e203f5..d34840b 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/cluster/node/index.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/cluster/node/index.go @@ -1,9 +1,11 @@ package node import ( + "strings" "time" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" timeutil "github.com/iwind/TeaGo/utils/time" ) @@ -50,7 +52,22 @@ func (this *IndexAction) RunGet(params struct { this.Data["nodeDatetime"] = nodeDatetime this.Data["nodeTimeDiff"] = nodeTimeDiff - this.Data["shouldUpgrade"] = false - this.Data["newVersion"] = "" + osName := strings.TrimSpace(status.GetString("os")) + if len(osName) > 0 { + checkVersionResp, err := this.RPC().HTTPDNSNodeRPC().CheckHTTPDNSNodeLatestVersion(this.AdminContext(), &pb.CheckHTTPDNSNodeLatestVersionRequest{ + Os: osName, + Arch: strings.TrimSpace(status.GetString("arch")), + CurrentVersion: strings.TrimSpace(status.GetString("buildVersion")), + }) + if err != nil { + this.ErrorPage(err) + return + } + this.Data["shouldUpgrade"] = checkVersionResp.GetHasNewVersion() + this.Data["newVersion"] = checkVersionResp.GetNewVersion() + } else { + this.Data["shouldUpgrade"] = false + this.Data["newVersion"] = "" + } this.Show() } diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/cluster/node/rpc_helpers.go b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/cluster/node/rpc_helpers.go index 2f1d6e5..2694746 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/cluster/node/rpc_helpers.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/cluster/node/rpc_helpers.go @@ -22,14 +22,14 @@ func findHTTPDNSClusterMap(parent *actionutils.ParentAction, clusterID int64) (m return maps.Map{ "id": clusterID, "name": "", - "installDir": "/opt/edge-httpdns", + "installDir": "/root/edge-httpdns", }, nil } cluster := resp.GetCluster() installDir := strings.TrimSpace(cluster.GetInstallDir()) if len(installDir) == 0 { - installDir = "/opt/edge-httpdns" + installDir = "/root/edge-httpdns" } return maps.Map{ "id": cluster.GetId(), @@ -93,7 +93,7 @@ func findHTTPDNSNodeMap(parent *actionutils.ParentAction, nodeID int64) (maps.Ma installDir := strings.TrimSpace(node.GetInstallDir()) if len(installDir) == 0 { - installDir = "/opt/edge-httpdns" + installDir = "/root/edge-httpdns" } clusterMap, err := findHTTPDNSClusterMap(parent, node.GetClusterId()) @@ -137,22 +137,22 @@ func findHTTPDNSNodeMap(parent *actionutils.ParentAction, nodeID int64) (maps.Ma } return maps.Map{ - "id": node.GetId(), - "clusterId": node.GetClusterId(), - "name": node.GetName(), - "isOn": node.GetIsOn(), - "isUp": node.GetIsUp(), - "isInstalled": node.GetIsInstalled(), - "isActive": node.GetIsActive(), - "uniqueId": node.GetUniqueId(), - "secret": node.GetSecret(), - "installDir": installDir, - "status": statusMap, + "id": node.GetId(), + "clusterId": node.GetClusterId(), + "name": node.GetName(), + "isOn": node.GetIsOn(), + "isUp": node.GetIsUp(), + "isInstalled": node.GetIsInstalled(), + "isActive": node.GetIsActive(), + "uniqueId": node.GetUniqueId(), + "secret": node.GetSecret(), + "installDir": installDir, + "status": statusMap, "installStatus": installStatusMap, - "cluster": clusterMap, - "login": loginMap, - "apiNodeAddrs": []string{}, - "ipAddresses": ipAddresses, + "cluster": clusterMap, + "login": loginMap, + "apiNodeAddrs": []string{}, + "ipAddresses": ipAddresses, }, nil } @@ -165,21 +165,23 @@ func decodeNodeStatus(raw []byte) maps.Map { memText := fmt.Sprintf("%.2f%%", status.MemoryUsage*100) return maps.Map{ - "isActive": status.IsActive, - "updatedAt": status.UpdatedAt, - "hostname": status.Hostname, - "hostIP": status.HostIP, - "cpuUsage": status.CPUUsage, - "cpuUsageText": cpuText, - "memUsage": status.MemoryUsage, - "memUsageText": memText, - "load1m": status.Load1m, - "load5m": status.Load5m, - "load15m": status.Load15m, - "buildVersion": status.BuildVersion, - "cpuPhysicalCount": status.CPUPhysicalCount, - "cpuLogicalCount": status.CPULogicalCount, - "exePath": status.ExePath, + "isActive": status.IsActive, + "updatedAt": status.UpdatedAt, + "os": status.OS, + "arch": status.Arch, + "hostname": status.Hostname, + "hostIP": status.HostIP, + "cpuUsage": status.CPUUsage, + "cpuUsageText": cpuText, + "memUsage": status.MemoryUsage, + "memUsageText": memText, + "load1m": status.Load1m, + "load5m": status.Load5m, + "load15m": status.Load15m, + "buildVersion": status.BuildVersion, + "cpuPhysicalCount": status.CPUPhysicalCount, + "cpuLogicalCount": status.CPULogicalCount, + "exePath": status.ExePath, "apiSuccessPercent": status.APISuccessPercent, "apiAvgCostSeconds": status.APIAvgCostSeconds, } diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/cluster/node/update.go b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/cluster/node/update.go index a6a90d5..43ad6b0 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/cluster/node/update.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/cluster/node/update.go @@ -195,7 +195,7 @@ func (this *UpdateAction) RunPost(params struct { installDir := strings.TrimSpace(node.GetInstallDir()) if len(installDir) == 0 { - installDir = "/opt/edge-httpdns" + installDir = "/root/edge-httpdns" } _, err = this.RPC().HTTPDNSNodeRPC().UpdateHTTPDNSNode(this.AdminContext(), &pb.UpdateHTTPDNSNodeRequest{ diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/clusterSettings.go b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/clusterSettings.go index f8737ed..29acd26 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/clusterSettings.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/clusterSettings.go @@ -1,17 +1,20 @@ -package clusters +package clusters import ( "encoding/json" + "fmt" "strconv" "strings" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils" + "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs" "github.com/iwind/TeaGo/actions" "github.com/iwind/TeaGo/maps" + "google.golang.org/grpc/metadata" ) type ClusterSettingsAction struct { @@ -41,14 +44,15 @@ func (this *ClusterSettingsAction) RunGet(params struct { } settings := maps.Map{ - "name": cluster.GetString("name"), - "gatewayDomain": cluster.GetString("gatewayDomain"), - "cacheTtl": cluster.GetInt("defaultTTL"), - "fallbackTimeout": cluster.GetInt("fallbackTimeout"), - "installDir": cluster.GetString("installDir"), - "isOn": cluster.GetBool("isOn"), - "autoRemoteStart": cluster.GetBool("autoRemoteStart"), - "accessLogIsOn": cluster.GetBool("accessLogIsOn"), + "name": cluster.GetString("name"), + "gatewayDomain": cluster.GetString("gatewayDomain"), + "cacheTtl": cluster.GetInt("defaultTTL"), + "fallbackTimeout": cluster.GetInt("fallbackTimeout"), + "installDir": cluster.GetString("installDir"), + "isOn": cluster.GetBool("isOn"), + "autoRemoteStart": cluster.GetBool("autoRemoteStart"), + "accessLogIsOn": cluster.GetBool("accessLogIsOn"), + "timeZone": cluster.GetString("timeZone"), } if settings.GetInt("cacheTtl") <= 0 { settings["cacheTtl"] = 60 @@ -57,7 +61,10 @@ func (this *ClusterSettingsAction) RunGet(params struct { settings["fallbackTimeout"] = 300 } if len(settings.GetString("installDir")) == 0 { - settings["installDir"] = "/opt/edge-httpdns" + settings["installDir"] = "/root/edge-httpdns" + } + if len(settings.GetString("timeZone")) == 0 { + settings["timeZone"] = "Asia/Shanghai" } listenAddresses := []*serverconfigs.NetworkAddressConfig{ @@ -101,19 +108,29 @@ func (this *ClusterSettingsAction) RunGet(params struct { "listen": listenAddresses, "sslPolicy": sslPolicy, } + + this.Data["timeZoneGroups"] = nodeconfigs.FindAllTimeZoneGroups() + this.Data["timeZoneLocations"] = nodeconfigs.FindAllTimeZoneLocations() + timeZoneStr := settings.GetString("timeZone") + if len(timeZoneStr) == 0 { + timeZoneStr = nodeconfigs.DefaultTimeZoneLocation + } + this.Data["timeZoneLocation"] = nodeconfigs.FindTimeZoneLocation(timeZoneStr) + this.Show() } func (this *ClusterSettingsAction) RunPost(params struct { - ClusterId int64 - Name string - GatewayDomain string - CacheTtl int32 - FallbackTimeout int32 - InstallDir string - IsOn bool + ClusterId int64 + Name string + GatewayDomain string + CacheTtl int32 + FallbackTimeout int32 + InstallDir string + IsOn bool AutoRemoteStart bool - AccessLogIsOn bool + AccessLogIsOn bool + TimeZone string Addresses []byte SslPolicyJSON []byte @@ -129,6 +146,31 @@ func (this *ClusterSettingsAction) RunPost(params struct { params.Must.Field("name", params.Name).Require("请输入集群名称") params.Must.Field("gatewayDomain", params.GatewayDomain).Require("请输入服务域名") + cluster, err := findClusterMap(this.Parent(), params.ClusterId) + if err != nil { + this.ErrorPage(err) + return + } + + // 开关项按请求值强制覆盖:未提交/空值都视为 false,支持取消勾选 + autoRemoteStartRaw := strings.ToLower(strings.TrimSpace(this.ParamString("autoRemoteStart"))) + params.AutoRemoteStart = autoRemoteStartRaw == "1" || autoRemoteStartRaw == "true" || autoRemoteStartRaw == "on" || autoRemoteStartRaw == "yes" || autoRemoteStartRaw == "enabled" + + accessLogIsOnRaw := strings.ToLower(strings.TrimSpace(this.ParamString("accessLogIsOn"))) + params.AccessLogIsOn = accessLogIsOnRaw == "1" || accessLogIsOnRaw == "true" || accessLogIsOnRaw == "on" || accessLogIsOnRaw == "yes" || accessLogIsOnRaw == "enabled" + + isOnRaw := strings.ToLower(strings.TrimSpace(this.ParamString("isOn"))) + params.IsOn = isOnRaw == "1" || isOnRaw == "true" || isOnRaw == "on" || isOnRaw == "yes" || isOnRaw == "enabled" + + // 时区为空时继承当前值,再兜底默认值 + params.TimeZone = strings.TrimSpace(this.ParamString("timeZone")) + if len(params.TimeZone) == 0 { + params.TimeZone = strings.TrimSpace(cluster.GetString("timeZone")) + } + if len(params.TimeZone) == 0 { + params.TimeZone = "Asia/Shanghai" + } + if params.CacheTtl <= 0 { params.CacheTtl = 60 } @@ -136,20 +178,13 @@ func (this *ClusterSettingsAction) RunPost(params struct { params.FallbackTimeout = 300 } if len(params.InstallDir) == 0 { - params.InstallDir = "/opt/edge-httpdns" - } - - cluster, err := findClusterMap(this.Parent(), params.ClusterId) - if err != nil { - this.ErrorPage(err) - return + params.InstallDir = "/root/edge-httpdns" } tlsConfig := maps.Map{} if rawTLS := strings.TrimSpace(cluster.GetString("tlsPolicyJSON")); len(rawTLS) > 0 { _ = json.Unmarshal([]byte(rawTLS), &tlsConfig) } - if len(params.Addresses) > 0 { var addresses []*serverconfigs.NetworkAddressConfig if err := json.Unmarshal(params.Addresses, &addresses); err != nil { @@ -158,7 +193,6 @@ func (this *ClusterSettingsAction) RunPost(params struct { } tlsConfig["listen"] = addresses } - if len(params.SslPolicyJSON) > 0 { sslPolicy := &sslconfigs.SSLPolicy{} if err := json.Unmarshal(params.SslPolicyJSON, sslPolicy); err != nil { @@ -177,7 +211,7 @@ func (this *ClusterSettingsAction) RunPost(params struct { } } - _, err = this.RPC().HTTPDNSClusterRPC().UpdateHTTPDNSCluster(this.AdminContext(), &pb.UpdateHTTPDNSClusterRequest{ + updateReq := &pb.UpdateHTTPDNSClusterRequest{ ClusterId: params.ClusterId, Name: params.Name, ServiceDomain: params.GatewayDomain, @@ -189,7 +223,16 @@ func (this *ClusterSettingsAction) RunPost(params struct { IsDefault: false, AutoRemoteStart: params.AutoRemoteStart, AccessLogIsOn: params.AccessLogIsOn, - }) + TimeZone: params.TimeZone, + } + updateCtx := metadata.AppendToOutgoingContext( + this.AdminContext(), + "x-httpdns-auto-remote-start", fmt.Sprintf("%t", updateReq.GetAutoRemoteStart()), + "x-httpdns-access-log-is-on", fmt.Sprintf("%t", updateReq.GetAccessLogIsOn()), + "x-httpdns-time-zone", updateReq.GetTimeZone(), + ) + + _, err = this.RPC().HTTPDNSClusterRPC().UpdateHTTPDNSCluster(updateCtx, updateReq) if err != nil { this.ErrorPage(err) return diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/create.go b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/create.go index 0823114..23bcd22 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/create.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/create.go @@ -1,6 +1,7 @@ package clusters import ( + "strconv" "strings" "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" @@ -36,7 +37,7 @@ func (this *CreateAction) RunPost(params struct { params.GatewayDomain = strings.TrimSpace(params.GatewayDomain) params.InstallDir = strings.TrimSpace(params.InstallDir) if len(params.InstallDir) == 0 { - params.InstallDir = "/opt/edge-httpdns" + params.InstallDir = "/root/edge-httpdns" } if params.CacheTtl <= 0 { params.CacheTtl = 60 @@ -56,6 +57,9 @@ func (this *CreateAction) RunPost(params struct { InstallDir: params.InstallDir, IsOn: params.IsOn, IsDefault: false, + AutoRemoteStart: true, + AccessLogIsOn: true, + TimeZone: "Asia/Shanghai", }) if err != nil { this.ErrorPage(err) @@ -63,5 +67,12 @@ func (this *CreateAction) RunPost(params struct { } this.Data["clusterId"] = resp.GetClusterId() + + // fallback: if frontend JS doesn't intercept form submit, redirect instead of showing raw JSON + if len(this.Request.Header.Get("X-Requested-With")) == 0 { + this.RedirectURL("/httpdns/clusters/cluster?clusterId=" + strconv.FormatInt(resp.GetClusterId(), 10)) + return + } + this.Success() } diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/createNode.go b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/createNode.go index b34fb45..9b57ca4 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/createNode.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/createNode.go @@ -51,7 +51,7 @@ func (this *CreateNodeAction) RunPost(params struct { params.InstallDir = strings.TrimSpace(cluster.GetString("installDir")) } if len(params.InstallDir) == 0 { - params.InstallDir = "/opt/edge-httpdns" + params.InstallDir = "/root/edge-httpdns" } } diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/init.go b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/init.go index 999e0ab..3c61885 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/init.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/init.go @@ -22,7 +22,9 @@ func init() { // Node level GetPost("/createNode", new(CreateNodeAction)). Post("/deleteNode", new(DeleteNodeAction)). - Get("/upgradeRemote", new(UpgradeRemoteAction)). + GetPost("/cluster/upgradeRemote", new(UpgradeRemoteAction)). + GetPost("/upgradeRemote", new(UpgradeRemoteAction)). + Post("/upgradeStatus", new(UpgradeStatusAction)). GetPost("/updateNodeSSH", new(UpdateNodeSSHAction)). Post("/checkPorts", new(CheckPortsAction)). diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/rpc_helpers.go b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/rpc_helpers.go index 3090532..8a6013d 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/rpc_helpers.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/rpc_helpers.go @@ -9,6 +9,10 @@ import ( "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/iwind/TeaGo/maps" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/status" ) func listClusterMaps(parent *actionutils.ParentAction, keyword string) ([]maps.Map, error) { @@ -37,6 +41,11 @@ func listClusterMaps(parent *actionutils.ParentAction, keyword string) ([]maps.M } } + countUpgradeNodes, err := countUpgradeHTTPDNSNodes(parent, cluster.GetId()) + if err != nil { + return nil, err + } + port := "443" if rawTLS := cluster.GetTlsPolicyJSON(); len(rawTLS) > 0 { tlsConfig := maps.Map{} @@ -56,32 +65,87 @@ func listClusterMaps(parent *actionutils.ParentAction, keyword string) ([]maps.M apiAddress := "https://" + cluster.GetServiceDomain() + ":" + port result = append(result, maps.Map{ - "id": cluster.GetId(), - "name": cluster.GetName(), - "gatewayDomain": cluster.GetServiceDomain(), - "apiAddress": apiAddress, - "defaultTTL": cluster.GetDefaultTTL(), - "fallbackTimeout": cluster.GetFallbackTimeoutMs(), - "installDir": cluster.GetInstallDir(), - "isOn": cluster.GetIsOn(), - "isDefault": cluster.GetIsDefault(), - "countAllNodes": countAllNodes, - "countActiveNodes": countActiveNodes, + "id": cluster.GetId(), + "name": cluster.GetName(), + "gatewayDomain": cluster.GetServiceDomain(), + "apiAddress": apiAddress, + "defaultTTL": cluster.GetDefaultTTL(), + "fallbackTimeout": cluster.GetFallbackTimeoutMs(), + "installDir": cluster.GetInstallDir(), + "timeZone": cluster.GetTimeZone(), + "isOn": cluster.GetIsOn(), + "isDefault": cluster.GetIsDefault(), + "autoRemoteStart": cluster.GetAutoRemoteStart(), + "accessLogIsOn": cluster.GetAccessLogIsOn(), + "tlsPolicyJSON": cluster.GetTlsPolicyJSON(), + "countAllNodes": countAllNodes, + "countActiveNodes": countActiveNodes, + "countUpgradeNodes": countUpgradeNodes, }) } return result, nil } +func countUpgradeHTTPDNSNodes(parent *actionutils.ParentAction, clusterID int64) (int64, error) { + countResp, err := parent.RPC().HTTPDNSNodeRPC().CountAllUpgradeHTTPDNSNodesWithClusterId(parent.AdminContext(), &pb.CountAllUpgradeHTTPDNSNodesWithClusterIdRequest{ + ClusterId: clusterID, + }) + if err == nil { + return countResp.GetCount(), nil + } + + grpcStatus, ok := status.FromError(err) + if !ok || grpcStatus.Code() != codes.Unimplemented { + return 0, err + } + + // Compatibility fallback: old edge-api may not implement countAllUpgradeHTTPDNSNodesWithClusterId yet. + listResp, listErr := parent.RPC().HTTPDNSNodeRPC().FindAllUpgradeHTTPDNSNodesWithClusterId(parent.AdminContext(), &pb.FindAllUpgradeHTTPDNSNodesWithClusterIdRequest{ + ClusterId: clusterID, + }) + if listErr == nil { + return int64(len(listResp.GetNodes())), nil + } + + listStatus, ok := status.FromError(listErr) + if ok && listStatus.Code() == codes.Unimplemented { + // Compatibility fallback: both methods missing on old edge-api, don't block page rendering. + return 0, nil + } + + return 0, listErr +} + func findClusterMap(parent *actionutils.ParentAction, clusterID int64) (maps.Map, error) { if clusterID > 0 { + var headerMD metadata.MD resp, err := parent.RPC().HTTPDNSClusterRPC().FindHTTPDNSCluster(parent.AdminContext(), &pb.FindHTTPDNSClusterRequest{ ClusterId: clusterID, - }) + }, grpc.Header(&headerMD)) if err != nil { return nil, err } if resp.GetCluster() != nil { cluster := resp.GetCluster() + autoRemoteStart := cluster.GetAutoRemoteStart() + accessLogIsOn := cluster.GetAccessLogIsOn() + timeZone := cluster.GetTimeZone() + + // Compatibility fallback: + // Some deployed admin binaries may decode newly-added protobuf fields incorrectly. + // Read values from grpc response headers as a source of truth. + if values := headerMD.Get("x-httpdns-auto-remote-start"); len(values) > 0 { + autoRemoteStart = parseBoolLike(values[0], autoRemoteStart) + } + if values := headerMD.Get("x-httpdns-access-log-is-on"); len(values) > 0 { + accessLogIsOn = parseBoolLike(values[0], accessLogIsOn) + } + if values := headerMD.Get("x-httpdns-time-zone"); len(values) > 0 { + if tz := strings.TrimSpace(values[0]); len(tz) > 0 { + timeZone = tz + } + } + return maps.Map{ "id": cluster.GetId(), "name": cluster.GetName(), @@ -92,8 +156,9 @@ func findClusterMap(parent *actionutils.ParentAction, clusterID int64) (maps.Map "isOn": cluster.GetIsOn(), "isDefault": cluster.GetIsDefault(), "tlsPolicyJSON": cluster.GetTlsPolicyJSON(), - "autoRemoteStart": cluster.GetAutoRemoteStart(), - "accessLogIsOn": cluster.GetAccessLogIsOn(), + "autoRemoteStart": autoRemoteStart, + "accessLogIsOn": accessLogIsOn, + "timeZone": timeZone, }, nil } } @@ -109,12 +174,25 @@ func findClusterMap(parent *actionutils.ParentAction, clusterID int64) (maps.Map "gatewayDomain": "", "defaultTTL": 60, "fallbackTimeout": 300, - "installDir": "/opt/edge-httpdns", + "installDir": "/root/edge-httpdns", + "timeZone": "Asia/Shanghai", }, nil } return clusters[0], nil } +func parseBoolLike(raw string, defaultValue bool) bool { + s := strings.ToLower(strings.TrimSpace(raw)) + switch s { + case "1", "true", "on", "yes", "enabled": + return true + case "0", "false", "off", "no", "disabled": + return false + default: + return defaultValue + } +} + func listNodeMaps(parent *actionutils.ParentAction, clusterID int64) ([]maps.Map, error) { resp, err := parent.RPC().HTTPDNSNodeRPC().ListHTTPDNSNodes(parent.AdminContext(), &pb.ListHTTPDNSNodesRequest{ ClusterId: clusterID, @@ -132,21 +210,21 @@ func listNodeMaps(parent *actionutils.ParentAction, clusterID int64) ([]maps.Map ip = parsed } nodeMap := maps.Map{ - "id": node.GetId(), - "clusterId": node.GetClusterId(), - "name": node.GetName(), - "isOn": node.GetIsOn(), - "isUp": node.GetIsUp(), - "isInstalled": node.GetIsInstalled(), - "isActive": node.GetIsActive(), - "installDir": node.GetInstallDir(), - "uniqueId": node.GetUniqueId(), - "secret": node.GetSecret(), - "status": statusMap, + "id": node.GetId(), + "clusterId": node.GetClusterId(), + "name": node.GetName(), + "isOn": node.GetIsOn(), + "isUp": node.GetIsUp(), + "isInstalled": node.GetIsInstalled(), + "isActive": node.GetIsActive(), + "installDir": node.GetInstallDir(), + "uniqueId": node.GetUniqueId(), + "secret": node.GetSecret(), + "status": statusMap, "installStatus": installStatusMap, - "region": nil, - "login": nil, - "apiNodeAddrs": []string{}, + "region": nil, + "login": nil, + "apiNodeAddrs": []string{}, "cluster": maps.Map{ "id": node.GetClusterId(), "installDir": node.GetInstallDir(), @@ -227,21 +305,21 @@ func decodeNodeStatus(raw []byte) maps.Map { cpuText := fmt.Sprintf("%.2f%%", status.CPUUsage*100) memText := fmt.Sprintf("%.2f%%", status.MemoryUsage*100) return maps.Map{ - "isActive": status.IsActive, - "updatedAt": status.UpdatedAt, - "hostname": status.Hostname, - "hostIP": status.HostIP, - "cpuUsage": status.CPUUsage, - "cpuUsageText": cpuText, - "memUsage": status.MemoryUsage, - "memUsageText": memText, - "load1m": status.Load1m, - "load5m": status.Load5m, - "load15m": status.Load15m, - "buildVersion": status.BuildVersion, + "isActive": status.IsActive, + "updatedAt": status.UpdatedAt, + "hostname": status.Hostname, + "hostIP": status.HostIP, + "cpuUsage": status.CPUUsage, + "cpuUsageText": cpuText, + "memUsage": status.MemoryUsage, + "memUsageText": memText, + "load1m": status.Load1m, + "load5m": status.Load5m, + "load15m": status.Load15m, + "buildVersion": status.BuildVersion, "cpuPhysicalCount": status.CPUPhysicalCount, - "cpuLogicalCount": status.CPULogicalCount, - "exePath": status.ExePath, + "cpuLogicalCount": status.CPULogicalCount, + "exePath": status.ExePath, } } diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/upgradeRemote.go b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/upgradeRemote.go index ba99390..846151f 100644 --- a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/upgradeRemote.go +++ b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/upgradeRemote.go @@ -1,16 +1,108 @@ package clusters -import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" +import ( + "encoding/json" + "strings" + + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/default/httpdns/httpdnsutils" + "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" + "github.com/iwind/TeaGo/actions" + "github.com/iwind/TeaGo/maps" +) type UpgradeRemoteAction struct { actionutils.ParentAction } +func (this *UpgradeRemoteAction) Init() { + this.Nav("httpdns", "cluster", "index") +} + func (this *UpgradeRemoteAction) RunGet(params struct { NodeId int64 ClusterId int64 }) { - this.Data["nodeId"] = params.NodeId + httpdnsutils.AddLeftMenu(this.Parent()) + + cluster, err := findClusterMap(this.Parent(), params.ClusterId) + if err != nil { + this.ErrorPage(err) + return + } + httpdnsutils.AddClusterTabbar(this.Parent(), cluster.GetString("name"), params.ClusterId, "node") + this.Data["clusterId"] = params.ClusterId + this.Data["cluster"] = cluster + + resp, err := this.RPC().HTTPDNSNodeRPC().FindAllUpgradeHTTPDNSNodesWithClusterId(this.AdminContext(), &pb.FindAllUpgradeHTTPDNSNodesWithClusterIdRequest{ + ClusterId: params.ClusterId, + }) + if err != nil { + this.ErrorPage(err) + return + } + + nodes := make([]maps.Map, 0, len(resp.GetNodes())) + for _, upgradeNode := range resp.GetNodes() { + node := upgradeNode.Node + if node == nil { + continue + } + + loginParams := maps.Map{} + if node.GetNodeLogin() != nil && len(node.GetNodeLogin().GetParams()) > 0 { + _ = json.Unmarshal(node.GetNodeLogin().GetParams(), &loginParams) + } + + status := decodeNodeStatus(node.GetStatusJSON()) + accessIP := strings.TrimSpace(status.GetString("hostIP")) + if len(accessIP) == 0 { + accessIP = strings.TrimSpace(node.GetName()) + } + + nodes = append(nodes, maps.Map{ + "id": node.GetId(), + "name": node.GetName(), + "accessIP": accessIP, + "oldVersion": upgradeNode.OldVersion, + "newVersion": upgradeNode.NewVersion, + "login": node.GetNodeLogin(), + "loginParams": loginParams, + "installStatus": decodeUpgradeInstallStatus(node.GetInstallStatusJSON()), + }) + } + this.Data["nodes"] = nodes this.Show() } + +func (this *UpgradeRemoteAction) RunPost(params struct { + NodeId int64 + + Must *actions.Must +}) { + _, err := this.RPC().HTTPDNSNodeRPC().UpgradeHTTPDNSNode(this.AdminContext(), &pb.UpgradeHTTPDNSNodeRequest{ + NodeId: params.NodeId, + }) + if err != nil { + this.ErrorPage(err) + return + } + this.Success() +} + +func decodeUpgradeInstallStatus(raw []byte) maps.Map { + result := maps.Map{ + "isRunning": false, + "isFinished": false, + "isOk": false, + "error": "", + "errorCode": "", + } + if len(raw) == 0 { + return result + } + + _ = json.Unmarshal(raw, &result) + return result +} diff --git a/EdgeAdmin/internal/web/actions/default/httpdns/clusters/upgradeStatus.go b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/upgradeStatus.go new file mode 100644 index 0000000..5635d2e --- /dev/null +++ b/EdgeAdmin/internal/web/actions/default/httpdns/clusters/upgradeStatus.go @@ -0,0 +1,48 @@ +package clusters + +import ( + "encoding/json" + + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" +) + +type UpgradeStatusAction struct { + actionutils.ParentAction +} + +func (this *UpgradeStatusAction) RunPost(params struct { + NodeId int64 +}) { + resp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{ + NodeId: params.NodeId, + }) + if err != nil { + this.ErrorPage(err) + return + } + if resp.GetNode() == nil { + this.Data["status"] = nil + this.Success() + return + } + + this.Data["status"] = decodeUpgradeInstallStatusMap(resp.GetNode().GetInstallStatusJSON()) + this.Success() +} + +func decodeUpgradeInstallStatusMap(raw []byte) map[string]interface{} { + result := map[string]interface{}{ + "isRunning": false, + "isFinished": false, + "isOk": false, + "error": "", + "errorCode": "", + } + if len(raw) == 0 { + return result + } + + _ = json.Unmarshal(raw, &result) + return result +} diff --git a/EdgeAdmin/internal/web/actions/default/settings/settingutils/helper.go b/EdgeAdmin/internal/web/actions/default/settings/settingutils/helper.go index be3299c..083fae6 100644 --- a/EdgeAdmin/internal/web/actions/default/settings/settingutils/helper.go +++ b/EdgeAdmin/internal/web/actions/default/settings/settingutils/helper.go @@ -39,6 +39,7 @@ func (this *Helper) BeforeAction(actionPtr actions.ActionWrapper) (goNext bool) if configloaders.AllowModule(adminId, configloaders.AdminModuleCodeSetting) { tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabAdminServer), "", "/settings/server", "", this.tab == "server") tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabAdminUI), "", "/settings/ui", "", this.tab == "ui") + tabbar.Add("升级设置", "", "/settings/upgrade", "", this.tab == "upgrade") tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabAdminSecuritySettings), "", "/settings/security", "", this.tab == "security") if teaconst.IsPlus { tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabIPLibrary), "", "/settings/ip-library", "", this.tab == "ipLibrary") diff --git a/EdgeAdmin/internal/web/actions/default/settings/settingutils/helper_plus.go b/EdgeAdmin/internal/web/actions/default/settings/settingutils/helper_plus.go index 5f10787..131a55a 100644 --- a/EdgeAdmin/internal/web/actions/default/settings/settingutils/helper_plus.go +++ b/EdgeAdmin/internal/web/actions/default/settings/settingutils/helper_plus.go @@ -42,6 +42,7 @@ func (this *Helper) BeforeAction(actionPtr actions.ActionWrapper) (goNext bool) if teaconst.IsPlus { tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabUserUI), "", "/settings/user-ui", "", this.tab == "userUI") } + tabbar.Add("升级设置", "", "/settings/upgrade", "", this.tab == "upgrade") tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabAdminSecuritySettings), "", "/settings/security", "", this.tab == "security") tabbar.Add(this.Lang(actionPtr, codes.AdminSetting_TabIPLibrary), "", "/settings/ip-library", "", this.tab == "ipLibrary") } diff --git a/EdgeAdmin/internal/web/actions/default/settings/ui/index.go b/EdgeAdmin/internal/web/actions/default/settings/ui/index.go index d2de556..f178c8a 100644 --- a/EdgeAdmin/internal/web/actions/default/settings/ui/index.go +++ b/EdgeAdmin/internal/web/actions/default/settings/ui/index.go @@ -56,9 +56,6 @@ func (this *IndexAction) RunPost(params struct { TimeZone string DnsResolverType string - SupportModuleCDN bool - SupportModuleNS bool - Must *actions.Must CSRF *actionutils.CSRF }) { @@ -93,13 +90,7 @@ func (this *IndexAction) RunPost(params struct { config.DefaultPageSize = 10 } - config.Modules = []userconfigs.UserModule{} - if params.SupportModuleCDN { - config.Modules = append(config.Modules, userconfigs.UserModuleCDN) - } - if params.SupportModuleNS { - config.Modules = append(config.Modules, userconfigs.UserModuleNS) - } + config.Modules = []userconfigs.UserModule{userconfigs.UserModuleCDN, userconfigs.UserModuleNS} // 上传Favicon文件 if params.FaviconFile != nil { diff --git a/EdgeAdmin/internal/web/actions/default/settings/upgrade/dns_helper_default.go b/EdgeAdmin/internal/web/actions/default/settings/upgrade/dns_helper_default.go new file mode 100644 index 0000000..9e0c41a --- /dev/null +++ b/EdgeAdmin/internal/web/actions/default/settings/upgrade/dns_helper_default.go @@ -0,0 +1,23 @@ +//go:build !plus + +package upgrade + +import ( + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/iwind/TeaGo/maps" +) + +// loadDNSUpgradeModules 非Plus版本不支持DNS模块 +func loadDNSUpgradeModules(parent *actionutils.ParentAction) []maps.Map { + return nil +} + +// upgradeDNSNode 非Plus版本不支持DNS节点升级 +func upgradeDNSNode(parent *actionutils.ParentAction, nodeId int64) error { + return nil +} + +// loadDNSNodeStatus 非Plus版本不支持DNS节点状态查询 +func loadDNSNodeStatus(parent *actionutils.ParentAction, nodeIds []int64) []maps.Map { + return nil +} diff --git a/EdgeAdmin/internal/web/actions/default/settings/upgrade/dns_helper_plus.go b/EdgeAdmin/internal/web/actions/default/settings/upgrade/dns_helper_plus.go new file mode 100644 index 0000000..653a840 --- /dev/null +++ b/EdgeAdmin/internal/web/actions/default/settings/upgrade/dns_helper_plus.go @@ -0,0 +1,100 @@ +//go:build plus + +package upgrade + +import ( + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" + "github.com/iwind/TeaGo/maps" +) + +// loadDNSUpgradeModules 加载DNS模块的待升级节点信息 +func loadDNSUpgradeModules(parent *actionutils.ParentAction) []maps.Map { + clustersResp, err := parent.RPC().NSClusterRPC().ListNSClusters(parent.AdminContext(), &pb.ListNSClustersRequest{ + Offset: 0, + Size: 10000, + }) + if err != nil { + return nil + } + + var clusterMaps []maps.Map + for _, cluster := range clustersResp.NsClusters { + nodesResp, err := parent.RPC().NSNodeRPC().FindAllUpgradeNSNodesWithNSClusterId(parent.AdminContext(), &pb.FindAllUpgradeNSNodesWithNSClusterIdRequest{ + NsClusterId: cluster.Id, + }) + if err != nil { + continue + } + var nodeMaps []maps.Map + for _, nodeUpgrade := range nodesResp.Nodes { + if nodeUpgrade.NsNode == nil { + continue + } + installStatusMap := decodeInstallStatusFromPB(nodeUpgrade.NsNode.InstallStatus) + accessIP, login, loginParams := decodeNodeAccessInfo(nodeUpgrade.NsNode.StatusJSON, nodeUpgrade.NsNode.NodeLogin, nodeUpgrade.NsNode.Name) + + nodeMaps = append(nodeMaps, maps.Map{ + "id": nodeUpgrade.NsNode.Id, + "name": nodeUpgrade.NsNode.Name, + "os": nodeUpgrade.Os, + "arch": nodeUpgrade.Arch, + "oldVersion": nodeUpgrade.OldVersion, + "newVersion": nodeUpgrade.NewVersion, + "isOn": nodeUpgrade.NsNode.IsOn, + "isUp": nodeUpgrade.NsNode.IsUp, + "accessIP": accessIP, + "login": login, + "loginParams": loginParams, + "installStatus": installStatusMap, + }) + } + if len(nodeMaps) == 0 { + continue + } + clusterMaps = append(clusterMaps, maps.Map{ + "id": cluster.Id, + "name": cluster.Name, + "nodes": nodeMaps, + "count": len(nodeMaps), + }) + } + return clusterMaps +} + +// upgradeDNSNode 升级DNS节点 +func upgradeDNSNode(parent *actionutils.ParentAction, nodeId int64) error { + _, err := parent.RPC().NSNodeRPC().UpgradeNSNode(parent.AdminContext(), &pb.UpgradeNSNodeRequest{ + NsNodeId: nodeId, + }) + return err +} + +// loadDNSNodeStatus 加载DNS节点安装状态 +func loadDNSNodeStatus(parent *actionutils.ParentAction, nodeIds []int64) []maps.Map { + var result []maps.Map + for _, nodeId := range nodeIds { + resp, err := parent.RPC().NSNodeRPC().FindNSNode(parent.AdminContext(), &pb.FindNSNodeRequest{ + NsNodeId: nodeId, + }) + if err != nil || resp.NsNode == nil { + continue + } + var installStatusMap maps.Map + if resp.NsNode.InstallStatus != nil { + installStatusMap = maps.Map{ + "isRunning": resp.NsNode.InstallStatus.IsRunning, + "isFinished": resp.NsNode.InstallStatus.IsFinished, + "isOk": resp.NsNode.InstallStatus.IsOk, + "error": resp.NsNode.InstallStatus.Error, + "errorCode": resp.NsNode.InstallStatus.ErrorCode, + "updatedAt": resp.NsNode.InstallStatus.UpdatedAt, + } + } + result = append(result, maps.Map{ + "id": nodeId, + "installStatus": installStatusMap, + }) + } + return result +} diff --git a/EdgeAdmin/internal/web/actions/default/settings/upgrade/index.go b/EdgeAdmin/internal/web/actions/default/settings/upgrade/index.go index 66ca047..6997fc8 100644 --- a/EdgeAdmin/internal/web/actions/default/settings/upgrade/index.go +++ b/EdgeAdmin/internal/web/actions/default/settings/upgrade/index.go @@ -1,6 +1,15 @@ package upgrade -import "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" +import ( + "encoding/json" + "strings" + + "github.com/TeaOSLab/EdgeAdmin/internal/configloaders" + teaconst "github.com/TeaOSLab/EdgeAdmin/internal/const" + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" + "github.com/iwind/TeaGo/maps" +) type IndexAction struct { actionutils.ParentAction @@ -11,5 +20,264 @@ func (this *IndexAction) Init() { } func (this *IndexAction) RunGet(params struct{}) { + // 加载升级配置 + config, err := configloaders.LoadUpgradeConfig() + if err != nil { + this.ErrorPage(err) + return + } + this.Data["config"] = config + + // 模块列表 + modules := []maps.Map{} + + // 1. 边缘节点 (EdgeNode) + nodeModule := this.loadEdgeNodeModule() + if nodeModule != nil { + modules = append(modules, nodeModule) + } + + // 2. DNS节点 (EdgeDNS) — 仅Plus版本 + if teaconst.IsPlus { + dnsClusters := loadDNSUpgradeModules(&this.ParentAction) + if len(dnsClusters) > 0 { + totalCount := 0 + for _, c := range dnsClusters { + totalCount += c.GetInt("count") + } + modules = append(modules, maps.Map{ + "name": "DNS节点", + "code": "dns", + "clusters": dnsClusters, + "count": totalCount, + }) + } + } + + // 3. HTTPDNS节点 + httpdnsModule := this.loadHTTPDNSModule() + if httpdnsModule != nil { + modules = append(modules, httpdnsModule) + } + + this.Data["modules"] = modules + this.Show() } + +func (this *IndexAction) RunPost(params struct { + AutoUpgrade bool +}) { + config, err := configloaders.LoadUpgradeConfig() + if err != nil { + this.ErrorPage(err) + return + } + + config.AutoUpgrade = params.AutoUpgrade + + err = configloaders.UpdateUpgradeConfig(config) + if err != nil { + this.ErrorPage(err) + return + } + + this.Success() +} + +// loadEdgeNodeModule 加载边缘节点模块的待升级信息 +func (this *IndexAction) loadEdgeNodeModule() maps.Map { + // 获取所有集群 + clustersResp, err := this.RPC().NodeClusterRPC().ListEnabledNodeClusters(this.AdminContext(), &pb.ListEnabledNodeClustersRequest{ + Offset: 0, + Size: 10000, + }) + if err != nil { + return nil + } + + var clusterMaps []maps.Map + totalCount := 0 + for _, cluster := range clustersResp.NodeClusters { + resp, err := this.RPC().NodeRPC().FindAllUpgradeNodesWithNodeClusterId(this.AdminContext(), &pb.FindAllUpgradeNodesWithNodeClusterIdRequest{ + NodeClusterId: cluster.Id, + }) + if err != nil { + continue + } + if len(resp.Nodes) == 0 { + continue + } + + var nodeMaps []maps.Map + for _, nodeUpgrade := range resp.Nodes { + if nodeUpgrade.Node == nil { + continue + } + installStatusMap := decodeInstallStatusFromPB(nodeUpgrade.Node.InstallStatus) + accessIP, login, loginParams := decodeNodeAccessInfo(nodeUpgrade.Node.StatusJSON, nodeUpgrade.Node.NodeLogin, nodeUpgrade.Node.Name) + + nodeMaps = append(nodeMaps, maps.Map{ + "id": nodeUpgrade.Node.Id, + "name": nodeUpgrade.Node.Name, + "os": nodeUpgrade.Os, + "arch": nodeUpgrade.Arch, + "oldVersion": nodeUpgrade.OldVersion, + "newVersion": nodeUpgrade.NewVersion, + "isOn": nodeUpgrade.Node.IsOn, + "isUp": nodeUpgrade.Node.IsUp, + "accessIP": accessIP, + "login": login, + "loginParams": loginParams, + "installStatus": installStatusMap, + }) + } + totalCount += len(nodeMaps) + clusterMaps = append(clusterMaps, maps.Map{ + "id": cluster.Id, + "name": cluster.Name, + "nodes": nodeMaps, + "count": len(nodeMaps), + }) + } + + if len(clusterMaps) == 0 { + return nil + } + + return maps.Map{ + "name": "边缘节点", + "code": "node", + "clusters": clusterMaps, + "count": totalCount, + } +} + +// loadHTTPDNSModule 加载HTTPDNS模块的待升级信息 +func (this *IndexAction) loadHTTPDNSModule() maps.Map { + clustersResp, err := this.RPC().HTTPDNSClusterRPC().ListHTTPDNSClusters(this.AdminContext(), &pb.ListHTTPDNSClustersRequest{ + Offset: 0, + Size: 10000, + }) + if err != nil { + return nil + } + + var clusterMaps []maps.Map + totalCount := 0 + for _, cluster := range clustersResp.Clusters { + resp, err := this.RPC().HTTPDNSNodeRPC().FindAllUpgradeHTTPDNSNodesWithClusterId(this.AdminContext(), &pb.FindAllUpgradeHTTPDNSNodesWithClusterIdRequest{ + ClusterId: cluster.Id, + }) + if err != nil { + continue + } + if len(resp.Nodes) == 0 { + continue + } + + var nodeMaps []maps.Map + for _, nodeUpgrade := range resp.Nodes { + if nodeUpgrade.Node == nil { + continue + } + installStatusMap := decodeInstallStatusFromJSON(nodeUpgrade.Node.InstallStatusJSON) + accessIP, login, loginParams := decodeNodeAccessInfo(nodeUpgrade.Node.StatusJSON, nodeUpgrade.Node.NodeLogin, nodeUpgrade.Node.Name) + + nodeMaps = append(nodeMaps, maps.Map{ + "id": nodeUpgrade.Node.Id, + "name": nodeUpgrade.Node.Name, + "os": nodeUpgrade.Os, + "arch": nodeUpgrade.Arch, + "oldVersion": nodeUpgrade.OldVersion, + "newVersion": nodeUpgrade.NewVersion, + "isOn": nodeUpgrade.Node.IsOn, + "isUp": nodeUpgrade.Node.IsUp, + "accessIP": accessIP, + "login": login, + "loginParams": loginParams, + "installStatus": installStatusMap, + }) + } + totalCount += len(nodeMaps) + clusterMaps = append(clusterMaps, maps.Map{ + "id": cluster.Id, + "name": cluster.Name, + "nodes": nodeMaps, + "count": len(nodeMaps), + }) + } + + if len(clusterMaps) == 0 { + return nil + } + + return maps.Map{ + "name": "HTTPDNS节点", + "code": "httpdns", + "clusters": clusterMaps, + "count": totalCount, + } +} + +// decodeInstallStatusFromPB 从 protobuf InstallStatus 解码安装状态 +func decodeInstallStatusFromPB(status *pb.NodeInstallStatus) maps.Map { + if status == nil { + return nil + } + // 历史成功状态,在待升级列表中忽略 + if status.IsFinished && status.IsOk { + return nil + } + return maps.Map{ + "isRunning": status.IsRunning, + "isFinished": status.IsFinished, + "isOk": status.IsOk, + "error": status.Error, + "errorCode": status.ErrorCode, + "updatedAt": status.UpdatedAt, + } +} + +// decodeInstallStatusFromJSON 从 JSON 字节解码安装状态 +func decodeInstallStatusFromJSON(raw []byte) maps.Map { + if len(raw) == 0 { + return nil + } + result := maps.Map{} + _ = json.Unmarshal(raw, &result) + + isFinished, _ := result["isFinished"].(bool) + isOk, _ := result["isOk"].(bool) + if isFinished && isOk { + return nil + } + return result +} + +// decodeNodeAccessInfo 从节点状态和登录信息中提取 accessIP、login、loginParams +func decodeNodeAccessInfo(statusJSON []byte, nodeLogin *pb.NodeLogin, nodeName string) (accessIP string, login maps.Map, loginParams maps.Map) { + // 从 statusJSON 中提取 hostIP 作为 accessIP + if len(statusJSON) > 0 { + statusMap := maps.Map{} + _ = json.Unmarshal(statusJSON, &statusMap) + accessIP = strings.TrimSpace(statusMap.GetString("hostIP")) + } + if len(accessIP) == 0 { + accessIP = strings.TrimSpace(nodeName) + } + + // 解码 login 信息 + if nodeLogin != nil { + login = maps.Map{ + "id": nodeLogin.Id, + "name": nodeLogin.Name, + "type": nodeLogin.Type, + } + if len(nodeLogin.Params) > 0 { + loginParams = maps.Map{} + _ = json.Unmarshal(nodeLogin.Params, &loginParams) + } + } + return +} diff --git a/EdgeAdmin/internal/web/actions/default/settings/upgrade/init.go b/EdgeAdmin/internal/web/actions/default/settings/upgrade/init.go index c495cad..322e7cb 100644 --- a/EdgeAdmin/internal/web/actions/default/settings/upgrade/init.go +++ b/EdgeAdmin/internal/web/actions/default/settings/upgrade/init.go @@ -13,7 +13,9 @@ func init() { Helper(helpers.NewUserMustAuth(configloaders.AdminModuleCodeSetting)). Helper(settingutils.NewHelper("upgrade")). Prefix("/settings/upgrade"). - Get("", new(IndexAction)). + GetPost("", new(IndexAction)). + Post("/upgradeNode", new(UpgradeNodeAction)). + Post("/status", new(StatusAction)). EndAll() }) } diff --git a/EdgeAdmin/internal/web/actions/default/settings/upgrade/status.go b/EdgeAdmin/internal/web/actions/default/settings/upgrade/status.go new file mode 100644 index 0000000..67617a6 --- /dev/null +++ b/EdgeAdmin/internal/web/actions/default/settings/upgrade/status.go @@ -0,0 +1,80 @@ +package upgrade + +import ( + "encoding/json" + + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" + "github.com/iwind/TeaGo/maps" +) + +type StatusAction struct { + actionutils.ParentAction +} + +func (this *StatusAction) RunPost(params struct { + NodeIdsJSON []byte // JSON: {"node": [1,2], "dns": [3], "httpdns": [4,5]} +}) { + var nodeIdsMap map[string][]int64 + if len(params.NodeIdsJSON) > 0 { + _ = json.Unmarshal(params.NodeIdsJSON, &nodeIdsMap) + } + + result := maps.Map{} + + // EdgeNode 状态 + if nodeIds, ok := nodeIdsMap["node"]; ok && len(nodeIds) > 0 { + var nodeStatuses []maps.Map + for _, nodeId := range nodeIds { + resp, err := this.RPC().NodeRPC().FindEnabledNode(this.AdminContext(), &pb.FindEnabledNodeRequest{NodeId: nodeId}) + if err != nil || resp.Node == nil { + continue + } + var installStatusMap maps.Map + if resp.Node.InstallStatus != nil { + installStatusMap = maps.Map{ + "isRunning": resp.Node.InstallStatus.IsRunning, + "isFinished": resp.Node.InstallStatus.IsFinished, + "isOk": resp.Node.InstallStatus.IsOk, + "error": resp.Node.InstallStatus.Error, + "errorCode": resp.Node.InstallStatus.ErrorCode, + "updatedAt": resp.Node.InstallStatus.UpdatedAt, + } + } + nodeStatuses = append(nodeStatuses, maps.Map{ + "id": nodeId, + "installStatus": installStatusMap, + }) + } + result["node"] = nodeStatuses + } + + // DNS 状态 + if nodeIds, ok := nodeIdsMap["dns"]; ok && len(nodeIds) > 0 { + result["dns"] = loadDNSNodeStatus(&this.ParentAction, nodeIds) + } + + // HTTPDNS 状态 + if nodeIds, ok := nodeIdsMap["httpdns"]; ok && len(nodeIds) > 0 { + var nodeStatuses []maps.Map + for _, nodeId := range nodeIds { + resp, err := this.RPC().HTTPDNSNodeRPC().FindHTTPDNSNode(this.AdminContext(), &pb.FindHTTPDNSNodeRequest{NodeId: nodeId}) + if err != nil || resp.Node == nil { + continue + } + var installStatusMap maps.Map + if len(resp.Node.InstallStatusJSON) > 0 { + installStatusMap = maps.Map{} + _ = json.Unmarshal(resp.Node.InstallStatusJSON, &installStatusMap) + } + nodeStatuses = append(nodeStatuses, maps.Map{ + "id": nodeId, + "installStatus": installStatusMap, + }) + } + result["httpdns"] = nodeStatuses + } + + this.Data["statuses"] = result + this.Success() +} diff --git a/EdgeAdmin/internal/web/actions/default/settings/upgrade/upgradeNode.go b/EdgeAdmin/internal/web/actions/default/settings/upgrade/upgradeNode.go new file mode 100644 index 0000000..8bd7d78 --- /dev/null +++ b/EdgeAdmin/internal/web/actions/default/settings/upgrade/upgradeNode.go @@ -0,0 +1,150 @@ +package upgrade + +import ( + "github.com/TeaOSLab/EdgeAdmin/internal/web/actions/actionutils" + "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" + "github.com/iwind/TeaGo/maps" +) + +type UpgradeNodeAction struct { + actionutils.ParentAction +} + +func (this *UpgradeNodeAction) RunPost(params struct { + Module string // node, dns, httpdns + Scope string // all, module, cluster, node + ClusterId int64 + NodeId int64 +}) { + switch params.Scope { + case "node": + err := this.upgradeSingleNode(params.Module, params.NodeId) + if err != nil { + this.ErrorPage(err) + return + } + case "cluster": + err := this.upgradeCluster(params.Module, params.ClusterId) + if err != nil { + this.ErrorPage(err) + return + } + case "module": + err := this.upgradeModule(params.Module) + if err != nil { + this.ErrorPage(err) + return + } + case "all": + _ = this.upgradeModule("node") + _ = this.upgradeModule("dns") + _ = this.upgradeModule("httpdns") + } + + this.Success() +} + +func (this *UpgradeNodeAction) upgradeSingleNode(module string, nodeId int64) error { + switch module { + case "node": + _, err := this.RPC().NodeRPC().UpgradeNode(this.AdminContext(), &pb.UpgradeNodeRequest{NodeId: nodeId}) + return err + case "dns": + return upgradeDNSNode(&this.ParentAction, nodeId) + case "httpdns": + _, err := this.RPC().HTTPDNSNodeRPC().UpgradeHTTPDNSNode(this.AdminContext(), &pb.UpgradeHTTPDNSNodeRequest{NodeId: nodeId}) + return err + } + return nil +} + +func (this *UpgradeNodeAction) upgradeCluster(module string, clusterId int64) error { + switch module { + case "node": + resp, err := this.RPC().NodeRPC().FindAllUpgradeNodesWithNodeClusterId(this.AdminContext(), &pb.FindAllUpgradeNodesWithNodeClusterIdRequest{ + NodeClusterId: clusterId, + }) + if err != nil { + return err + } + for _, nodeUpgrade := range resp.Nodes { + if nodeUpgrade.Node == nil { + continue + } + _, _ = this.RPC().NodeRPC().UpgradeNode(this.AdminContext(), &pb.UpgradeNodeRequest{NodeId: nodeUpgrade.Node.Id}) + } + case "dns": + this.upgradeDNSCluster(clusterId) + case "httpdns": + resp, err := this.RPC().HTTPDNSNodeRPC().FindAllUpgradeHTTPDNSNodesWithClusterId(this.AdminContext(), &pb.FindAllUpgradeHTTPDNSNodesWithClusterIdRequest{ + ClusterId: clusterId, + }) + if err != nil { + return err + } + for _, nodeUpgrade := range resp.Nodes { + if nodeUpgrade.Node == nil { + continue + } + _, _ = this.RPC().HTTPDNSNodeRPC().UpgradeHTTPDNSNode(this.AdminContext(), &pb.UpgradeHTTPDNSNodeRequest{NodeId: nodeUpgrade.Node.Id}) + } + } + return nil +} + +func (this *UpgradeNodeAction) upgradeModule(module string) error { + switch module { + case "node": + clustersResp, err := this.RPC().NodeClusterRPC().ListEnabledNodeClusters(this.AdminContext(), &pb.ListEnabledNodeClustersRequest{ + Offset: 0, + Size: 10000, + }) + if err != nil { + return err + } + for _, cluster := range clustersResp.NodeClusters { + _ = this.upgradeCluster("node", cluster.Id) + } + case "dns": + dnsClusters := loadDNSUpgradeModules(&this.ParentAction) + for _, c := range dnsClusters { + this.upgradeDNSClusterFromMap(c) + } + case "httpdns": + clustersResp, err := this.RPC().HTTPDNSClusterRPC().ListHTTPDNSClusters(this.AdminContext(), &pb.ListHTTPDNSClustersRequest{ + Offset: 0, + Size: 10000, + }) + if err != nil { + return err + } + for _, cluster := range clustersResp.Clusters { + _ = this.upgradeCluster("httpdns", cluster.Id) + } + } + return nil +} + +// upgradeDNSCluster 根据集群ID升级DNS节点 +func (this *UpgradeNodeAction) upgradeDNSCluster(clusterId int64) { + dnsClusters := loadDNSUpgradeModules(&this.ParentAction) + for _, c := range dnsClusters { + if c.GetInt64("id") == clusterId { + this.upgradeDNSClusterFromMap(c) + break + } + } +} + +// upgradeDNSClusterFromMap 从maps.Map中提取节点ID并升级 +func (this *UpgradeNodeAction) upgradeDNSClusterFromMap(c maps.Map) { + nodesVal := c.Get("nodes") + if nodeMaps, ok := nodesVal.([]maps.Map); ok { + for _, n := range nodeMaps { + nodeId := n.GetInt64("id") + if nodeId > 0 { + _ = upgradeDNSNode(&this.ParentAction, nodeId) + } + } + } +} diff --git a/EdgeAdmin/internal/web/actions/default/users/createPopup.go b/EdgeAdmin/internal/web/actions/default/users/createPopup.go index 40da64f..54b3ef9 100644 --- a/EdgeAdmin/internal/web/actions/default/users/createPopup.go +++ b/EdgeAdmin/internal/web/actions/default/users/createPopup.go @@ -25,20 +25,16 @@ func (this *CreatePopupAction) Init() { func (this *CreatePopupAction) RunGet(params struct{}) { // 检查是否启用了 HTTPDNS 功能(全局用户注册设置中) var hasHTTPDNSFeature = false - var defaultHttpdnsClusterId int64 = 0 resp, err := this.RPC().SysSettingRPC().ReadSysSetting(this.AdminContext(), &pb.ReadSysSettingRequest{Code: systemconfigs.SettingCodeUserRegisterConfig}) if err == nil && len(resp.ValueJSON) > 0 { var config = userconfigs.DefaultUserRegisterConfig() if json.Unmarshal(resp.ValueJSON, config) == nil { hasHTTPDNSFeature = config.HTTPDNSIsOn - if len(config.HTTPDNSDefaultClusterIds) > 0 { - defaultHttpdnsClusterId = config.HTTPDNSDefaultClusterIds[0] - } } } this.Data["hasHTTPDNSFeature"] = hasHTTPDNSFeature - this.Data["httpdnsClusterId"] = defaultHttpdnsClusterId + this.Data["httpdnsClusterId"] = 0 // 加载所有 HTTPDNS 集群 var httpdnsClusters = []maps.Map{} diff --git a/EdgeAdmin/web/public/js/components.js b/EdgeAdmin/web/public/js/components.js index 11d2339..e333e92 100644 --- a/EdgeAdmin/web/public/js/components.js +++ b/EdgeAdmin/web/public/js/components.js @@ -22046,15 +22046,16 @@ Vue.component("ssl-certs-box", { {{description}}
-
+
  - |   + |    
` }) + Vue.component("ssl-certs-view", { props: ["v-certs"], data: function () { @@ -22518,12 +22519,16 @@ Vue.component("ssl-config-box", { 选择或上传证书后HTTPSTLS服务才能生效。
-   - |   -   -   - |   - +
+ + | + + + +
diff --git a/EdgeAdmin/web/public/js/components.src.js b/EdgeAdmin/web/public/js/components.src.js index 11d2339..e333e92 100644 --- a/EdgeAdmin/web/public/js/components.src.js +++ b/EdgeAdmin/web/public/js/components.src.js @@ -22046,15 +22046,16 @@ Vue.component("ssl-certs-box", { {{description}}
-
+
  - |   + |    
` }) + Vue.component("ssl-certs-view", { props: ["v-certs"], data: function () { @@ -22518,12 +22519,16 @@ Vue.component("ssl-config-box", { 选择或上传证书后HTTPSTLS服务才能生效。
-   - |   -   -   - |   - +
+ + | + + + +
diff --git a/EdgeAdmin/web/public/js/components/server/ssl-certs-box.js b/EdgeAdmin/web/public/js/components/server/ssl-certs-box.js index 04846c0..51ac768 100644 --- a/EdgeAdmin/web/public/js/components/server/ssl-certs-box.js +++ b/EdgeAdmin/web/public/js/components/server/ssl-certs-box.js @@ -160,11 +160,11 @@ Vue.component("ssl-certs-box", { {{description}}
-
+
  - |   + |    
` -}) \ No newline at end of file +}) diff --git a/EdgeAdmin/web/public/js/components/server/ssl-config-box.js b/EdgeAdmin/web/public/js/components/server/ssl-config-box.js index e1cf825..786879b 100644 --- a/EdgeAdmin/web/public/js/components/server/ssl-config-box.js +++ b/EdgeAdmin/web/public/js/components/server/ssl-config-box.js @@ -427,12 +427,16 @@ Vue.component("ssl-config-box", { 选择或上传证书后HTTPSTLS服务才能生效。
-   - |   -   -   - |   - +
+ + | + + + +
diff --git a/EdgeAdmin/web/public/js/sweetalert2/dist/sweetalert2.css b/EdgeAdmin/web/public/js/sweetalert2/dist/sweetalert2.css index 380c5b1..5527431 100644 --- a/EdgeAdmin/web/public/js/sweetalert2/dist/sweetalert2.css +++ b/EdgeAdmin/web/public/js/sweetalert2/dist/sweetalert2.css @@ -7,78 +7,96 @@ background: #fff; box-shadow: 0 0 0.625em #d9d9d9; } + .swal2-popup.swal2-toast .swal2-header { flex-direction: row; } + .swal2-popup.swal2-toast .swal2-title { flex-grow: 1; justify-content: flex-start; margin: 0 0.6em; font-size: 1em; } + .swal2-popup.swal2-toast .swal2-footer { margin: 0.5em 0 0; padding: 0.5em 0 0; font-size: 0.8em; } + .swal2-popup.swal2-toast .swal2-close { position: static; width: 0.8em; height: 0.8em; line-height: 0.8; } + .swal2-popup.swal2-toast .swal2-content { justify-content: flex-start; font-size: 1em; } + .swal2-popup.swal2-toast .swal2-icon { width: 2em; min-width: 2em; height: 2em; margin: 0; } + .swal2-popup.swal2-toast .swal2-icon .swal2-icon-content { display: flex; align-items: center; font-size: 1.8em; font-weight: bold; } -@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { + +@media all and (-ms-high-contrast: none), +(-ms-high-contrast: active) { .swal2-popup.swal2-toast .swal2-icon .swal2-icon-content { font-size: 0.25em; } } + .swal2-popup.swal2-toast .swal2-icon.swal2-success .swal2-success-ring { width: 2em; height: 2em; } + .swal2-popup.swal2-toast .swal2-icon.swal2-error [class^=swal2-x-mark-line] { top: 0.875em; width: 1.375em; } + .swal2-popup.swal2-toast .swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=left] { left: 0.3125em; } + .swal2-popup.swal2-toast .swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=right] { right: 0.3125em; } + .swal2-popup.swal2-toast .swal2-actions { flex-basis: auto !important; width: auto; height: auto; margin: 0 0.3125em; } + .swal2-popup.swal2-toast .swal2-styled { margin: 0 0.3125em; padding: 0.3125em 0.625em; font-size: 1em; } + .swal2-popup.swal2-toast .swal2-styled:focus { box-shadow: 0 0 0 1px #fff, 0 0 0 3px rgba(50, 100, 150, 0.4); } + .swal2-popup.swal2-toast .swal2-success { border-color: #a5dc86; } + .swal2-popup.swal2-toast .swal2-success [class^=swal2-success-circular-line] { position: absolute; width: 1.6em; @@ -86,6 +104,7 @@ transform: rotate(45deg); border-radius: 50%; } + .swal2-popup.swal2-toast .swal2-success [class^=swal2-success-circular-line][class$=left] { top: -0.8em; left: -0.5em; @@ -93,50 +112,60 @@ transform-origin: 2em 2em; border-radius: 4em 0 0 4em; } + .swal2-popup.swal2-toast .swal2-success [class^=swal2-success-circular-line][class$=right] { top: -0.25em; left: 0.9375em; transform-origin: 0 1.5em; border-radius: 0 4em 4em 0; } + .swal2-popup.swal2-toast .swal2-success .swal2-success-ring { width: 2em; height: 2em; } + .swal2-popup.swal2-toast .swal2-success .swal2-success-fix { top: 0; left: 0.4375em; width: 0.4375em; height: 2.6875em; } + .swal2-popup.swal2-toast .swal2-success [class^=swal2-success-line] { height: 0.3125em; } + .swal2-popup.swal2-toast .swal2-success [class^=swal2-success-line][class$=tip] { top: 1.125em; left: 0.1875em; width: 0.75em; } + .swal2-popup.swal2-toast .swal2-success [class^=swal2-success-line][class$=long] { top: 0.9375em; right: 0.1875em; width: 1.375em; } + .swal2-popup.swal2-toast .swal2-success.swal2-icon-show .swal2-success-line-tip { -webkit-animation: swal2-toast-animate-success-line-tip 0.75s; - animation: swal2-toast-animate-success-line-tip 0.75s; + animation: swal2-toast-animate-success-line-tip 0.75s; } + .swal2-popup.swal2-toast .swal2-success.swal2-icon-show .swal2-success-line-long { -webkit-animation: swal2-toast-animate-success-line-long 0.75s; - animation: swal2-toast-animate-success-line-long 0.75s; + animation: swal2-toast-animate-success-line-long 0.75s; } + .swal2-popup.swal2-toast.swal2-show { -webkit-animation: swal2-toast-show 0.5s; - animation: swal2-toast-show 0.5s; + animation: swal2-toast-show 0.5s; } + .swal2-popup.swal2-toast.swal2-hide { -webkit-animation: swal2-toast-hide 0.1s forwards; - animation: swal2-toast-hide 0.1s forwards; + animation: swal2-toast-hide 0.1s forwards; } .swal2-container { @@ -155,86 +184,131 @@ transition: background-color 0.1s; -webkit-overflow-scrolling: touch; } + .swal2-container.swal2-backdrop-show { background: rgba(0, 0, 0, 0.4); } + .swal2-container.swal2-backdrop-hide { background: transparent !important; } + .swal2-container.swal2-top { align-items: flex-start; } -.swal2-container.swal2-top-start, .swal2-container.swal2-top-left { + +.swal2-container.swal2-top-start, +.swal2-container.swal2-top-left { align-items: flex-start; justify-content: flex-start; } -.swal2-container.swal2-top-end, .swal2-container.swal2-top-right { + +.swal2-container.swal2-top-end, +.swal2-container.swal2-top-right { align-items: flex-start; justify-content: flex-end; } + .swal2-container.swal2-center { align-items: center; } -.swal2-container.swal2-center-start, .swal2-container.swal2-center-left { + +.swal2-container.swal2-center-start, +.swal2-container.swal2-center-left { align-items: center; justify-content: flex-start; } -.swal2-container.swal2-center-end, .swal2-container.swal2-center-right { + +.swal2-container.swal2-center-end, +.swal2-container.swal2-center-right { align-items: center; justify-content: flex-end; } + .swal2-container.swal2-bottom { align-items: flex-end; } -.swal2-container.swal2-bottom-start, .swal2-container.swal2-bottom-left { + +.swal2-container.swal2-bottom-start, +.swal2-container.swal2-bottom-left { align-items: flex-end; justify-content: flex-start; } -.swal2-container.swal2-bottom-end, .swal2-container.swal2-bottom-right { + +.swal2-container.swal2-bottom-end, +.swal2-container.swal2-bottom-right { align-items: flex-end; justify-content: flex-end; } -.swal2-container.swal2-bottom > :first-child, .swal2-container.swal2-bottom-start > :first-child, .swal2-container.swal2-bottom-left > :first-child, .swal2-container.swal2-bottom-end > :first-child, .swal2-container.swal2-bottom-right > :first-child { + +.swal2-container.swal2-bottom> :first-child, +.swal2-container.swal2-bottom-start> :first-child, +.swal2-container.swal2-bottom-left> :first-child, +.swal2-container.swal2-bottom-end> :first-child, +.swal2-container.swal2-bottom-right> :first-child { margin-top: auto; } -.swal2-container.swal2-grow-fullscreen > .swal2-modal { + +.swal2-container.swal2-grow-fullscreen>.swal2-modal { display: flex !important; flex: 1; align-self: stretch; justify-content: center; } -.swal2-container.swal2-grow-row > .swal2-modal { + +.swal2-container.swal2-grow-row>.swal2-modal { display: flex !important; flex: 1; align-content: center; justify-content: center; } + .swal2-container.swal2-grow-column { flex: 1; flex-direction: column; } -.swal2-container.swal2-grow-column.swal2-top, .swal2-container.swal2-grow-column.swal2-center, .swal2-container.swal2-grow-column.swal2-bottom { + +.swal2-container.swal2-grow-column.swal2-top, +.swal2-container.swal2-grow-column.swal2-center, +.swal2-container.swal2-grow-column.swal2-bottom { align-items: center; } -.swal2-container.swal2-grow-column.swal2-top-start, .swal2-container.swal2-grow-column.swal2-center-start, .swal2-container.swal2-grow-column.swal2-bottom-start, .swal2-container.swal2-grow-column.swal2-top-left, .swal2-container.swal2-grow-column.swal2-center-left, .swal2-container.swal2-grow-column.swal2-bottom-left { + +.swal2-container.swal2-grow-column.swal2-top-start, +.swal2-container.swal2-grow-column.swal2-center-start, +.swal2-container.swal2-grow-column.swal2-bottom-start, +.swal2-container.swal2-grow-column.swal2-top-left, +.swal2-container.swal2-grow-column.swal2-center-left, +.swal2-container.swal2-grow-column.swal2-bottom-left { align-items: flex-start; } -.swal2-container.swal2-grow-column.swal2-top-end, .swal2-container.swal2-grow-column.swal2-center-end, .swal2-container.swal2-grow-column.swal2-bottom-end, .swal2-container.swal2-grow-column.swal2-top-right, .swal2-container.swal2-grow-column.swal2-center-right, .swal2-container.swal2-grow-column.swal2-bottom-right { + +.swal2-container.swal2-grow-column.swal2-top-end, +.swal2-container.swal2-grow-column.swal2-center-end, +.swal2-container.swal2-grow-column.swal2-bottom-end, +.swal2-container.swal2-grow-column.swal2-top-right, +.swal2-container.swal2-grow-column.swal2-center-right, +.swal2-container.swal2-grow-column.swal2-bottom-right { align-items: flex-end; } -.swal2-container.swal2-grow-column > .swal2-modal { + +.swal2-container.swal2-grow-column>.swal2-modal { display: flex !important; flex: 1; align-content: center; justify-content: center; } + .swal2-container.swal2-no-transition { transition: none !important; } -.swal2-container:not(.swal2-top):not(.swal2-top-start):not(.swal2-top-end):not(.swal2-top-left):not(.swal2-top-right):not(.swal2-center-start):not(.swal2-center-end):not(.swal2-center-left):not(.swal2-center-right):not(.swal2-bottom):not(.swal2-bottom-start):not(.swal2-bottom-end):not(.swal2-bottom-left):not(.swal2-bottom-right):not(.swal2-grow-fullscreen) > .swal2-modal { + +.swal2-container:not(.swal2-top):not(.swal2-top-start):not(.swal2-top-end):not(.swal2-top-left):not(.swal2-top-right):not(.swal2-center-start):not(.swal2-center-end):not(.swal2-center-left):not(.swal2-center-right):not(.swal2-bottom):not(.swal2-bottom-start):not(.swal2-bottom-end):not(.swal2-bottom-left):not(.swal2-bottom-right):not(.swal2-grow-fullscreen)>.swal2-modal { margin: auto; } -@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { + +@media all and (-ms-high-contrast: none), +(-ms-high-contrast: active) { .swal2-container .swal2-modal { margin: 0 !important; } @@ -255,9 +329,11 @@ font-family: inherit; font-size: 1rem; } + .swal2-popup:focus { outline: none; } + .swal2-popup.swal2-loading { overflow-y: hidden; } @@ -290,15 +366,19 @@ width: 100%; margin: 1.25em auto 0; } + .swal2-actions:not(.swal2-loading) .swal2-styled[disabled] { opacity: 0.4; } + .swal2-actions:not(.swal2-loading) .swal2-styled:hover { background-image: linear-gradient(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.1)); } + .swal2-actions:not(.swal2-loading) .swal2-styled:active { background-image: linear-gradient(rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)); } + .swal2-actions.swal2-loading .swal2-styled.swal2-confirm { box-sizing: border-box; width: 2.5em; @@ -306,7 +386,7 @@ margin: 0.46875em; padding: 0; -webkit-animation: swal2-rotate-loading 1.5s linear 0s infinite normal; - animation: swal2-rotate-loading 1.5s linear 0s infinite normal; + animation: swal2-rotate-loading 1.5s linear 0s infinite normal; border: 0.25em solid transparent; border-radius: 100%; border-color: transparent; @@ -314,14 +394,16 @@ color: transparent; cursor: default; -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } + .swal2-actions.swal2-loading .swal2-styled.swal2-cancel { margin-right: 30px; margin-left: 30px; } + .swal2-actions.swal2-loading :not(.swal2-styled).swal2-confirm::after { content: ""; display: inline-block; @@ -329,7 +411,7 @@ height: 15px; margin-left: 5px; -webkit-animation: swal2-rotate-loading 1.5s linear 0s infinite normal; - animation: swal2-rotate-loading 1.5s linear 0s infinite normal; + animation: swal2-rotate-loading 1.5s linear 0s infinite normal; border: 3px solid #999999; border-radius: 50%; border-right-color: transparent; @@ -342,9 +424,11 @@ box-shadow: none; font-weight: 500; } + .swal2-styled:not([disabled]) { cursor: pointer; } + .swal2-styled.swal2-confirm { border: 0; border-radius: 0.25em; @@ -353,6 +437,7 @@ color: #fff; font-size: 1.0625em; } + .swal2-styled.swal2-cancel { border: 0; border-radius: 0.25em; @@ -361,10 +446,12 @@ color: #fff; font-size: 1.0625em; } + .swal2-styled:focus { outline: none; box-shadow: 0 0 0 1px #fff, 0 0 0 3px rgba(50, 100, 150, 0.4); } + .swal2-styled::-moz-focus-inner { border: 0; } @@ -423,11 +510,13 @@ line-height: 1.2; cursor: pointer; } + .swal2-close:hover { transform: none; background: transparent; color: #f27474; } + .swal2-close::-moz-focus-inner { border: 0; } @@ -467,12 +556,14 @@ color: inherit; font-size: 1.125em; } + .swal2-input.swal2-inputerror, .swal2-file.swal2-inputerror, .swal2-textarea.swal2-inputerror { border-color: #f27474 !important; box-shadow: 0 0 2px #f27474 !important; } + .swal2-input:focus, .swal2-file:focus, .swal2-textarea:focus { @@ -480,18 +571,31 @@ outline: none; box-shadow: 0 0 3px #c4e6f5; } -.swal2-input::-webkit-input-placeholder, .swal2-file::-webkit-input-placeholder, .swal2-textarea::-webkit-input-placeholder { + +.swal2-input::-webkit-input-placeholder, +.swal2-file::-webkit-input-placeholder, +.swal2-textarea::-webkit-input-placeholder { color: #cccccc; } -.swal2-input::-moz-placeholder, .swal2-file::-moz-placeholder, .swal2-textarea::-moz-placeholder { + +.swal2-input::-moz-placeholder, +.swal2-file::-moz-placeholder, +.swal2-textarea::-moz-placeholder { color: #cccccc; } -.swal2-input:-ms-input-placeholder, .swal2-file:-ms-input-placeholder, .swal2-textarea:-ms-input-placeholder { + +.swal2-input:-ms-input-placeholder, +.swal2-file:-ms-input-placeholder, +.swal2-textarea:-ms-input-placeholder { color: #cccccc; } -.swal2-input::-ms-input-placeholder, .swal2-file::-ms-input-placeholder, .swal2-textarea::-ms-input-placeholder { + +.swal2-input::-ms-input-placeholder, +.swal2-file::-ms-input-placeholder, +.swal2-textarea::-ms-input-placeholder { color: #cccccc; } + .swal2-input::placeholder, .swal2-file::placeholder, .swal2-textarea::placeholder { @@ -502,15 +606,18 @@ margin: 1em auto; background: #fff; } + .swal2-range input { width: 80%; } + .swal2-range output { width: 20%; color: inherit; font-weight: 600; text-align: center; } + .swal2-range input, .swal2-range output { height: 2.625em; @@ -523,6 +630,7 @@ height: 2.625em; padding: 0 0.75em; } + .swal2-input[type=number] { max-width: 10em; } @@ -553,11 +661,13 @@ background: #fff; color: inherit; } + .swal2-radio label, .swal2-checkbox label { margin: 0 0.6em; font-size: 1.125em; } + .swal2-radio input, .swal2-checkbox input { margin: 0 0.4em; @@ -574,6 +684,7 @@ font-size: 1em; font-weight: 300; } + .swal2-validation-message::before { content: "!"; display: inline-block; @@ -602,23 +713,27 @@ line-height: 5em; cursor: default; -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } + .swal2-icon .swal2-icon-content { display: flex; align-items: center; font-size: 3.75em; } + .swal2-icon.swal2-error { border-color: #f27474; color: #f27474; } + .swal2-icon.swal2-error .swal2-x-mark { position: relative; flex-grow: 1; } + .swal2-icon.swal2-error [class^=swal2-x-mark-line] { display: block; position: absolute; @@ -628,38 +743,47 @@ border-radius: 0.125em; background-color: #f27474; } + .swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=left] { left: 1.0625em; transform: rotate(45deg); } + .swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=right] { right: 1em; transform: rotate(-45deg); } + .swal2-icon.swal2-error.swal2-icon-show { -webkit-animation: swal2-animate-error-icon 0.5s; - animation: swal2-animate-error-icon 0.5s; + animation: swal2-animate-error-icon 0.5s; } + .swal2-icon.swal2-error.swal2-icon-show .swal2-x-mark { -webkit-animation: swal2-animate-error-x-mark 0.5s; - animation: swal2-animate-error-x-mark 0.5s; + animation: swal2-animate-error-x-mark 0.5s; } + .swal2-icon.swal2-warning { border-color: #facea8; color: #f8bb86; } + .swal2-icon.swal2-info { border-color: #9de0f6; color: #3fc3ee; } + .swal2-icon.swal2-question { border-color: #c9dae1; color: #87adbd; } + .swal2-icon.swal2-success { border-color: #a5dc86; color: #a5dc86; } + .swal2-icon.swal2-success [class^=swal2-success-circular-line] { position: absolute; width: 3.75em; @@ -667,6 +791,7 @@ transform: rotate(45deg); border-radius: 50%; } + .swal2-icon.swal2-success [class^=swal2-success-circular-line][class$=left] { top: -0.4375em; left: -2.0635em; @@ -674,6 +799,7 @@ transform-origin: 3.75em 3.75em; border-radius: 7.5em 0 0 7.5em; } + .swal2-icon.swal2-success [class^=swal2-success-circular-line][class$=right] { top: -0.6875em; left: 1.875em; @@ -681,6 +807,7 @@ transform-origin: 0 3.75em; border-radius: 0 7.5em 7.5em 0; } + .swal2-icon.swal2-success .swal2-success-ring { position: absolute; z-index: 2; @@ -692,6 +819,7 @@ border: 0.25em solid rgba(165, 220, 134, 0.3); border-radius: 50%; } + .swal2-icon.swal2-success .swal2-success-fix { position: absolute; z-index: 1; @@ -701,6 +829,7 @@ height: 5.625em; transform: rotate(-45deg); } + .swal2-icon.swal2-success [class^=swal2-success-line] { display: block; position: absolute; @@ -709,29 +838,34 @@ border-radius: 0.125em; background-color: #a5dc86; } + .swal2-icon.swal2-success [class^=swal2-success-line][class$=tip] { top: 2.875em; left: 0.8125em; width: 1.5625em; transform: rotate(45deg); } + .swal2-icon.swal2-success [class^=swal2-success-line][class$=long] { top: 2.375em; right: 0.5em; width: 2.9375em; transform: rotate(-45deg); } + .swal2-icon.swal2-success.swal2-icon-show .swal2-success-line-tip { -webkit-animation: swal2-animate-success-line-tip 0.75s; - animation: swal2-animate-success-line-tip 0.75s; + animation: swal2-animate-success-line-tip 0.75s; } + .swal2-icon.swal2-success.swal2-icon-show .swal2-success-line-long { -webkit-animation: swal2-animate-success-line-long 0.75s; - animation: swal2-animate-success-line-long 0.75s; + animation: swal2-animate-success-line-long 0.75s; } + .swal2-icon.swal2-success.swal2-icon-show .swal2-success-circular-line-right { -webkit-animation: swal2-rotate-success-circular-line 4.25s ease-in; - animation: swal2-rotate-success-circular-line 4.25s ease-in; + animation: swal2-rotate-success-circular-line 4.25s ease-in; } .swal2-progress-steps { @@ -741,10 +875,12 @@ background: inherit; font-weight: 600; } + .swal2-progress-steps li { display: inline-block; position: relative; } + .swal2-progress-steps .swal2-progress-step { z-index: 20; width: 2em; @@ -755,16 +891,20 @@ line-height: 2em; text-align: center; } + .swal2-progress-steps .swal2-progress-step.swal2-active-progress-step { background: #3085d6; } -.swal2-progress-steps .swal2-progress-step.swal2-active-progress-step ~ .swal2-progress-step { + +.swal2-progress-steps .swal2-progress-step.swal2-active-progress-step~.swal2-progress-step { background: #add8e6; color: #fff; } -.swal2-progress-steps .swal2-progress-step.swal2-active-progress-step ~ .swal2-progress-step-line { + +.swal2-progress-steps .swal2-progress-step.swal2-active-progress-step~.swal2-progress-step-line { background: #add8e6; } + .swal2-progress-steps .swal2-progress-step-line { z-index: 10; width: 2.5em; @@ -779,12 +919,12 @@ .swal2-show { -webkit-animation: swal2-show 0.3s; - animation: swal2-show 0.3s; + animation: swal2-show 0.3s; } .swal2-hide { -webkit-animation: swal2-hide 0.15s forwards; - animation: swal2-hide 0.15s forwards; + animation: swal2-hide 0.15s forwards; } .swal2-noanimation { @@ -803,6 +943,7 @@ right: auto; left: 0; } + .swal2-rtl .swal2-timer-progress-bar { right: 0; left: auto; @@ -812,419 +953,509 @@ .swal2-range input { width: 100% !important; } + .swal2-range output { display: none; } } -@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { + +@media all and (-ms-high-contrast: none), +(-ms-high-contrast: active) { .swal2-range input { width: 100% !important; } + .swal2-range output { display: none; } } + @-moz-document url-prefix() { .swal2-close:focus { outline: 2px solid rgba(50, 100, 150, 0.4); } } + @-webkit-keyframes swal2-toast-show { 0% { transform: translateY(-0.625em) rotateZ(2deg); } + 33% { transform: translateY(0) rotateZ(-2deg); } + 66% { transform: translateY(0.3125em) rotateZ(2deg); } + 100% { transform: translateY(0) rotateZ(0deg); } } + @keyframes swal2-toast-show { 0% { transform: translateY(-0.625em) rotateZ(2deg); } + 33% { transform: translateY(0) rotateZ(-2deg); } + 66% { transform: translateY(0.3125em) rotateZ(2deg); } + 100% { transform: translateY(0) rotateZ(0deg); } } + @-webkit-keyframes swal2-toast-hide { 100% { transform: rotateZ(1deg); opacity: 0; } } + @keyframes swal2-toast-hide { 100% { transform: rotateZ(1deg); opacity: 0; } } + @-webkit-keyframes swal2-toast-animate-success-line-tip { 0% { top: 0.5625em; left: 0.0625em; width: 0; } + 54% { top: 0.125em; left: 0.125em; width: 0; } + 70% { top: 0.625em; left: -0.25em; width: 1.625em; } + 84% { top: 1.0625em; left: 0.75em; width: 0.5em; } + 100% { top: 1.125em; left: 0.1875em; width: 0.75em; } } + @keyframes swal2-toast-animate-success-line-tip { 0% { top: 0.5625em; left: 0.0625em; width: 0; } + 54% { top: 0.125em; left: 0.125em; width: 0; } + 70% { top: 0.625em; left: -0.25em; width: 1.625em; } + 84% { top: 1.0625em; left: 0.75em; width: 0.5em; } + 100% { top: 1.125em; left: 0.1875em; width: 0.75em; } } + @-webkit-keyframes swal2-toast-animate-success-line-long { 0% { top: 1.625em; right: 1.375em; width: 0; } + 65% { top: 1.25em; right: 0.9375em; width: 0; } + 84% { top: 0.9375em; right: 0; width: 1.125em; } + 100% { top: 0.9375em; right: 0.1875em; width: 1.375em; } } + @keyframes swal2-toast-animate-success-line-long { 0% { top: 1.625em; right: 1.375em; width: 0; } + 65% { top: 1.25em; right: 0.9375em; width: 0; } + 84% { top: 0.9375em; right: 0; width: 1.125em; } + 100% { top: 0.9375em; right: 0.1875em; width: 1.375em; } } + @-webkit-keyframes swal2-show { 0% { transform: scale(0.7); } + 45% { transform: scale(1.05); } + 80% { transform: scale(0.95); } + 100% { transform: scale(1); } } + @keyframes swal2-show { 0% { transform: scale(0.7); } + 45% { transform: scale(1.05); } + 80% { transform: scale(0.95); } + 100% { transform: scale(1); } } + @-webkit-keyframes swal2-hide { 0% { transform: scale(1); opacity: 1; } + 100% { transform: scale(0.5); opacity: 0; } } + @keyframes swal2-hide { 0% { transform: scale(1); opacity: 1; } + 100% { transform: scale(0.5); opacity: 0; } } + @-webkit-keyframes swal2-animate-success-line-tip { 0% { top: 1.1875em; left: 0.0625em; width: 0; } + 54% { top: 1.0625em; left: 0.125em; width: 0; } + 70% { top: 2.1875em; left: -0.375em; width: 3.125em; } + 84% { top: 3em; left: 1.3125em; width: 1.0625em; } + 100% { top: 2.8125em; left: 0.8125em; width: 1.5625em; } } + @keyframes swal2-animate-success-line-tip { 0% { top: 1.1875em; left: 0.0625em; width: 0; } + 54% { top: 1.0625em; left: 0.125em; width: 0; } + 70% { top: 2.1875em; left: -0.375em; width: 3.125em; } + 84% { top: 3em; left: 1.3125em; width: 1.0625em; } + 100% { top: 2.8125em; left: 0.8125em; width: 1.5625em; } } + @-webkit-keyframes swal2-animate-success-line-long { 0% { top: 3.375em; right: 2.875em; width: 0; } + 65% { top: 3.375em; right: 2.875em; width: 0; } + 84% { top: 2.1875em; right: 0; width: 3.4375em; } + 100% { top: 2.375em; right: 0.5em; width: 2.9375em; } } + @keyframes swal2-animate-success-line-long { 0% { top: 3.375em; right: 2.875em; width: 0; } + 65% { top: 3.375em; right: 2.875em; width: 0; } + 84% { top: 2.1875em; right: 0; width: 3.4375em; } + 100% { top: 2.375em; right: 0.5em; width: 2.9375em; } } + @-webkit-keyframes swal2-rotate-success-circular-line { 0% { transform: rotate(-45deg); } + 5% { transform: rotate(-45deg); } + 12% { transform: rotate(-405deg); } + 100% { transform: rotate(-405deg); } } + @keyframes swal2-rotate-success-circular-line { 0% { transform: rotate(-45deg); } + 5% { transform: rotate(-45deg); } + 12% { transform: rotate(-405deg); } + 100% { transform: rotate(-405deg); } } + @-webkit-keyframes swal2-animate-error-x-mark { 0% { margin-top: 1.625em; transform: scale(0.4); opacity: 0; } + 50% { margin-top: 1.625em; transform: scale(0.4); opacity: 0; } + 80% { margin-top: -0.375em; transform: scale(1.15); } + 100% { margin-top: 0; transform: scale(1); opacity: 1; } } + @keyframes swal2-animate-error-x-mark { 0% { margin-top: 1.625em; transform: scale(0.4); opacity: 0; } + 50% { margin-top: 1.625em; transform: scale(0.4); opacity: 0; } + 80% { margin-top: -0.375em; transform: scale(1.15); } + 100% { margin-top: 0; transform: scale(1); opacity: 1; } } + @-webkit-keyframes swal2-animate-error-icon { 0% { transform: rotateX(100deg); opacity: 0; } + 100% { transform: rotateX(0deg); opacity: 1; } } + @keyframes swal2-animate-error-icon { 0% { transform: rotateX(100deg); opacity: 0; } + 100% { transform: rotateX(0deg); opacity: 1; } } + @-webkit-keyframes swal2-rotate-loading { 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } } + @keyframes swal2-rotate-loading { 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } } + body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown) { overflow: hidden; } + body.swal2-height-auto { height: auto !important; } + body.swal2-no-backdrop .swal2-container { top: auto; right: auto; @@ -1233,64 +1464,85 @@ body.swal2-no-backdrop .swal2-container { max-width: calc(100% - 0.625em * 2); background-color: transparent !important; } -body.swal2-no-backdrop .swal2-container > .swal2-modal { + +body.swal2-no-backdrop .swal2-container>.swal2-modal { box-shadow: 0 0 10px rgba(0, 0, 0, 0.4); } + body.swal2-no-backdrop .swal2-container.swal2-top { top: 0; left: 50%; transform: translateX(-50%); } -body.swal2-no-backdrop .swal2-container.swal2-top-start, body.swal2-no-backdrop .swal2-container.swal2-top-left { + +body.swal2-no-backdrop .swal2-container.swal2-top-start, +body.swal2-no-backdrop .swal2-container.swal2-top-left { top: 0; left: 0; } -body.swal2-no-backdrop .swal2-container.swal2-top-end, body.swal2-no-backdrop .swal2-container.swal2-top-right { + +body.swal2-no-backdrop .swal2-container.swal2-top-end, +body.swal2-no-backdrop .swal2-container.swal2-top-right { top: 0; right: 0; } + body.swal2-no-backdrop .swal2-container.swal2-center { top: 50%; left: 50%; transform: translate(-50%, -50%); } -body.swal2-no-backdrop .swal2-container.swal2-center-start, body.swal2-no-backdrop .swal2-container.swal2-center-left { + +body.swal2-no-backdrop .swal2-container.swal2-center-start, +body.swal2-no-backdrop .swal2-container.swal2-center-left { top: 50%; left: 0; transform: translateY(-50%); } -body.swal2-no-backdrop .swal2-container.swal2-center-end, body.swal2-no-backdrop .swal2-container.swal2-center-right { + +body.swal2-no-backdrop .swal2-container.swal2-center-end, +body.swal2-no-backdrop .swal2-container.swal2-center-right { top: 50%; right: 0; transform: translateY(-50%); } + body.swal2-no-backdrop .swal2-container.swal2-bottom { bottom: 0; left: 50%; transform: translateX(-50%); } -body.swal2-no-backdrop .swal2-container.swal2-bottom-start, body.swal2-no-backdrop .swal2-container.swal2-bottom-left { + +body.swal2-no-backdrop .swal2-container.swal2-bottom-start, +body.swal2-no-backdrop .swal2-container.swal2-bottom-left { bottom: 0; left: 0; } -body.swal2-no-backdrop .swal2-container.swal2-bottom-end, body.swal2-no-backdrop .swal2-container.swal2-bottom-right { + +body.swal2-no-backdrop .swal2-container.swal2-bottom-end, +body.swal2-no-backdrop .swal2-container.swal2-bottom-right { right: 0; bottom: 0; } + @media print { body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown) { overflow-y: scroll !important; } - body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown) > [aria-hidden=true] { + + body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown)>[aria-hidden=true] { display: none; } + body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown) .swal2-container { position: static !important; } } + body.swal2-toast-shown .swal2-container { background-color: transparent; } + body.swal2-toast-shown .swal2-container.swal2-top { top: 0; right: auto; @@ -1298,25 +1550,32 @@ body.swal2-toast-shown .swal2-container.swal2-top { left: 50%; transform: translateX(-50%); } -body.swal2-toast-shown .swal2-container.swal2-top-end, body.swal2-toast-shown .swal2-container.swal2-top-right { + +body.swal2-toast-shown .swal2-container.swal2-top-end, +body.swal2-toast-shown .swal2-container.swal2-top-right { top: 0; right: 0; bottom: auto; left: auto; } -body.swal2-toast-shown .swal2-container.swal2-top-start, body.swal2-toast-shown .swal2-container.swal2-top-left { + +body.swal2-toast-shown .swal2-container.swal2-top-start, +body.swal2-toast-shown .swal2-container.swal2-top-left { top: 0; right: auto; bottom: auto; left: 0; } -body.swal2-toast-shown .swal2-container.swal2-center-start, body.swal2-toast-shown .swal2-container.swal2-center-left { + +body.swal2-toast-shown .swal2-container.swal2-center-start, +body.swal2-toast-shown .swal2-container.swal2-center-left { top: 50%; right: auto; bottom: auto; left: 0; transform: translateY(-50%); } + body.swal2-toast-shown .swal2-container.swal2-center { top: 50%; right: auto; @@ -1324,19 +1583,24 @@ body.swal2-toast-shown .swal2-container.swal2-center { left: 50%; transform: translate(-50%, -50%); } -body.swal2-toast-shown .swal2-container.swal2-center-end, body.swal2-toast-shown .swal2-container.swal2-center-right { + +body.swal2-toast-shown .swal2-container.swal2-center-end, +body.swal2-toast-shown .swal2-container.swal2-center-right { top: 50%; right: 0; bottom: auto; left: auto; transform: translateY(-50%); } -body.swal2-toast-shown .swal2-container.swal2-bottom-start, body.swal2-toast-shown .swal2-container.swal2-bottom-left { + +body.swal2-toast-shown .swal2-container.swal2-bottom-start, +body.swal2-toast-shown .swal2-container.swal2-bottom-left { top: auto; right: auto; bottom: 0; left: 0; } + body.swal2-toast-shown .swal2-container.swal2-bottom { top: auto; right: auto; @@ -1344,30 +1608,37 @@ body.swal2-toast-shown .swal2-container.swal2-bottom { left: 50%; transform: translateX(-50%); } -body.swal2-toast-shown .swal2-container.swal2-bottom-end, body.swal2-toast-shown .swal2-container.swal2-bottom-right { + +body.swal2-toast-shown .swal2-container.swal2-bottom-end, +body.swal2-toast-shown .swal2-container.swal2-bottom-right { top: auto; right: 0; bottom: 0; left: auto; } + body.swal2-toast-column .swal2-toast { flex-direction: column; align-items: stretch; } + body.swal2-toast-column .swal2-toast .swal2-actions { flex: 1; align-self: stretch; height: 2.2em; margin-top: 0.3125em; } + body.swal2-toast-column .swal2-toast .swal2-loading { justify-content: center; } + body.swal2-toast-column .swal2-toast .swal2-input { height: 2em; margin: 0.3125em auto; font-size: 1em; } + body.swal2-toast-column .swal2-toast .swal2-validation-message { font-size: 1em; } \ No newline at end of file diff --git a/EdgeAdmin/web/views/@default/@layout.css b/EdgeAdmin/web/views/@default/@layout.css index 2109f21..66b2498 100644 --- a/EdgeAdmin/web/views/@default/@layout.css +++ b/EdgeAdmin/web/views/@default/@layout.css @@ -7,20 +7,24 @@ overflow-x: hidden; border-right: 1px #ddd solid; } + .left-box .menu { width: 95% !important; } + .left-box .menu .item { line-height: 1.2; position: relative; padding-left: 1em !important; } + .left-box .menu .item .icon { position: absolute; top: 50%; left: 0; margin-top: -0.4em !important; } + .left-box .menu .item.separator { border-bottom: 1px #eee solid !important; padding-top: 0; @@ -28,9 +32,11 @@ margin-top: 0 !important; margin-bottom: 0 !important; } + .left-box .menu .item.on span { border-bottom: 1px #666 dashed; } + .left-box .menu .item.off span var { font-style: normal; background: #db2828; @@ -40,33 +46,42 @@ border-radius: 2px; margin-left: 1em; } + .left-box .menu .item.active { background: rgba(230, 230, 230, 0.35) !important; border-radius: 3px; } + .left-box .menu .header { border-bottom: 1px #ddd solid; padding-left: 0 !important; padding-bottom: 1em !important; } + .left-box::-webkit-scrollbar { width: 4px; } + .left-box.disabled { opacity: 0.1; } + .left-box.tiny { top: 10em; } + .left-box.without-tabbar { top: 3em; } + .left-box.with-menu { top: 8.7em; } + .left-box.without-menu { top: 6em; } + .right-box { position: fixed; top: 7em; @@ -77,47 +92,60 @@ padding-bottom: 2em; overflow-y: auto; } + .right-box h4:first-child { margin-top: 1em; } -.right-box > .comment:first-child { + +.right-box>.comment:first-child { margin-top: 0.5em; } + @media screen and (max-width: 512px) { .right-box { left: 13em; padding-right: 1em; } } + body.expanded .right-box { left: 10em; } + .right-box.tiny { top: 10em; left: 26.5em; } + .right-box::-webkit-scrollbar { width: 4px; } + .right-box.without-tabbar { top: 3em; } + .right-box.with-menu { top: 8.6em; } + .right-box.without-menu { top: 6em; } + .main.without-footer .left-box { bottom: 0.2em; } + .narrow-scrollbar::-webkit-scrollbar { width: 4px; } + .grid.counter-chart { margin-top: 1em !important; margin-left: 0.4em !important; } + .grid.counter-chart .column { margin-bottom: 1em; font-size: 0.85em; @@ -126,203 +154,256 @@ body.expanded .right-box { border: 1px rgba(0, 0, 0, 0.1) solid; border-right: 0; } + .grid.counter-chart .column div.value { margin-top: 1.5em; font-weight: normal; } + .grid.counter-chart .column div.value span { font-size: 1.5em; margin-right: 0.2em; } + .grid.counter-chart .column.with-border { border-right: 1px rgba(0, 0, 0, 0.1) solid; } + .grid.counter-chart h4 { color: grey; position: relative; font-size: 1em; text-align: left; } + .grid.counter-chart h4 a { position: absolute; right: 0.1em; font-size: 1.26em; display: none; } + .grid.counter-chart .column:hover { background: rgba(0, 0, 0, 0.03) !important; } + .grid.counter-chart .column:hover a { display: inline; } + .grid.chart-grid { margin-top: 1em !important; margin-left: 0.4em !important; } + .grid.chart-grid .column { margin-bottom: 1em; border: 1px rgba(0, 0, 0, 0.1) solid; border-right: 0; } + .grid.chart-grid .column .menu { margin-top: -0.6em !important; margin-bottom: -0.6em !important; } + .grid.chart-grid .column h4 span { font-size: 0.8em; color: grey; } + .grid.chart-grid .column.with-border { border-right: 1px rgba(0, 0, 0, 0.1) solid; } + /** 通用 **/ * { scrollbar-color: rgba(0, 0, 0, 0.2) transparent; scrollbar-width: thin; } + .clear { clear: both; } + .hidden { display: none; } + pre { white-space: pre-wrap; } + a.disabled, a.disabled:hover, a.disabled:active, span.disabled { color: #ccc !important; } + a.enabled, span.enabled, span.green { color: #21ba45; } + span.grey, label.grey, p.grey { color: grey !important; } + p.grey { margin-top: 0.8em; } + span.red, pre.red { color: #db2828; } + span.blue { color: #4183c4; } + span.orange { color: #ff851b; } + pre:not(.CodeMirror-line) { font-family: Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif !important; } + tbody { background: transparent; } + .table-box { margin-top: 1em; overflow-x: auto; } + .table-box::-webkit-scrollbar { height: 6px; } + .table.width30 { width: 30em !important; } + .table.width35 { width: 35em !important; } + .table.width40 { width: 40em !important; } + .table.width1024 { width: 1024px !important; } + .table th, .table td { font-size: 0.9em !important; } + .table tr.active td { background: rgba(0, 0, 0, 0.01) !important; } + p.comment, div.comment { color: #959da6; padding-top: 0.4em; font-size: 1em; } + p.comment em, div.comment em { font-style: italic !important; } + .truncate { white-space: nowrap; -ms-text-overflow: ellipsis; overflow: hidden; text-overflow: ellipsis; } + div.margin, p.margin { margin-top: 1em; } + .opacity-mask { opacity: 0.3; } + /** 操作按钮容器 **/ .op.one { width: 4em; } + .op.two { width: 7.4em; } + .op.three { width: 9em; } + .op.four { width: 12em; } + /** 主菜单 **/ .main-menu { width: 8em !important; } + .main-menu .ui.menu { width: 100% !important; } + .main-menu .ui.menu .item.separator { border-top: 1px rgba(0, 0, 0, 0.2) solid; height: 1px; min-height: 0; padding: 0; } + @media screen and (max-width: 512px) { .main-menu { width: auto !important; } + .main-menu .ui.menu { width: 3.6em !important; } + .main-menu .ui.menu .item.separator { display: none; } + .main-menu .ui.menu .item { padding-top: 2em; padding-bottom: 2.4em; } } + .main-menu .ui.labeled.icon.menu .item { font-size: 0.9em; } + .main-menu .ui.menu { padding-bottom: 3em; } + .main-menu .ui.menu .item .subtitle { display: none; } + .main-menu .ui.menu .item.expend .subtitle { display: block; font-size: 10px; @@ -330,20 +411,24 @@ p.margin { margin-top: 0.5em; color: grey; } + @media screen and (max-width: 512px) { .main-menu .ui.menu .item.expend .subtitle { display: none; } } + .main-menu .ui.menu .sub-items .item { padding-left: 2.8em !important; padding-right: 0.4em !important; } + .main-menu .ui.menu .sub-items .item .icon { position: absolute; left: 1.1em; top: 0.93em; } + .main-menu .ui.menu .sub-items .item .label { margin-left: 0; margin-right: 0; @@ -351,53 +436,67 @@ p.margin { padding-right: 0.4em; min-width: 2em; } + @media screen and (max-width: 512px) { .main-menu .ui.menu .sub-items .item { padding-left: 1em !important; } } + .main-menu .ui.menu .sub-items .item.active { background-color: #2185d0 !important; } + /** 扩展UI **/ .field.text { padding: 0.5em; } + .form .fields:not(.inline) .field { margin-bottom: 0.5em !important; } + .form .fields:not(.inline) .field .button { min-width: 5em; } + /** body **/ @keyframes blink { from { opacity: 0.1; } + to { opacity: 0.8; } } + @keyframes rotation { from { transform: rotate(0); } + to { transform: rotate(360deg); } } + body .ui.menu .item .blink { animation: blink 1s infinite; } + body .ui.menu .item:not(:hover) span.rotate { animation: rotation 3s infinite; } + body.expanded .main-menu { display: none; } + body.expanded .main { left: 1em; } + /** 布局相关 */ .top-nav { border-radius: 0 !important; @@ -407,6 +506,7 @@ body.expanded .main { overflow-x: auto; border: 0 !important; } + .top-nav img.avatar { width: 1.6em !important; height: 1.6em !important; @@ -415,44 +515,57 @@ body.expanded .main { border-radius: 0.9em; margin-right: 0.5em !important; } + .top-nav em { font-style: normal; font-size: 0.9em; padding-left: 0.2em; } + .top-nav .item .hover-span span { display: none; } + .top-nav .item:hover .hover-span span { display: inline; } + .top-nav .item.red { color: red !important; } + .top-nav.theme1 { background: #14539A !important; } + .top-nav.theme2 { background: #276AC6 !important; } + .top-nav.theme3 { background: #007D9C !important; } + .top-nav.theme4 { background: #A12568 !important; } + .top-nav.theme5 { background: #1C7947 !important; } + .top-nav.theme6 { background: #1D365D !important; } + .top-nav.theme7 { background: black !important; } + .top-nav::-webkit-scrollbar { height: 2px; } + /** 顶部菜单 **/ .top-secondary-menu { position: fixed; @@ -462,23 +575,28 @@ body.expanded .main { z-index: 100; background: white; } + .top-secondary-menu .menu { margin-top: 0 !important; margin-bottom: 0 !important; border-radius: 0 !important; } + .top-secondary-menu .menu var { font-style: normal; } + .top-secondary-menu .divider { margin-top: 0 !important; margin-bottom: 0 !important; } + @media screen and (max-width: 512px) { .top-secondary-menu { left: 4em; } } + /** 右侧主操作区 **/ .main { position: absolute; @@ -488,84 +606,107 @@ body.expanded .main { padding-right: 0.2em; right: 1em; } + @media screen and (max-width: 512px) { .main { left: 4em; } + .main .main-box { display: block; } } + .main.without-menu { left: 9em; } + .main.without-secondary-menu { top: 2.9em; } + @media screen and (max-width: 512px) { .main.without-menu { left: 4em; } } + .main table td.title { width: 10em; } + .main table td.middle-title { width: 14em; } + .main table td { vertical-align: top; } + .main table td.color-border { border-left: 1px #276ac6 solid !important; } + .main table td.vertical-top { vertical-align: top; } + .main table td.vertical-middle { vertical-align: middle; } + .main table td[colspan="2"] a { font-weight: normal; } + .main table td em { font-weight: normal; font-style: normal; font-size: 0.9em; } + .main table td em.grey { color: grey; } + .main h3 { font-weight: normal; margin-top: 1em !important; position: relative; } + .main h3 span { font-size: 0.8em; } + .main h3 span.label { color: #6435c9; } + .main h3 a { margin-left: 1em; font-size: 14px !important; right: 1em; } + .main h4 { font-weight: normal; } + .main form h4 { margin-top: 0.6em; } + .main td span.small { font-size: 0.8em; } + .main .button.mini { font-size: 0.8em; padding: 0.2em; margin-left: 1em; } + .main-menu { position: fixed; /**top: 1.05em;**/ @@ -574,89 +715,114 @@ body.expanded .main { overflow-y: auto; z-index: 10; } + .main-menu .menu { border: 0 !important; box-shadow: none !important; } + .main-menu.theme1 { background: #14539A !important; } + .main-menu.theme1 .menu { background: #14539A !important; } + .main-menu.theme2 { background: #276AC6 !important; } + .main-menu.theme2 .menu { background: #276AC6 !important; } + .main-menu.theme3 { background: #007D9C !important; } + .main-menu.theme3 .menu { background: #007D9C !important; } + .main-menu.theme4 { background: #A12568 !important; } + .main-menu.theme4 .menu { background: #A12568 !important; } + .main-menu.theme5 { background: #1C7947 !important; } + .main-menu.theme5 .menu { background: #1C7947 !important; } + .main-menu.theme6 { background: #1D365D !important; } + .main-menu.theme6 .menu { background: #1D365D !important; } + .main-menu.theme7 { background: black !important; } + .main-menu.theme7 .menu { background: black !important; } + .main-menu::-webkit-scrollbar { width: 4px; } + .main .tab-menu { margin-top: 0.3em !important; margin-bottom: 0 !important; overflow-x: auto; overflow-y: hidden; } + .main .tab-menu .item { padding: 0 1em !important; } + .main .tab-menu .item var { font-style: normal; } + .main .tab-menu .item span { font-size: 0.8em; padding-left: 0.3em; } + .main .tab-menu .item .icon { margin-left: 0.6em; } + .main .tab-menu .item.active.title { font-weight: normal !important; margin-right: 1em !important; border-radius: 0 !important; } + .main .tab-menu .item:hover { background: #f8f8f9 !important; border-width: 1px; } + .main .tab-menu .item.active:not(.title) { font-weight: normal !important; border: none; border-radius: 0 !important; color: #2185d0 !important; } + .main .tab-menu .item.active:not(.title) .bottom-indicator { border-bottom: 1px #2185d0 solid; position: absolute; @@ -664,6 +830,7 @@ body.expanded .main { right: 0; bottom: 1px; } + .main .tab-menu .item.active:not(.title).icon .bottom-indicator { border-bottom: 1px #2185d0 solid; position: absolute; @@ -671,12 +838,15 @@ body.expanded .main { right: 0.6em; bottom: 1px; } + .main .tab-menu .item.active.blue { font-weight: bold !important; } + .main .tab-menu::-webkit-scrollbar { height: 4px; } + .main .go-top-btn { position: fixed; right: 2.6em; @@ -687,14 +857,17 @@ body.expanded .main { z-index: 999999; background: white; } + /** 右侧文本子菜单 **/ .text.menu { overflow-x: auto; } + .text.menu::-webkit-scrollbar { width: 4px; height: 4px; } + /** 脚部相关样式 **/ #footer { position: fixed; @@ -706,27 +879,34 @@ body.expanded .main { z-index: 10; overflow-x: auto; } + #footer::-webkit-scrollbar { height: 2px; } + #footer a { font-size: 0.9em; } + #footer a form { display: none; } + #footer a:hover span, #footer a:active span { display: none; } + #footer a:hover form, #footer a:active form { display: block; } + #footer form input { padding: 0; margin: 0; } + #footer-outer-box { z-index: 999999; position: fixed; @@ -736,6 +916,7 @@ body.expanded .main { background: rgba(0, 0, 0, 0.8); bottom: 2.6em; } + #footer-outer-box .qrcode { width: 20em; position: absolute; @@ -744,95 +925,119 @@ body.expanded .main { margin-top: -14em; margin-left: -10em; } + #footer-outer-box .qrcode img { width: 100%; } + #footer-outer-box .qrcode a { position: absolute; right: 0.5em; top: 0.5em; } + @media screen and (max-width: 512px) { #footer-outer-box .qrcode { margin-left: 0; left: 3.5em; } } + /** Vue **/ [v-cloak] { display: none !important; } + /** auto complete **/ .autocomplete-box .menu { background: #eee !important; } + .autocomplete-box .menu::-webkit-scrollbar { width: 4px; } + .autocomplete-box .menu .item { border-top: none !important; } + select.auto-width { width: auto !important; } + /** column **/ @media screen and (max-width: 512px) { .column:not(.one) { width: 100% !important; } } + label[for] { cursor: pointer !important; } + label.blue { color: #2185d0 !important; } + /** Menu **/ .first-menu .menu.text { margin-top: 0 !important; margin-bottom: 0 !important; } + .first-menu .divider { margin-top: 0 !important; margin-bottom: 0 !important; } + .second-menu .menu.text { margin-top: 0 !important; margin-bottom: 0 !important; } + .second-menu .menu.text em { font-style: normal; } + .second-menu .divider { margin-top: 0 !important; } + .menu a { outline: none; } + /** var **/ span.olive, var.olive { color: #b5cc18 !important; } + span.dash { border-bottom: 1px dashed grey; } + span.hover:hover { background: #eee; } + var.normal { font-style: normal; } + /** checkbox **/ .checkbox label a, .checkbox label { font-size: 0.9em !important; } + /** page **/ .page { margin-top: 1em; border-left: 1px solid #ddd; } + .page a { display: inline-block; background: #fafafa; @@ -843,86 +1048,109 @@ var.normal { border: 1px solid #ddd; border-left: 0; } + .page a.active { background: #2185d0 !important; color: white; } + .page a:hover { background: #eee; } + .page select { padding-top: 0.3em !important; padding-bottom: 0.3em !important; } + /** popup **/ .swal2-html-container { overflow-x: hidden; } + .swal2-close, .swal2-close:focus { border: 0; } + .swal2-confirm:focus, .swal2-cancel:focus { border: 3px #ddd solid !important; } + .swal2-confirm, .swal2-cancel { border: 3px #fff solid !important; } + .swal2-cancel { margin-left: 2em !important; } + /** 排序 **/ .sortable-ghost { background: #ddd !important; opacity: 0.1; } + .sortable-drag { opacity: 1; } + .icon.handle { cursor: pointer; } + .label.port-label { margin-top: 0.4em !important; margin-bottom: 0.4em !important; display: block; line-height: 1.5; } + .label { word-break: break-all; } + td .label.small { margin-bottom: 0.2em !important; } + td { word-break: break-all; } + .source-code-box .CodeMirror { border: 1px solid #eee; height: auto !important; } + .source-code-box .CodeMirror-vscrollbar { width: 6px; border-radius: 3px !important; } + .source-code-box .CodeMirror-vscrollbar::-webkit-scrollbar-thumb { border-radius: 2px; } + .scroll-box { overflow-y: auto; } + .scroll-box::-webkit-scrollbar { width: 4px; } + input.error { border: 1px #e0b4b4 solid !important; } + textarea.wide-code { font-family: Menlo, Monaco, "Courier New", monospace !important; line-height: 1.6 !important; } + .combo-box .menu { max-height: 17em; overflow-y: auto; @@ -931,7 +1159,66 @@ textarea.wide-code { border-top: 0; z-index: 100; } + .combo-box .menu::-webkit-scrollbar { width: 4px; } -/*# sourceMappingURL=@layout.css.map */ \ No newline at end of file + +/*# sourceMappingURL=@layout.css.map */ +/* Override Primary Button Color for WAF Platform */ +.ui.primary.button, +.ui.primary.buttons .button { + background-color: #0f2c54 !important; + color: #ffffff !important; +} + +.ui.primary.button:hover, +.ui.primary.buttons .button:hover { + background-color: #0a1f3a !important; +} + +.ui.primary.button:focus, +.ui.primary.buttons .button:focus { + background-color: #08192e !important; +} + +.ui.primary.button:active, +.ui.primary.buttons .button:active, +.ui.primary.active.button { + background-color: #050d18 !important; +} + +.text-primary, +.blue { + color: #0f2c54 !important; +} + +/* Override Semantic UI Default Blue */ +.ui.blue.button, +.ui.blue.buttons .button { + background-color: #0f2c54 !important; + color: #ffffff !important; +} + +.ui.blue.button:hover, +.ui.blue.buttons .button:hover { + background-color: #0a1f3a !important; +} + +.ui.basic.blue.button, +.ui.basic.blue.buttons .button { + box-shadow: 0 0 0 1px #0f2c54 inset !important; + color: #0f2c54 !important; +} + +.ui.basic.blue.button:hover, +.ui.basic.blue.buttons .button:hover { + background: transparent !important; + box-shadow: 0 0 0 1px #0a1f3a inset !important; + color: #0a1f3a !important; +} + +.ui.menu .active.item { + border-color: #2185d0 !important; + color: #0f2c54 !important; +} \ No newline at end of file diff --git a/EdgeAdmin/web/views/@default/@layout.html b/EdgeAdmin/web/views/@default/@layout.html index 708bdc6..722a93c 100644 --- a/EdgeAdmin/web/views/@default/@layout.html +++ b/EdgeAdmin/web/views/@default/@layout.html @@ -1,15 +1,16 @@ + {$ htmlEncode .teaTitle} - + {$if eq .teaFaviconFileId 0} - + {$else} - + {$end} - + {$TEA.SEMANTIC} {$TEA.VUE} @@ -20,7 +21,7 @@ window.BRAND_DOCS_SITE = {$ jsonEncode .brandConfig.docsSite}; window.BRAND_DOCS_PREFIX = {$ jsonEncode .brandConfig.docsPathPrefix}; window.BRAND_PRODUCT_NAME = {$ jsonEncode .brandConfig.productName}; - + // 确保 teaName 和 teaVersion 在 Vue 初始化前可用 if (typeof window.TEA === "undefined") { window.TEA = {}; @@ -36,122 +37,152 @@ - - - - + + + + - + - + + -
- - \ No newline at end of file +
diff --git a/EdgeAdmin/web/views/@default/httpdns/clusters/upgradeRemote.html b/EdgeAdmin/web/views/@default/httpdns/clusters/upgradeRemote.html index 3416c5b..be8cc61 100644 --- a/EdgeAdmin/web/views/@default/httpdns/clusters/upgradeRemote.html +++ b/EdgeAdmin/web/views/@default/httpdns/clusters/upgradeRemote.html @@ -1,3 +1,62 @@ -{$layout} +{$layout} -

姝ゅ姛鑳芥殏鏈紑鏀俱€?/p> \ No newline at end of file +

暂时没有需要升级的节点。

+ +
+

+ 所有需要升级的节点 + +

+ + + + + + + + + + + + + + + + + + + + + + + +
+ + 节点名访问IPSSH地址版本变化节点状态操作
+ + + {{node.name}} + [详情] + + {{node.accessIP}} + - + + + {{node.loginParams.host}}:{{node.loginParams.port}} + + 没有设置 + v{{node.oldVersion}} -> v{{node.newVersion}} +
+
升级中...
+
+ 已升级成功 + 升级过程中发生错误:{{node.installStatus.error}} +
+
+ 等待升级 +
+ 升级 + 升级中... + 升级 +
+
diff --git a/EdgeAdmin/web/views/@default/httpdns/clusters/upgradeRemote.js b/EdgeAdmin/web/views/@default/httpdns/clusters/upgradeRemote.js new file mode 100644 index 0000000..ee7c383 --- /dev/null +++ b/EdgeAdmin/web/views/@default/httpdns/clusters/upgradeRemote.js @@ -0,0 +1,147 @@ +Tea.context(function () { + this.isInstalling = false + this.isBatch = false + let installingNode = null + + this.nodes.forEach(function (v) { + v.isChecked = false + }) + + this.$delay(function () { + this.reload() + }) + + let that = this + + this.checkNodes = function (isChecked) { + this.nodes.forEach(function (v) { + v.isChecked = isChecked + }) + } + + this.countCheckedNodes = function () { + return that.nodes.$count(function (k, v) { + return v.isChecked + }) + } + + this.installNode = function (node) { + let that = this + if (this.isBatch) { + installingNode = node + that.isInstalling = true + node.isInstalling = true + + that.$post("$") + .params({ + nodeId: node.id + }) + } else { + teaweb.confirm("确定要开始升级此节点吗?", function () { + installingNode = node + that.isInstalling = true + node.isInstalling = true + + that.$post("$") + .params({ + nodeId: node.id + }) + }) + } + } + + this.installBatch = function () { + let that = this + this.isBatch = true + teaweb.confirm("确定要批量升级选中的节点吗?", function () { + that.installNext() + }) + } + + this.installNext = function () { + let nextNode = this.nodes.$find(function (k, v) { + return v.isChecked + }) + + if (nextNode == null) { + teaweb.success("全部升级成功", function () { + teaweb.reload() + }) + } else { + this.installNode(nextNode) + } + return + } + + this.reload = function () { + let that = this + if (installingNode != null) { + this.$post("/httpdns/clusters/upgradeStatus") + .params({ + nodeId: installingNode.id + }) + .success(function (resp) { + if (resp.data.status != null) { + installingNode.installStatus = resp.data.status + if (installingNode.installStatus.isFinished) { + if (installingNode.installStatus.isOk) { + installingNode.isChecked = false + installingNode = null + if (that.isBatch) { + that.installNext() + } else { + teaweb.success("升级成功", function () { + teaweb.reload() + }) + } + } else { + let nodeId = installingNode.id + let errMsg = installingNode.installStatus.error + that.isInstalling = false + installingNode.isInstalling = false + installingNode = null + + switch (resp.data.status.errorCode) { + case "EMPTY_LOGIN": + case "EMPTY_SSH_HOST": + case "EMPTY_SSH_PORT": + case "EMPTY_GRANT": + teaweb.warn("需要填写SSH登录信息", function () { + teaweb.popup("/httpdns/clusters/updateNodeSSH?nodeId=" + nodeId, { + height: "20em", + callback: function () { + teaweb.reload() + } + }) + }) + return + case "CREATE_ROOT_DIRECTORY_FAILED": + teaweb.warn("创建根目录失败,请检查目录权限或手工创建:" + errMsg) + return + case "INSTALL_HELPER_FAILED": + teaweb.warn("安装助手失败:" + errMsg) + return + case "TEST_FAILED": + teaweb.warn("环境测试失败:" + errMsg) + return + case "RPC_TEST_FAILED": + teaweb.confirm("html:要升级的节点到API服务之间的RPC通讯测试失败,具体错误:" + errMsg + ",
现在修改API信息?", function () { + window.location = "/settings/api" + }) + return + default: + teaweb.warn("升级失败:" + errMsg) + } + } + } + } + }) + .done(function () { + setTimeout(this.reload, 3000) + }) + } else { + setTimeout(this.reload, 3000) + } + } +}) + diff --git a/EdgeAdmin/web/views/@default/ns/clusters/cluster/index.html b/EdgeAdmin/web/views/@default/ns/clusters/cluster/index.html index 57b5f7c..78b0bfd 100644 --- a/EdgeAdmin/web/views/@default/ns/clusters/cluster/index.html +++ b/EdgeAdmin/web/views/@default/ns/clusters/cluster/index.html @@ -52,7 +52,7 @@
{{node.name}} -
+
v{{node.status.version}} -> v{{latestVersion}}
@@ -112,4 +112,4 @@ -
\ No newline at end of file +
diff --git a/EdgeAdmin/web/views/@default/settings/authority/index.html b/EdgeAdmin/web/views/@default/settings/authority/index.html index 0696884..7e50216 100644 --- a/EdgeAdmin/web/views/@default/settings/authority/index.html +++ b/EdgeAdmin/web/views/@default/settings/authority/index.html @@ -97,4 +97,4 @@

数据更新于{{key.updatedTime}}。

-
+ \ No newline at end of file diff --git a/EdgeAdmin/web/views/@default/settings/ui/index.html b/EdgeAdmin/web/views/@default/settings/ui/index.html index bca8794..3c528df 100644 --- a/EdgeAdmin/web/views/@default/settings/ui/index.html +++ b/EdgeAdmin/web/views/@default/settings/ui/index.html @@ -1,45 +1,38 @@ {$layout}
- + - - - - - - - - - - - - - - - - - - - +
产品名称 * - -

可以使用变量${product.name}在网页里展示此名称,建议仅包含英文和数字字符。

-
管理员系统名称 * - -

当前管理系统界面上显示的名称。

-
显示版本号 - -

选中后,在界面中显示系统版本号。

-
定制版本号 - -

定制自己的版本号,留空表示使用系统自带的版本号。

-
显示模块
+ + + + + + + + + + + + + + + @@ -66,25 +60,27 @@ -
产品名称 * - CDN       - 智能DNS -

当前管理系统中可以显示的模块,不能为空。

+ +

可以使用变量${product.name}在网页里展示此名称,建议仅包含英文和数字字符。

管理员系统名称 * + +

当前管理系统界面上显示的名称。

+
显示版本号 + +

选中后,在界面中显示系统版本号。

+
定制版本号 + +

定制自己的版本号,留空表示使用系统自带的版本号。

+
显示财务相关功能 @@ -51,13 +44,14 @@ 浏览器图标
- +
还没有上传。
- +

在浏览器标签栏显示的图标,请使用PNG格式。

Logo
- +
还没有上传。
- +

显示在系统界面上的图标,请使用PNG格式。

+

其他

@@ -94,12 +90,15 @@
@@ -119,5 +118,5 @@
每页显示数 - +

在有分页的地方每页显示数量;不能超过100。

- +
\ No newline at end of file diff --git a/EdgeAdmin/web/views/@default/settings/upgrade/index.html b/EdgeAdmin/web/views/@default/settings/upgrade/index.html index 79a92a0..53ab4c6 100644 --- a/EdgeAdmin/web/views/@default/settings/upgrade/index.html +++ b/EdgeAdmin/web/views/@default/settings/upgrade/index.html @@ -1,3 +1,139 @@ {$layout} -

此功能暂未开放,敬请期待。

\ No newline at end of file +
+ + +
+

自动升级

+ + + + + +
开启自动升级 +
+ + +
+

开启后,边缘节点、DNS节点、HTTPDNS节点每分钟检查新版本并自动下载升级。关闭后节点不会自动升级,但管理员仍可在下方手动升级。

+
+
+ +
+ + +
+

+ 手动升级 + +

+ +
+

暂无需要升级的节点。

+
+ +
+

+
+ + + {{mod.name}} + {{mod.count}}个待升级 +
+ +

+ +
+
+
+
+ + + {{cluster.name}} + {{cluster.count}}个待升级 +
+
+ + +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+
节点名访问IPSSH地址版本变化节点状态操作
+
+ + +
+
+ {{node.name}} + + + {{node.accessIP}} + - + + + {{node.loginParams.host}}:{{node.loginParams.port}} + + 没有设置 + + v{{node.oldVersion}} + 未知 + -> v{{node.newVersion}} + +
+
+ 升级中... +
+
+ + 已升级成功 + + 升级过程中发生错误:{{node.installStatus.error}} +
+
+ 等待升级 +
+ 升级 + 升级中... +
+
+
+
+
+
diff --git a/EdgeAdmin/web/views/@default/settings/upgrade/index.js b/EdgeAdmin/web/views/@default/settings/upgrade/index.js new file mode 100644 index 0000000..3a984c4 --- /dev/null +++ b/EdgeAdmin/web/views/@default/settings/upgrade/index.js @@ -0,0 +1,457 @@ +Tea.context(function () { + let that = this + + // 计算总待升级数 + this.totalUpgradeCount = 0 + this.modules.forEach(function (mod) { + mod.expanded = true + that.totalUpgradeCount += mod.count + if (mod.clusters != null) { + mod.clusters.forEach(function (cluster) { + cluster.expanded = true + if (cluster.nodes != null) { + cluster.nodes.forEach(function (node) { + node.isUpgrading = false + node.isChecked = false + }) + } + }) + } + }) + + // 正在升级的节点ID集合(按模块) + this.upgradingNodeIds = { + "node": [], + "dns": [], + "httpdns": [] + } + + // 启动状态轮询 + this.$delay(function () { + this.pollStatus() + }) + + /** + * 获取节点详情页URL + */ + this.nodeDetailURL = function (moduleCode, clusterId, nodeId) { + switch (moduleCode) { + case "node": + return "/clusters/cluster/node?clusterId=" + clusterId + "&nodeId=" + nodeId + case "dns": + return "/ns/clusters/cluster/node?clusterId=" + clusterId + "&nodeId=" + nodeId + case "httpdns": + return "/httpdns/clusters/cluster/node?clusterId=" + clusterId + "&nodeId=" + nodeId + default: + return "#" + } + } + + /** + * 判断节点升级是否已完成(成功) + */ + this.isNodeUpgradeFinished = function (node) { + return node.installStatus != null && node.installStatus.isFinished && node.installStatus.isOk + } + + /** + * 全选/取消全选 + */ + this.toggleCheckAll = function (cluster) { + if (cluster.nodes == null) return + let allChecked = that.isAllChecked(cluster) + cluster.nodes.forEach(function (node) { + if (!that.isNodeUpgradeFinished(node)) { + node.isChecked = !allChecked + } + }) + } + + /** + * 是否全部选中 + */ + this.isAllChecked = function (cluster) { + if (cluster.nodes == null || cluster.nodes.length == 0) return false + let checkableNodes = cluster.nodes.filter(function (node) { + return !that.isNodeUpgradeFinished(node) + }) + if (checkableNodes.length == 0) return false + return checkableNodes.every(function (node) { return node.isChecked }) + } + + /** + * 计算集群内选中的节点数 + */ + this.countCheckedNodesInCluster = function (cluster) { + if (cluster.nodes == null) return 0 + return cluster.nodes.filter(function (node) { return node.isChecked }).length + } + + /** + * 全部升级 + */ + this.upgradeAll = function () { + var vue = this + teaweb.confirm("确定要升级所有模块的所有待升级节点吗?", function () { + vue.$post("/settings/upgrade/upgradeNode") + .params({ + module: "", + scope: "all", + clusterId: 0, + nodeId: 0 + }) + .success(function () { + // 标记所有节点为升级中 + that.modules.forEach(function (mod) { + if (mod.clusters != null) { + mod.clusters.forEach(function (cluster) { + if (cluster.nodes != null) { + cluster.nodes.forEach(function (node) { + node.installStatus = { + isRunning: true, + isFinished: false, + isOk: false, + error: "", + errorCode: "" + } + node.isUpgrading = true + that.trackNode(mod.code, node.id) + }) + } + }) + } + }) + }) + .fail(function (resp) { + teaweb.warn("升级请求失败:" + resp.message) + }) + }) + } + + /** + * 按模块升级 + */ + this.upgradeModule = function (moduleCode) { + var vue = this + teaweb.confirm("确定要升级该模块的所有待升级节点吗?", function () { + vue.$post("/settings/upgrade/upgradeNode") + .params({ + module: moduleCode, + scope: "module", + clusterId: 0, + nodeId: 0 + }) + .success(function () { + that.markModuleUpgrading(moduleCode) + }) + .fail(function (resp) { + teaweb.warn("升级请求失败:" + resp.message) + }) + }) + } + + /** + * 按集群升级 + */ + this.upgradeCluster = function (moduleCode, clusterId) { + var vue = this + teaweb.confirm("确定要升级该集群的所有待升级节点吗?", function () { + vue.$post("/settings/upgrade/upgradeNode") + .params({ + module: moduleCode, + scope: "cluster", + clusterId: clusterId, + nodeId: 0 + }) + .success(function () { + that.markClusterUpgrading(moduleCode, clusterId) + }) + .fail(function (resp) { + teaweb.warn("升级请求失败:" + resp.message) + }) + }) + } + + /** + * 批量升级集群内选中的节点 + */ + this.upgradeBatchInCluster = function (moduleCode, cluster) { + if (cluster.nodes == null) return + var checkedNodes = cluster.nodes.filter(function (node) { return node.isChecked }) + if (checkedNodes.length == 0) return + + var vue = this + teaweb.confirm("确定要批量升级选中的 " + checkedNodes.length + " 个节点吗?", function () { + checkedNodes.forEach(function (node) { + node.installStatus = { + isRunning: true, + isFinished: false, + isOk: false, + error: "", + errorCode: "" + } + node.isUpgrading = true + node.isChecked = false + that.trackNode(moduleCode, node.id) + + vue.$post("/settings/upgrade/upgradeNode") + .params({ + module: moduleCode, + scope: "node", + clusterId: 0, + nodeId: node.id + }) + }) + }) + } + + /** + * 升级单个节点 + */ + this.upgradeNode = function (moduleCode, node) { + var vue = this + teaweb.confirm("确定要升级节点 \"" + node.name + "\" 吗?", function () { + node.installStatus = { + isRunning: true, + isFinished: false, + isOk: false, + error: "", + errorCode: "" + } + node.isUpgrading = true + that.trackNode(moduleCode, node.id) + + vue.$post("/settings/upgrade/upgradeNode") + .params({ + module: moduleCode, + scope: "node", + clusterId: 0, + nodeId: node.id + }) + .success(function () { }) + .fail(function (resp) { + node.isUpgrading = false + teaweb.warn("升级请求失败:" + resp.message) + }) + }) + } + + /** + * 标记模块下所有节点为升级中 + */ + this.markModuleUpgrading = function (moduleCode) { + that.modules.forEach(function (mod) { + if (mod.code == moduleCode && mod.clusters != null) { + mod.clusters.forEach(function (cluster) { + if (cluster.nodes != null) { + cluster.nodes.forEach(function (node) { + node.installStatus = { + isRunning: true, + isFinished: false, + isOk: false, + error: "", + errorCode: "" + } + node.isUpgrading = true + that.trackNode(moduleCode, node.id) + }) + } + }) + } + }) + } + + /** + * 标记集群下所有节点为升级中 + */ + this.markClusterUpgrading = function (moduleCode, clusterId) { + that.modules.forEach(function (mod) { + if (mod.code == moduleCode && mod.clusters != null) { + mod.clusters.forEach(function (cluster) { + if (cluster.id == clusterId && cluster.nodes != null) { + cluster.nodes.forEach(function (node) { + node.installStatus = { + isRunning: true, + isFinished: false, + isOk: false, + error: "", + errorCode: "" + } + node.isUpgrading = true + that.trackNode(moduleCode, node.id) + }) + } + }) + } + }) + } + + /** + * 追踪节点升级状态 + */ + this.trackNode = function (moduleCode, nodeId) { + if (that.upgradingNodeIds[moduleCode] == null) { + that.upgradingNodeIds[moduleCode] = [] + } + if (that.upgradingNodeIds[moduleCode].indexOf(nodeId) < 0) { + that.upgradingNodeIds[moduleCode].push(nodeId) + } + } + + /** + * 状态轮询 + */ + this.pollStatus = function () { + var vue = this + + // 检查是否有正在升级的节点 + let hasUpgrading = false + for (let key in that.upgradingNodeIds) { + if (that.upgradingNodeIds[key].length > 0) { + hasUpgrading = true + break + } + } + + if (!hasUpgrading) { + setTimeout(function () { that.pollStatus() }, 5000) + return + } + + vue.$post("/settings/upgrade/status") + .params({ + nodeIdsJSON: JSON.stringify(that.upgradingNodeIds) + }) + .success(function (resp) { + let statuses = resp.data.statuses + if (statuses == null) { + return + } + + // 更新各模块节点状态 + for (let moduleCode in statuses) { + let nodeStatuses = statuses[moduleCode] + if (nodeStatuses == null) { + continue + } + nodeStatuses.forEach(function (ns) { + that.updateNodeStatus(moduleCode, ns.id, ns.installStatus) + }) + } + }) + .done(function () { + setTimeout(function () { that.pollStatus() }, 3000) + }) + } + + /** + * 更新节点安装状态 + */ + this.updateNodeStatus = function (moduleCode, nodeId, installStatus) { + that.modules.forEach(function (mod) { + if (mod.code == moduleCode && mod.clusters != null) { + mod.clusters.forEach(function (cluster) { + if (cluster.nodes != null) { + cluster.nodes.forEach(function (node) { + if (node.id == nodeId && installStatus != null) { + node.installStatus = installStatus + // 升级完成后移除跟踪 + if (installStatus.isFinished) { + node.isUpgrading = false + let idx = that.upgradingNodeIds[moduleCode].indexOf(nodeId) + if (idx >= 0) { + that.upgradingNodeIds[moduleCode].splice(idx, 1) + } + + if (installStatus.isOk) { + // 升级成功,延迟后从列表中移除 + setTimeout(function () { + let nIdx = cluster.nodes.indexOf(node) + if (nIdx >= 0) { + cluster.nodes.splice(nIdx, 1) + cluster.count-- + mod.count-- + that.totalUpgradeCount-- + + if (cluster.count <= 0) { + let cIdx = mod.clusters.indexOf(cluster) + if (cIdx >= 0) { + mod.clusters.splice(cIdx, 1) + } + } + if (mod.count <= 0) { + let mIdx = that.modules.indexOf(mod) + if (mIdx >= 0) { + that.modules.splice(mIdx, 1) + } + } + } + }, 2000) + } else { + // 升级失败,根据 errorCode 给出具体提示 + that.handleUpgradeError(moduleCode, node, installStatus) + } + } + } + }) + } + }) + } + }) + } + + /** + * 处理升级错误,根据 errorCode 提供更友好的提示 + */ + this.handleUpgradeError = function (moduleCode, node, installStatus) { + let errorCode = installStatus.errorCode || "" + let errMsg = installStatus.error || "" + + switch (errorCode) { + case "EMPTY_LOGIN": + case "EMPTY_SSH_HOST": + case "EMPTY_SSH_PORT": + case "EMPTY_GRANT": + // SSH 信息未配置的错误,不弹窗(页面上已有"没有设置"提示) + break + case "CREATE_ROOT_DIRECTORY_FAILED": + teaweb.warn("节点 \"" + node.name + "\" 创建根目录失败:" + errMsg) + break + case "INSTALL_HELPER_FAILED": + teaweb.warn("节点 \"" + node.name + "\" 安装助手失败:" + errMsg) + break + case "TEST_FAILED": + teaweb.warn("节点 \"" + node.name + "\" 环境测试失败:" + errMsg) + break + case "RPC_TEST_FAILED": + teaweb.warn("节点 \"" + node.name + "\" RPC通讯测试失败:" + errMsg) + break + } + } + + /** + * 更新自动升级状态 + */ + this.updateAutoUpgrade = function () { + var vue = this + + // @change 已经翻转了值,先记录新值并立即恢复 + let newValue = that.config.autoUpgrade + that.config.autoUpgrade = !newValue + + let msg = newValue ? "确定要开启自动升级功能吗?开启后节点会自动下载安装新版本。" : "确定要关闭自动升级功能吗?关闭后只能通过这里手动执行升级。" + teaweb.confirm(msg, function () { + vue.$post("/settings/upgrade") + .params({ + autoUpgrade: newValue ? 1 : 0 + }) + .success(function () { + that.config.autoUpgrade = newValue + teaweb.successToast("设置保存成功") + }) + .fail(function (resp) { + teaweb.warn("设置保存失败:" + resp.message) + }) + }) + } +}) diff --git a/EdgeAdmin/web/views/@default/users/createPopup.html b/EdgeAdmin/web/views/@default/users/createPopup.html index 92ed641..63de930 100644 --- a/EdgeAdmin/web/views/@default/users/createPopup.html +++ b/EdgeAdmin/web/views/@default/users/createPopup.html @@ -39,7 +39,7 @@ - HTTPDNS关联集群 + HTTPDNS关联集群