diff --git a/.gitignore b/.gitignore index 0c7f6fd..f9c250d 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ lerna-debug.log* node_modules .DS_Store dist +**/wwwroot dist-ssr coverage *.local diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..8a69752 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "server/src/BsdlParser"] + path = server/src/BsdlParser + url = git@github.com:SikongJueluo/python-bsdl-parser.git diff --git a/.justfile b/.justfile index 7651ade..e71e46d 100644 --- a/.justfile +++ b/.justfile @@ -1,3 +1,5 @@ +isSelfContained := "false" + @_show-dir: echo "Current Working Directory:" pwd @@ -11,6 +13,11 @@ clean: rm -rf "server.test/obj" rm -rf "dist" +update: + npm install + cd server && dotnet restore + git submodule update --init --remote --recursive + # 生成Restful API到网页客户端 gen-api: cd server && dotnet run & @@ -19,16 +26,25 @@ gen-api: # 构建服务器,包含win与linux平台 [working-directory: "server"] -build-server: _show-dir - dotnet publish --self-contained false -t:PublishAllRids +build-server self-contained=isSelfContained: _show-dir + dotnet publish --self-contained {{self-contained}} -t:PublishAllRids + npm run build + rsync -avz --delete ../wwwroot/ ./bin/Release/net9.0/linux-x64/publish/wwwroot/ + rsync -avz --delete ../wwwroot/ ./bin/Release/net9.0/win-x64/publish/wwwroot/ -# 运行服务器 -[working-directory: "server"] -run-server: _show-dir - dotnet run +run-server: (build-server "true") + exec ./server/bin/Release/net9.0/linux-x64/publish/server + +run-web: + npm run build + npm run preview + +# 测试服务器 +dev-server: _show-dir + cd server && dotnet run # 运行网页客户端 -run-web: +dev-web: npm run dev # 运行测试用例测试服务器 diff --git a/package-lock.json b/package-lock.json index 29b917b..519c9a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "@tailwindcss/postcss": "^4.0.12", "@tsconfig/node22": "^22.0.0", "@types/node": "^22.13.4", + "@vitejs/plugin-basic-ssl": "^2.0.0", "@vitejs/plugin-vue": "^5.2.1", "@vitejs/plugin-vue-jsx": "^4.1.1", "@vue/tsconfig": "^0.7.0", @@ -1646,6 +1647,19 @@ "undici-types": "~6.21.0" } }, + "node_modules/@vitejs/plugin-basic-ssl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.0.0.tgz", + "integrity": "sha512-gc9Tjg8bUxBVSTzeWT3Njc0Cl3PakHFKdNfABnZWiUgbxqmHDEn7uECv3fHVylxoYgNzAcmU7ZrILz+BwSo3sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "peerDependencies": { + "vite": "^6.0.0" + } + }, "node_modules/@vitejs/plugin-vue": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.3.tgz", @@ -3785,18 +3799,18 @@ } }, "node_modules/vite": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.2.tgz", - "integrity": "sha512-ZSvGOXKGceizRQIZSz7TGJ0pS3QLlVY/9hwxVh17W3re67je1RKYzFHivZ/t0tubU78Vkyb9WnHPENSBCzbckg==", + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", - "fdir": "^6.4.3", + "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", - "tinyglobby": "^0.2.12" + "tinyglobby": "^0.2.13" }, "bin": { "vite": "bin/vite.js" diff --git a/package.json b/package.json index 238417f..dfc9ea7 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "type": "module", "scripts": { - "dev": "vite", + "dev": "vite --host", "build": "run-p type-check \"build-only {@}\" --", "preview": "vite preview", "build-only": "vite build", @@ -31,6 +31,7 @@ "@tailwindcss/postcss": "^4.0.12", "@tsconfig/node22": "^22.0.0", "@types/node": "^22.13.4", + "@vitejs/plugin-basic-ssl": "^2.0.0", "@vitejs/plugin-vue": "^5.2.1", "@vitejs/plugin-vue-jsx": "^4.1.1", "@vue/tsconfig": "^0.7.0", diff --git a/server/Program.cs b/server/Program.cs index 0b3c10c..fa122ef 100644 --- a/server/Program.cs +++ b/server/Program.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Http.Features; +using Microsoft.Extensions.FileProviders; using Newtonsoft.Json; using NLog; using NLog.Web; @@ -9,7 +10,6 @@ var logger = NLog.LogManager.Setup() .GetCurrentClassLogger(); logger.Debug("Init Main..."); - try { var builder = WebApplication.CreateBuilder(args); @@ -48,6 +48,12 @@ try ); }); } + builder.Services.AddCors(options => + { + options.AddPolicy("Users", policy => policy + .AllowAnyOrigin() + ); + }); // Add Swagger builder.Services.AddControllers(); @@ -78,34 +84,39 @@ try // Application Settings var app = builder.Build(); // Configure the HTTP request pipeline. - // app.UseExceptionHandler(new ExceptionHandlerOptions() - // { - // AllowStatusCode404Response = true, - // ExceptionHandlingPath = "/error" - // }); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); + if (!app.Environment.IsDevelopment()) + { + // app.UseExceptionHandler("/Home/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + + // Serve static files + logger.Info($"Use Static Files : {Path.Combine(Directory.GetCurrentDirectory(), "wwwroot")}"); + app.UseDefaultFiles(); + app.UseStaticFiles(); // Serves files from wwwroot by default + app.UseStaticFiles(new StaticFileOptions + { + FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "assets")), + RequestPath = "/assets" + }); + app.MapFallbackToFile("index.html"); + } app.UseHttpsRedirection(); - app.UseStaticFiles(); app.UseRouting(); app.UseCors(); app.UseAuthorization(); - // if (app.Environment.IsDevelopment()) - // { + // Swagger app.UseOpenApi(); app.UseSwaggerUi(); - // } + + // Router + app.MapControllers(); // Setup Program MsgBus.Init(); - // Router - // API Get - app.MapGet("/", () => Results.Redirect("/swagger")); - app.MapControllers(); - - app.Run("http://localhost:5000"); + app.Run(); } catch (Exception exception) { diff --git a/server/Properties/launchSettings.json b/server/Properties/launchSettings.json index ceb1922..fca3ffd 100644 --- a/server/Properties/launchSettings.json +++ b/server/Properties/launchSettings.json @@ -5,18 +5,20 @@ "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, - "applicationUrl": "http://localhost:5188", + "applicationUrl": "http://localhost:5000", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy" } }, "https": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, - "applicationUrl": "https://localhost:7070;http://localhost:5188", + "applicationUrl": "https://localhost:7278;http://localhost:5000", "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Development", + "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.SpaProxy" } } } diff --git a/server/PublishAllRids.targets b/server/PublishAllRids.xml similarity index 100% rename from server/PublishAllRids.targets rename to server/PublishAllRids.xml diff --git a/server/server.csproj b/server/server.csproj index a6dc9b5..3b2cfa1 100644 --- a/server/server.csproj +++ b/server/server.csproj @@ -1,27 +1,32 @@ - - - - - net9.0 - enable - enable - true - win-x64;linux-x64; - true - - + + + + + net9.0 + enable + enable + true + win-x64;linux-x64; + true + ../ + http://localhost:5173 + npm run dev + + + + - - - + + + diff --git a/server/src/BsdlParser b/server/src/BsdlParser new file mode 160000 index 0000000..ac164eb --- /dev/null +++ b/server/src/BsdlParser @@ -0,0 +1 @@ +Subproject commit ac164eb16d7d9a9a387ff1f62cef249c65565bef diff --git a/server/src/Common.cs b/server/src/Common.cs index 5639ef7..edf3245 100644 --- a/server/src/Common.cs +++ b/server/src/Common.cs @@ -41,6 +41,7 @@ namespace Common 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff }; + /// /// 整数转成二进制字节数组 /// @@ -166,6 +167,26 @@ namespace Common } + /// + /// [TODO:description] + /// + /// [TODO:parameter] + /// [TODO:return] + public static Result UInt32ArrayToBytes(UInt32[] uintArray) + { + byte[] byteArray = new byte[uintArray.Length * 4]; + try + { + Buffer.BlockCopy(uintArray, 0, byteArray, 0, uintArray.Length * 4); + return byteArray; + } + catch (Exception error) + { + return new(error); + } + } + + /// /// 比特合并成二进制字节 diff --git a/server/src/Controllers.cs b/server/src/Controllers.cs index 968ab0a..de626a8 100644 --- a/server/src/Controllers.cs +++ b/server/src/Controllers.cs @@ -8,6 +8,44 @@ using WebProtocol; namespace server.Controllers; +// /// +// /// [TODO:description] +// /// +// public class HomeController : ControllerBase +// { +// private static NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); +// +// string INDEX_HTML_PATH = Path.Combine(Environment.CurrentDirectory, "index.html"); +// +// /// +// /// [TODO:description] +// /// +// /// [TODO:return] +// [HttpGet("/")] +// public IResult Index() +// { +// return TypedResults.Content("Hello", "text/html"); +// } +// +// // [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] +// // public IResult Error() +// // { +// // return TypedResults.Ok(); +// // } +// +// /// +// /// [TODO:description] +// /// +// /// [TODO:return] +// [HttpGet("/hello")] +// [HttpPost("/hello")] +// public IActionResult Hello() +// { +// string randomString = Guid.NewGuid().ToString(); +// return this.Ok($"Hello World! GUID: {randomString}"); +// } +// } + /// /// UDP API /// @@ -180,8 +218,8 @@ public class JtagController : ControllerBase /// 设备地址 /// 设备端口 [HttpGet("GetDeviceIDCode")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status500InternalServerError)] + [ProducesResponseType(typeof(uint), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)] public async ValueTask GetDeviceIDCode(string address, int port) { var jtagCtrl = new JtagClient.Jtag(address, port); @@ -237,8 +275,9 @@ public class JtagController : ControllerBase /// 设备地址 /// 比特流文件 [HttpPost("UploadBitstream")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] + [EnableCors("Users")] + [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(string), StatusCodes.Status400BadRequest)] public async ValueTask UploadBitstream(string address, IFormFile file) { if (file == null || file.Length == 0) @@ -263,7 +302,7 @@ public class JtagController : ControllerBase } logger.Info($"Device {address} Upload Bitstream Successfully"); - return TypedResults.Ok("Bitstream Upload Successfully"); + return TypedResults.Ok(true); } /// @@ -272,9 +311,10 @@ public class JtagController : ControllerBase /// 设备地址 /// 设备端口 [HttpPost("DownloadBitstream")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status500InternalServerError)] + [EnableCors("Users")] + [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(string), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)] public async ValueTask DownloadBitstream(string address, int port) { // 检查文件 @@ -344,6 +384,32 @@ public class JtagController : ControllerBase } } + + /// + /// [TODO:description] + /// + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:parameter] + /// [TODO:return] + [HttpPost("BoundaryScan")] + [EnableCors("Users")] + [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(string), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(Exception), StatusCodes.Status500InternalServerError)] + public async ValueTask BoundaryScan(string address, int port, int portNum) + { + var jtagCtrl = new JtagClient.Jtag(address, port); + var ret = await jtagCtrl.BoundaryScan(portNum); + if (!ret.IsSuccessful) + { + if (ret.Error is ArgumentException) + return TypedResults.BadRequest(ret.Error); + else return TypedResults.InternalServerError(ret.Error); + } + + return TypedResults.Ok(ret.Value); + } } /// diff --git a/server/src/JtagClient.cs b/server/src/JtagClient.cs index b02ed6e..2153ec3 100644 --- a/server/src/JtagClient.cs +++ b/server/src/JtagClient.cs @@ -1,3 +1,4 @@ +using System.Collections; using System.Net; using DotNext; using Newtonsoft.Json; @@ -730,13 +731,13 @@ public class Jtag return true; } - async ValueTask> LoadDRCareOutput(UInt32 bytesLen) + async ValueTask> LoadDRCareOutput(UInt32 UInt32Num) { - if (bytesLen > Math.Pow(2, 28)) return new(new Exception("Length is over 2^(28 - 3)")); + if (UInt32Num > Math.Pow(2, 23)) return new(new Exception("Length is over 2^(28 - 5)")); var ret = await WriteFIFO( JtagAddr.WRITE_CMD, - Common.Number.MultiBitsToNumber(JtagCmd.CMD_JTAG_LOAD_DR_CAREO, JtagCmd.LEN_CMD_JTAG, 8 * bytesLen, 28).Value, + Common.Number.MultiBitsToNumber(JtagCmd.CMD_JTAG_LOAD_DR_CAREO, JtagCmd.LEN_CMD_JTAG, 32 * UInt32Num, 28).Value, 0x01_00_00_00, JtagState.CMD_EXEC_FINISH); if (ret.Value) @@ -745,6 +746,31 @@ public class Jtag return new(new Exception("LoadDRCareo Failed!")); } + async ValueTask> LoadDRCareOutputArray(UInt32 UInt32Num) + { + if (UInt32Num > Math.Pow(2, 23)) return new(new Exception("Length is over 2^(28 - 5)")); + + var ret = await WriteFIFO( + JtagAddr.WRITE_CMD, + Common.Number.MultiBitsToNumber(JtagCmd.CMD_JTAG_LOAD_DR_CAREO, JtagCmd.LEN_CMD_JTAG, 32 * UInt32Num, 28).Value, + 0x01_00_00_00, JtagState.CMD_EXEC_FINISH); + + if (ret.Value) + { + var array = new UInt32[UInt32Num]; + for (int i = 0; i < UInt32Num; i++) + { + var retData = await ReadFIFO(JtagAddr.READ_DATA); + if (!retData.IsSuccessful) + return new(new Exception("Read FIFO failed when Load DR")); + array[i] = retData.Value; + } + return array; + } + else + return new(new Exception("LoadDRCareo Failed!")); + } + /// /// 读取 JTAG 设备的 ID 代码 /// @@ -774,7 +800,7 @@ public class Jtag if (!ret.IsSuccessful) return new(ret.Error); else if (!ret.Value) return new(new Exception("Jtag Clear Write Registers Failed")); - var retData = await LoadDRCareOutput(4); + var retData = await LoadDRCareOutput(1); if (!retData.IsSuccessful) { return new(new Exception("Get ID Code Failed")); @@ -812,11 +838,9 @@ public class Jtag if (!ret.IsSuccessful) return new(ret.Error); else if (!ret.Value) return new(new Exception("Jtag Clear Write Registers Failed")); - var retData = await LoadDRCareOutput(4); + var retData = await LoadDRCareOutput(1); if (!retData.IsSuccessful) - { return new(new Exception("Read Status Reg Failed")); - } return retData.Value; } @@ -901,4 +925,48 @@ public class Jtag return true; } + + /// + /// [TODO:description] + /// + /// [TODO:parameter] + /// [TODO:return] + public async ValueTask> BoundaryScan(int portNum) + { + if (portNum <= 0) + return new(new ArgumentException("The number of port couldn't be negative", nameof(portNum))); + + // Clear Data + await MsgBus.UDPServer.ClearUDPData(this.address); + + logger.Trace($"Clear up udp server {this.address} receive data"); + + Result ret; + + ret = await CloseTest(); + if (!ret.IsSuccessful) return new(ret.Error); + else if (!ret.Value) return new(new Exception("Jtag Close Test Failed")); + + ret = await RunTest(); + if (!ret.IsSuccessful) return new(ret.Error); + else if (!ret.Value) return new(new Exception("Jtag Run Test Failed")); + + logger.Trace("Jtag initialize"); + + ret = await ExecRDCmd(JtagCmd.JTAG_DR_SAMPLE); + if (!ret.IsSuccessful) return new(ret.Error); + else if (!ret.Value) return new(new Exception("Jtag Execute Command JTAG_DR_JRST Failed")); + + var retData = await LoadDRCareOutputArray(((uint)(portNum % 32 == 0 ? portNum / 32 : portNum / 32 + 1))); + if (!retData.IsSuccessful) + return new(new Exception("Read Status Reg Failed")); + + ret = await CloseTest(); + if (!ret.IsSuccessful) return new(ret.Error); + else if (!ret.Value) return new(new Exception("Jtag Close Test Failed")); + + var byteArray = Common.Number.UInt32ArrayToBytes(retData.Value); + if (!byteArray.IsSuccessful) return new(byteArray.Error); + return new BitArray(byteArray.Value); + } } diff --git a/src/APIClient.ts b/src/APIClient.ts index 0b01edd..49a46e5 100644 --- a/src/APIClient.ts +++ b/src/APIClient.ts @@ -8,9 +8,6 @@ /* eslint-disable */ // ReSharper disable InconsistentNaming -import { batchSetConstraintStates, notifyConstraintChange } from './stores/constraints'; -import type { ConstraintLevel } from './stores/constraints'; - export class Client { private http: { fetch(url: RequestInfo, init?: RequestInit): Promise }; private baseUrl: string; @@ -473,7 +470,7 @@ export class JtagClient { * @param address (optional) 设备地址 * @param port (optional) 设备端口 */ - getDeviceIDCode(address: string | undefined, port: number | undefined): Promise { + getDeviceIDCode(address: string | undefined, port: number | undefined): Promise { let url_ = this.baseUrl + "/api/Jtag/GetDeviceIDCode?"; if (address === null) throw new Error("The parameter 'address' cannot be null."); @@ -485,6 +482,61 @@ export class JtagClient { url_ += "port=" + encodeURIComponent("" + port) + "&"; url_ = url_.replace(/[?&]$/, ""); + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/json" + } + }; + + return this.http.fetch(url_, options_).then((_response: Response) => { + return this.processGetDeviceIDCode(_response); + }); + } + + protected processGetDeviceIDCode(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result200 = resultData200 !== undefined ? resultData200 : null; + + return result200; + }); + } else if (status === 500) { + return response.text().then((_responseText) => { + let result500: any = null; + let resultData500 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result500 = Exception.fromJS(resultData500); + return throwException("A server side error occurred.", status, _responseText, _headers, result500); + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + /** + * 获取状态寄存器 + * @param address (optional) 设备地址 + * @param port (optional) 设备端口 + */ + readStatusReg(address: string | undefined, port: number | undefined): Promise { + let url_ = this.baseUrl + "/api/Jtag/ReadStatusReg?"; + if (address === null) + throw new Error("The parameter 'address' cannot be null."); + else if (address !== undefined) + url_ += "address=" + encodeURIComponent("" + address) + "&"; + if (port === null) + throw new Error("The parameter 'port' cannot be null."); + else if (port !== undefined) + url_ += "port=" + encodeURIComponent("" + port) + "&"; + url_ = url_.replace(/[?&]$/, ""); + let options_: RequestInit = { method: "GET", headers: { @@ -492,11 +544,11 @@ export class JtagClient { }; return this.http.fetch(url_, options_).then((_response: Response) => { - return this.processGetDeviceIDCode(_response); + return this.processReadStatusReg(_response); }); } - protected processGetDeviceIDCode(response: Response): Promise { + protected processReadStatusReg(response: Response): Promise { const status = response.status; let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; if (status === 200) { @@ -520,7 +572,7 @@ export class JtagClient { * @param address (optional) 设备地址 * @param file (optional) */ - uploadBitstream(address: string | undefined, file: FileParameter | null | undefined): Promise { + uploadBitstream(address: string | undefined, file: FileParameter | null | undefined): Promise { let url_ = this.baseUrl + "/api/Jtag/UploadBitstream?"; if (address === null) throw new Error("The parameter 'address' cannot be null."); @@ -536,6 +588,7 @@ export class JtagClient { body: content_, method: "POST", headers: { + "Accept": "application/json" } }; @@ -544,7 +597,147 @@ export class JtagClient { }); } - protected processUploadBitstream(response: Response): Promise { + protected processUploadBitstream(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result200 = resultData200 !== undefined ? resultData200 : null; + + return result200; + }); + } else if (status === 400) { + return response.text().then((_responseText) => { + let result400: any = null; + let resultData400 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result400 = resultData400 !== undefined ? resultData400 : null; + + return throwException("A server side error occurred.", status, _responseText, _headers, result400); + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + /** + * 通过Jtag下载比特流文件 + * @param address (optional) 设备地址 + * @param port (optional) 设备端口 + */ + downloadBitstream(address: string | undefined, port: number | undefined): Promise { + let url_ = this.baseUrl + "/api/Jtag/DownloadBitstream?"; + if (address === null) + throw new Error("The parameter 'address' cannot be null."); + else if (address !== undefined) + url_ += "address=" + encodeURIComponent("" + address) + "&"; + if (port === null) + throw new Error("The parameter 'port' cannot be null."); + else if (port !== undefined) + url_ += "port=" + encodeURIComponent("" + port) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "POST", + headers: { + "Accept": "application/json" + } + }; + + return this.http.fetch(url_, options_).then((_response: Response) => { + return this.processDownloadBitstream(_response); + }); + } + + protected processDownloadBitstream(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + let result200: any = null; + let resultData200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result200 = resultData200 !== undefined ? resultData200 : null; + + return result200; + }); + } else if (status === 400) { + return response.text().then((_responseText) => { + let result400: any = null; + let resultData400 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result400 = resultData400 !== undefined ? resultData400 : null; + + return throwException("A server side error occurred.", status, _responseText, _headers, result400); + }); + } else if (status === 500) { + return response.text().then((_responseText) => { + let result500: any = null; + let resultData500 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result500 = Exception.fromJS(resultData500); + return throwException("A server side error occurred.", status, _responseText, _headers, result500); + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } +} + +export class RemoteUpdaterClient { + private http: { fetch(url: RequestInfo, init?: RequestInit): Promise }; + private baseUrl: string; + protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined; + + constructor(baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise }) { + this.http = http ? http : window as any; + this.baseUrl = baseUrl ?? "http://localhost:5000"; + } + + /** + * 上传远程更新比特流文件 + * @param address (optional) 设备地址 + * @param goldenBitream (optional) + * @param bitstream1 (optional) + * @param bitstream2 (optional) + * @param bitstream3 (optional) + * @return 上传结果 + */ + uploadBitstreams(address: string | undefined, goldenBitream: FileParameter | null | undefined, bitstream1: FileParameter | null | undefined, bitstream2: FileParameter | null | undefined, bitstream3: FileParameter | null | undefined): Promise { + let url_ = this.baseUrl + "/api/RemoteUpdater/UploadBitstream?"; + if (address === null) + throw new Error("The parameter 'address' cannot be null."); + else if (address !== undefined) + url_ += "address=" + encodeURIComponent("" + address) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + const content_ = new FormData(); + if (goldenBitream !== null && goldenBitream !== undefined) + content_.append("goldenBitream", goldenBitream.data, goldenBitream.fileName ? goldenBitream.fileName : "goldenBitream"); + if (bitstream1 !== null && bitstream1 !== undefined) + content_.append("bitstream1", bitstream1.data, bitstream1.fileName ? bitstream1.fileName : "bitstream1"); + if (bitstream2 !== null && bitstream2 !== undefined) + content_.append("bitstream2", bitstream2.data, bitstream2.fileName ? bitstream2.fileName : "bitstream2"); + if (bitstream3 !== null && bitstream3 !== undefined) + content_.append("bitstream3", bitstream3.data, bitstream3.fileName ? bitstream3.fileName : "bitstream3"); + + let options_: RequestInit = { + body: content_, + method: "POST", + headers: { + } + }; + + return this.http.fetch(url_, options_).then((_response: Response) => { + return this.processUploadBitstreams(_response); + }); + } + + protected processUploadBitstreams(response: Response): Promise { const status = response.status; let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; if (status === 200) { @@ -567,12 +760,13 @@ export class JtagClient { } /** - * 通过Jtag下载比特流文件 + * 远程更新单个比特流文件 * @param address (optional) 设备地址 * @param port (optional) 设备端口 + * @param bitstreamNum (optional) 比特流位号 */ - downloadBitstream(address: string | undefined, port: number | undefined): Promise { - let url_ = this.baseUrl + "/api/Jtag/DownloadBitstream?"; + updateBitstream(address: string | undefined, port: number | undefined, bitstreamNum: number | undefined): Promise { + let url_ = this.baseUrl + "/api/RemoteUpdater/DownloadBitstream?"; if (address === null) throw new Error("The parameter 'address' cannot be null."); else if (address !== undefined) @@ -581,6 +775,10 @@ export class JtagClient { throw new Error("The parameter 'port' cannot be null."); else if (port !== undefined) url_ += "port=" + encodeURIComponent("" + port) + "&"; + if (bitstreamNum === null) + throw new Error("The parameter 'bitstreamNum' cannot be null."); + else if (bitstreamNum !== undefined) + url_ += "bitstreamNum=" + encodeURIComponent("" + bitstreamNum) + "&"; url_ = url_.replace(/[?&]$/, ""); let options_: RequestInit = { @@ -590,11 +788,11 @@ export class JtagClient { }; return this.http.fetch(url_, options_).then((_response: Response) => { - return this.processDownloadBitstream(_response); + return this.processUpdateBitstream(_response); }); } - protected processDownloadBitstream(response: Response): Promise { + protected processUpdateBitstream(response: Response): Promise { const status = response.status; let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; if (status === 200) { @@ -619,6 +817,308 @@ export class JtagClient { } return Promise.resolve(null as any); } + + /** + * 下载多个比特流文件 + * @param address (optional) 设备地址 + * @param port (optional) 设备端口 + * @param bitstreamNum (optional) 比特流编号 + * @return 总共上传比特流的数量 + */ + downloadMultiBitstreams(address: string | undefined, port: number | undefined, bitstreamNum: number | null | undefined): Promise { + let url_ = this.baseUrl + "/api/RemoteUpdater/DownloadMultiBitstreams?"; + if (address === null) + throw new Error("The parameter 'address' cannot be null."); + else if (address !== undefined) + url_ += "address=" + encodeURIComponent("" + address) + "&"; + if (port === null) + throw new Error("The parameter 'port' cannot be null."); + else if (port !== undefined) + url_ += "port=" + encodeURIComponent("" + port) + "&"; + if (bitstreamNum !== undefined && bitstreamNum !== null) + url_ += "bitstreamNum=" + encodeURIComponent("" + bitstreamNum) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "POST", + headers: { + } + }; + + return this.http.fetch(url_, options_).then((_response: Response) => { + return this.processDownloadMultiBitstreams(_response); + }); + } + + protected processDownloadMultiBitstreams(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + return; + }); + } else if (status === 400) { + return response.text().then((_responseText) => { + let result400: any = null; + let resultData400 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result400 = ProblemDetails.fromJS(resultData400); + return throwException("A server side error occurred.", status, _responseText, _headers, result400); + }); + } else if (status === 500) { + return response.text().then((_responseText) => { + return throwException("A server side error occurred.", status, _responseText, _headers); + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + /** + * 热复位比特流文件 + * @param address (optional) 设备地址 + * @param port (optional) 设备端口 + * @param bitstreamNum (optional) 比特流编号 + * @return 操作结果 + */ + hotResetBitstream(address: string | undefined, port: number | undefined, bitstreamNum: number | undefined): Promise { + let url_ = this.baseUrl + "/api/RemoteUpdater/HotResetBitstream?"; + if (address === null) + throw new Error("The parameter 'address' cannot be null."); + else if (address !== undefined) + url_ += "address=" + encodeURIComponent("" + address) + "&"; + if (port === null) + throw new Error("The parameter 'port' cannot be null."); + else if (port !== undefined) + url_ += "port=" + encodeURIComponent("" + port) + "&"; + if (bitstreamNum === null) + throw new Error("The parameter 'bitstreamNum' cannot be null."); + else if (bitstreamNum !== undefined) + url_ += "bitstreamNum=" + encodeURIComponent("" + bitstreamNum) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "POST", + headers: { + } + }; + + return this.http.fetch(url_, options_).then((_response: Response) => { + return this.processHotResetBitstream(_response); + }); + } + + protected processHotResetBitstream(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200) { + return response.text().then((_responseText) => { + return; + }); + } else if (status === 400) { + return response.text().then((_responseText) => { + let result400: any = null; + let resultData400 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver); + result400 = ProblemDetails.fromJS(resultData400); + return throwException("A server side error occurred.", status, _responseText, _headers, result400); + }); + } else if (status === 500) { + return response.text().then((_responseText) => { + return throwException("A server side error occurred.", status, _responseText, _headers); + }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } +} + +export class DataClient { + private http: { fetch(url: RequestInfo, init?: RequestInit): Promise }; + private baseUrl: string; + protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined; + + constructor(baseUrl?: string, http?: { fetch(url: RequestInfo, init?: RequestInit): Promise }) { + this.http = http ? http : window as any; + this.baseUrl = baseUrl ?? "http://localhost:5000"; + } + + /** + * 创建数据库表 + * @return 插入的记录数 + */ + createTables(): Promise { + let url_ = this.baseUrl + "/api/Data/CreateTable"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "POST", + headers: { + "Accept": "application/octet-stream" + } + }; + + return this.http.fetch(url_, options_).then((_response: Response) => { + return this.processCreateTables(_response); + }); + } + + protected processCreateTables(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200 || status === 206) { + const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined; + let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined; + let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined; + if (fileName) { + fileName = decodeURIComponent(fileName); + } else { + fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined; + fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; + } + return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + /** + * 删除数据库表 + * @return 插入的记录数 + */ + dropTables(): Promise { + let url_ = this.baseUrl + "/api/Data/DropTables"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "DELETE", + headers: { + "Accept": "application/octet-stream" + } + }; + + return this.http.fetch(url_, options_).then((_response: Response) => { + return this.processDropTables(_response); + }); + } + + protected processDropTables(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200 || status === 206) { + const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined; + let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined; + let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined; + if (fileName) { + fileName = decodeURIComponent(fileName); + } else { + fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined; + fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; + } + return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + /** + * 获取所有用户 + * @return 用户列表 + */ + allUsers(): Promise { + let url_ = this.baseUrl + "/api/Data/AllUsers"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "GET", + headers: { + "Accept": "application/octet-stream" + } + }; + + return this.http.fetch(url_, options_).then((_response: Response) => { + return this.processAllUsers(_response); + }); + } + + protected processAllUsers(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200 || status === 206) { + const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined; + let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined; + let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined; + if (fileName) { + fileName = decodeURIComponent(fileName); + } else { + fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined; + fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; + } + return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } + + /** + * 注册新用户 + * @param name (optional) 用户名 + * @return 操作结果 + */ + signUpUser(name: string | undefined): Promise { + let url_ = this.baseUrl + "/api/Data/SignUpUser?"; + if (name === null) + throw new Error("The parameter 'name' cannot be null."); + else if (name !== undefined) + url_ += "name=" + encodeURIComponent("" + name) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: RequestInit = { + method: "POST", + headers: { + "Accept": "application/octet-stream" + } + }; + + return this.http.fetch(url_, options_).then((_response: Response) => { + return this.processSignUpUser(_response); + }); + } + + protected processSignUpUser(response: Response): Promise { + const status = response.status; + let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); }; + if (status === 200 || status === 206) { + const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined; + let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined; + let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined; + if (fileName) { + fileName = decodeURIComponent(fileName); + } else { + fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined; + fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined; + } + return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; }); + } else if (status !== 200 && status !== 204) { + return response.text().then((_responseText) => { + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + }); + } + return Promise.resolve(null as any); + } } /** Package options which to send address to read or write */ @@ -687,8 +1187,8 @@ export interface ISendAddrPackOptions { /** Package Burst Type */ export enum BurstType { - ExtendBurst = 0, - FixedBurst = 1, + FixedBurst = 0, + ExtendBurst = 1, } /** UDP接受数据包格式 */ @@ -755,6 +1255,54 @@ export interface IUDPData { hasRead?: boolean; } +export class Exception implements IException { + message?: string; + innerException?: Exception | undefined; + source?: string | undefined; + stackTrace?: string | undefined; + + constructor(data?: IException) { + if (data) { + for (var property in data) { + if (data.hasOwnProperty(property)) + (this)[property] = (data)[property]; + } + } + } + + init(_data?: any) { + if (_data) { + this.message = _data["Message"]; + this.innerException = _data["InnerException"] ? Exception.fromJS(_data["InnerException"]) : undefined; + this.source = _data["Source"]; + this.stackTrace = _data["StackTrace"]; + } + } + + static fromJS(data: any): Exception { + data = typeof data === 'object' ? data : {}; + let result = new Exception(); + result.init(data); + return result; + } + + toJSON(data?: any) { + data = typeof data === 'object' ? data : {}; + data["Message"] = this.message; + data["InnerException"] = this.innerException ? this.innerException.toJSON() : undefined; + data["Source"] = this.source; + data["StackTrace"] = this.stackTrace; + return data; + } +} + +export interface IException { + message?: string; + innerException?: Exception | undefined; + source?: string | undefined; + stackTrace?: string | undefined; +} + export class ProblemDetails implements IProblemDetails { type?: string | undefined; title?: string | undefined; @@ -840,6 +1388,13 @@ export interface FileParameter { fileName: string; } +export interface FileResponse { + data: Blob; + status: number; + fileName?: string; + headers?: { [name: string]: any }; +} + export class ApiException extends Error { message: string; status: number; @@ -869,57 +1424,4 @@ function throwException(message: string, status: number, response: string, heade throw result; else throw new ApiException(message, status, response, headers, null); -} - -// 约束通信相关方法 -export function receiveConstraintUpdates(constraints: Record) { - // 批量更新约束状态 - batchSetConstraintStates(constraints); -} - -export function sendConstraintUpdate(constraint: string, level: ConstraintLevel) { - // 向后端发送约束状态变化 - console.log(`发送约束 ${constraint} 状态变化为 ${level}`); - - // TODO: 实际的WebSocket或HTTP请求发送约束变化 - // 例如: - // socket.emit('constraintUpdate', { constraint, level }); - // 或 - // fetch('/api/constraints', { - // method: 'POST', - // body: JSON.stringify({ constraint, level }), - // headers: { 'Content-Type': 'application/json' } - // }); -} - -// 初始化约束通信 -export function initConstraintCommunication() { - // 监听服务器发来的约束状态变化 - // 示例: - // socket.on('constraintUpdates', (data) => { - // receiveConstraintUpdates(data); - // }); - - // 模拟接收一些初始约束状态 - setTimeout(() => { - receiveConstraintUpdates({ - 'A1': 'high', - 'A2': 'low', - 'A3': 'undefined' - }); - }, 1000); -} - -// 覆盖全局notifyConstraintChange,加入发送逻辑 -const originalNotifyConstraintChange = notifyConstraintChange; -const wrappedNotifyConstraintChange = (constraint: string, level: ConstraintLevel) => { - // 调用原始方法更新本地状态 - originalNotifyConstraintChange(constraint, level); - - // 向后端发送更新 - sendConstraintUpdate(constraint, level); -}; - -// 替换全局方法 -(window as any).__notifyConstraintChange = notifyConstraintChange; -(window as any).notifyConstraintChange = wrappedNotifyConstraintChange; \ No newline at end of file +} \ No newline at end of file diff --git a/src/App.vue b/src/App.vue index 85dca44..ff979bf 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,26 +1,34 @@ @@ -40,15 +48,18 @@ provide('theme', {
+
-
+ +

Copyright © 2023 - All right reserved by OurEDA

-
+ + diff --git a/src/components/LoginCard.vue b/src/components/LoginCard.vue index 3907fed..3746e5b 100644 --- a/src/components/LoginCard.vue +++ b/src/components/LoginCard.vue @@ -5,19 +5,19 @@
- Forget Password + 忘记密码?
- - + +
diff --git a/src/components/Navbar.vue b/src/components/Navbar.vue index 2e14737..11bbedb 100644 --- a/src/components/Navbar.vue +++ b/src/components/Navbar.vue @@ -2,24 +2,28 @@