diff --git a/src/X1.Domain/Common/FormTypeStepTypeConverter.cs b/src/X1.Domain/Common/FormTypeStepTypeConverter.cs index 4f2d287..6d4297d 100644 --- a/src/X1.Domain/Common/FormTypeStepTypeConverter.cs +++ b/src/X1.Domain/Common/FormTypeStepTypeConverter.cs @@ -55,12 +55,12 @@ public static class FormTypeStepTypeConverter { CaseStepType.Start => new List { FormType.NoForm }, CaseStepType.End => new List { FormType.NoForm }, - CaseStepType.Process => new List - { - FormType.NoForm, - FormType.DeviceRegistrationForm, - FormType.NetworkConnectivityForm, - FormType.NetworkPerformanceForm, + CaseStepType.Process => new List + { + FormType.NoForm, + FormType.DeviceRegistrationForm, + FormType.NetworkConnectivityForm, + FormType.NetworkPerformanceForm, FormType.VoiceCallForm }, CaseStepType.Decision => new List { FormType.NoForm }, @@ -148,7 +148,7 @@ public static class FormTypeStepTypeConverter public static List GetStepMappings() { var stepMappings = new List(); - + foreach (StepMapping mapping in Enum.GetValues(typeof(StepMapping))) { var fieldInfo = typeof(StepMapping).GetField(mapping.ToString()); @@ -156,10 +156,10 @@ public static class FormTypeStepTypeConverter { var displayAttribute = fieldInfo.GetCustomAttribute(); var descriptionAttribute = fieldInfo.GetCustomAttribute(); - + var name = displayAttribute?.ShortName ?? mapping.ToString(); var description = descriptionAttribute?.Description ?? mapping.ToString(); - + stepMappings.Add(new EnumValueObject { Value = (int)mapping, @@ -168,7 +168,7 @@ public static class FormTypeStepTypeConverter }); } } - + return stepMappings; } @@ -182,6 +182,8 @@ public static class FormTypeStepTypeConverter return mapping switch { StepMapping.None => FormType.NoForm, + StepMapping.StartFlow => FormType.NoForm, + StepMapping.EndFlow => FormType.NoForm, StepMapping.EnableFlightMode => FormType.NoForm, StepMapping.DisableFlightMode => FormType.NoForm, StepMapping.ImsiRegistration => FormType.DeviceRegistrationForm, @@ -189,7 +191,7 @@ public static class FormTypeStepTypeConverter StepMapping.MtCall => FormType.VoiceCallForm, StepMapping.HangUpCall => FormType.VoiceCallForm, StepMapping.PingTest => FormType.NetworkConnectivityForm, - + StepMapping.IperfTest => FormType.NetworkPerformanceForm, _ => FormType.NoForm }; @@ -204,29 +206,31 @@ public static class FormTypeStepTypeConverter { return formType switch { - FormType.NoForm => new List - { - StepMapping.None, - StepMapping.EnableFlightMode, - StepMapping.DisableFlightMode + FormType.NoForm => new List + { + StepMapping.None, + StepMapping.StartFlow, + StepMapping.EndFlow, + StepMapping.EnableFlightMode, + StepMapping.DisableFlightMode }, - FormType.DeviceRegistrationForm => new List - { - StepMapping.ImsiRegistration + FormType.DeviceRegistrationForm => new List + { + StepMapping.ImsiRegistration }, - FormType.VoiceCallForm => new List - { - StepMapping.MoCall, - StepMapping.MtCall, - StepMapping.HangUpCall + FormType.VoiceCallForm => new List + { + StepMapping.MoCall, + StepMapping.MtCall, + StepMapping.HangUpCall }, - FormType.NetworkConnectivityForm => new List - { - StepMapping.PingTest + FormType.NetworkConnectivityForm => new List + { + StepMapping.PingTest }, - FormType.NetworkPerformanceForm => new List - { - StepMapping.IperfTest + FormType.NetworkPerformanceForm => new List + { + StepMapping.IperfTest }, _ => new List { StepMapping.None } }; diff --git a/src/X1.Domain/Entities/TestCase/StepMapping.cs b/src/X1.Domain/Entities/TestCase/StepMapping.cs index feebf3f..55247cb 100644 --- a/src/X1.Domain/Entities/TestCase/StepMapping.cs +++ b/src/X1.Domain/Entities/TestCase/StepMapping.cs @@ -15,59 +15,73 @@ public enum StepMapping [Description("不包含任何具体操作的步骤")] None = 0, + /// + /// 启动流程 + /// + [Display(Name = "启动流程", ShortName = "StartFlowController")] + [Description("启动测试流程,初始化测试环境和参数")] + StartFlow = 1, + + /// + /// 结束流程 + /// + [Display(Name = "结束流程", ShortName = "EndFlowController")] + [Description("结束测试流程,清理资源并生成测试报告")] + EndFlow = 2, + /// /// 开启飞行模式 /// [Display(Name = "开启飞行模式", ShortName = "EnableFlightModeController")] [Description("开启设备的飞行模式,禁用所有无线通信功能")] - EnableFlightMode = 1, + EnableFlightMode = 3, /// /// 关闭飞行模式 /// [Display(Name = "关闭飞行模式", ShortName = "DisableFlightModeController")] [Description("关闭设备的飞行模式,恢复无线通信功能")] - DisableFlightMode = 2, + DisableFlightMode = 4, /// /// IMSI注册 /// [Display(Name = "IMSI注册", ShortName = "ImsiRegistrationController")] [Description("使用IMSI进行网络注册,建立与移动网络的连接")] - ImsiRegistration = 3, + ImsiRegistration = 5, /// /// 主叫通话 (MoCall) /// [Display(Name = "主叫通话", ShortName = "MoCallController")] [Description("发起主叫通话,作为主叫方拨打电话")] - MoCall = 4, + MoCall = 6, /// /// 被叫通话 (MTCall) /// [Display(Name = "被叫通话", ShortName = "MtCallController")] [Description("接收被叫通话,作为被叫方接听电话")] - MtCall = 5, + MtCall = 7, /// /// 挂断电话 /// [Display(Name = "挂断电话", ShortName = "HangUpCallController")] [Description("结束当前通话,挂断电话连接")] - HangUpCall = 6, + HangUpCall = 8, /// /// Ping测试 /// [Display(Name = "Ping测试", ShortName = "PingTestController")] [Description("执行网络连通性测试,检测网络连接状态")] - PingTest = 7, + PingTest = 9, /// /// Iperf测试 /// [Display(Name = "Iperf测试", ShortName = "IperfTestController")] [Description("执行网络性能测试,测量网络带宽和延迟")] - IperfTest = 8 + IperfTest = 10 } diff --git a/src/X1.Infrastructure/Migrations/20250822184324_UpdateCaseStepConfigMappingFieldType.Designer.cs b/src/X1.Infrastructure/Migrations/20250822184324_UpdateCaseStepConfigMappingFieldType.Designer.cs new file mode 100644 index 0000000..58d9aea --- /dev/null +++ b/src/X1.Infrastructure/Migrations/20250822184324_UpdateCaseStepConfigMappingFieldType.Designer.cs @@ -0,0 +1,1978 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; +using X1.Infrastructure.Context; + +#nullable disable + +namespace X1.Infrastructure.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20250822184324_UpdateCaseStepConfigMappingFieldType")] + partial class UpdateCaseStepConfigMappingFieldType + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("X1.Domain.Entities.AppRole", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("角色ID,主键"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text") + .HasComment("并发控制戳"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("角色描述"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("角色名称"); + + b.Property("NormalizedName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("标准化角色名称(大写)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("更新时间"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique() + .HasDatabaseName("IX_Roles_Name"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("tb_roles", null, t => + { + t.HasComment("角色表"); + }); + }); + + modelBuilder.Entity("X1.Domain.Entities.AppUser", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("用户ID,主键"); + + b.Property("AccessFailedCount") + .HasColumnType("integer") + .HasComment("登录失败次数"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("text") + .HasComment("并发控制戳"); + + b.Property("CreatedTime") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("电子邮箱"); + + b.Property("EmailConfirmed") + .HasColumnType("boolean") + .HasComment("邮箱是否已验证"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasComment("用户状态(true: 启用, false: 禁用)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasComment("是否已删除"); + + b.Property("LastLoginTime") + .HasColumnType("timestamp with time zone") + .HasComment("最后登录时间"); + + b.Property("LockoutEnabled") + .HasColumnType("boolean") + .HasComment("是否启用账户锁定"); + + b.Property("LockoutEnd") + .HasColumnType("timestamp with time zone") + .HasComment("账户锁定结束时间"); + + b.Property("ModifiedTime") + .HasColumnType("timestamp with time zone") + .HasComment("修改时间"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("标准化电子邮箱(大写)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("标准化账号(大写)"); + + b.Property("PasswordHash") + .HasColumnType("text") + .HasComment("密码哈希值"); + + b.Property("PhoneNumber") + .IsRequired() + .HasColumnType("text") + .HasComment("电话号码"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("boolean") + .HasComment("电话号码是否已验证"); + + b.Property("RealName") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("用户名"); + + b.Property("SecurityStamp") + .HasColumnType("text") + .HasComment("安全戳,用于并发控制"); + + b.Property("TwoFactorEnabled") + .HasColumnType("boolean") + .HasComment("是否启用双因素认证"); + + b.Property("UserName") + .IsRequired() + .HasMaxLength(256) + .HasColumnType("character varying(256)") + .HasComment("账号"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique() + .HasDatabaseName("IX_user_Email"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.HasIndex("PhoneNumber") + .IsUnique() + .HasDatabaseName("IX_user_PhoneNumber"); + + b.HasIndex("UserName") + .IsUnique() + .HasDatabaseName("IX_user_UserName"); + + b.ToTable("tb_users", null, t => + { + t.HasComment("用户表"); + }); + }); + + modelBuilder.Entity("X1.Domain.Entities.Device.CellularDevice", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("设备ID"); + + b.Property("AgentPort") + .HasColumnType("integer") + .HasComment("Agent端口"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("设备描述"); + + b.Property("DeviceCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("设备编码"); + + b.Property("IpAddress") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("character varying(45)") + .HasComment("IP地址"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsEnabled") + .HasColumnType("boolean") + .HasComment("是否启用"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("设备名称"); + + b.Property("SerialNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("序列号"); + + b.Property("UpdatedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasComment("更新时间"); + + b.Property("UpdatedBy") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("DeviceCode") + .IsUnique() + .HasDatabaseName("IX_cellular_device_DeviceCode"); + + b.HasIndex("SerialNumber") + .IsUnique() + .HasDatabaseName("IX_cellular_device_SerialNumber"); + + b.ToTable("tb_cellular_device", null, t => + { + t.HasComment("蜂窝设备表"); + }); + }); + + modelBuilder.Entity("X1.Domain.Entities.Device.CellularDeviceRuntime", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("DeviceCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("IsDeleted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("NetworkStackCode") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RuntimeCode") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RuntimeStatus") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("DeviceCode") + .IsUnique(); + + b.HasIndex("IsDeleted"); + + b.HasIndex("NetworkStackCode"); + + b.HasIndex("RuntimeCode"); + + b.HasIndex("RuntimeStatus"); + + b.HasIndex("DeviceCode", "CreatedAt") + .HasDatabaseName("IX_CellularDeviceRuntimes_DeviceCode_CreatedAt"); + + b.ToTable("tb_cellular_device_runtimes", (string)null); + }); + + modelBuilder.Entity("X1.Domain.Entities.Device.CellularDeviceRuntimeDetail", b => + { + b.Property("Id") + .HasMaxLength(450) + .HasColumnType("character varying(450)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .HasMaxLength(450) + .HasColumnType("character varying(450)"); + + b.Property("DeviceCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("NetworkStackCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RuntimeCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("RuntimeStatus") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt") + .HasDatabaseName("IX_CellularDeviceRuntimeDetails_CreatedAt"); + + b.HasIndex("CreatedBy") + .HasDatabaseName("IX_CellularDeviceRuntimeDetails_CreatedBy"); + + b.HasIndex("RuntimeCode") + .HasDatabaseName("IX_CellularDeviceRuntimeDetails_RuntimeCode"); + + b.HasIndex("RuntimeStatus") + .HasDatabaseName("IX_CellularDeviceRuntimeDetails_RuntimeStatus"); + + b.ToTable("tb_cellular_device_runtime_details", (string)null); + }); + + modelBuilder.Entity("X1.Domain.Entities.Device.ProtocolVersion", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("版本ID"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("版本描述"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsEnabled") + .HasColumnType("boolean") + .HasComment("是否启用"); + + b.Property("MinimumSupportedVersion") + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasComment("最低支持版本"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("版本名称"); + + b.Property("ReleaseDate") + .HasColumnType("timestamp with time zone") + .HasComment("发布日期"); + + b.Property("SerialNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("设备序列号"); + + b.Property("UpdatedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasComment("更新时间"); + + b.Property("UpdatedBy") + .HasColumnType("text"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasComment("版本号"); + + b.HasKey("Id"); + + b.HasIndex("SerialNumber") + .HasDatabaseName("IX_ProtocolVersions_SerialNumber"); + + b.HasIndex("Version") + .HasDatabaseName("IX_ProtocolVersions_Version"); + + b.ToTable("tb_protocol_versions", null, t => + { + t.HasComment("协议版本表"); + }); + }); + + modelBuilder.Entity("X1.Domain.Entities.Logging.LoginLog", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("日志ID"); + + b.Property("Browser") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("浏览器信息"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("FailureReason") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasComment("失败原因"); + + b.Property("IpAddress") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("登录IP"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsSuccess") + .HasColumnType("boolean") + .HasComment("登录状态(成功/失败)"); + + b.Property("Location") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasComment("登录位置"); + + b.Property("LoginSource") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("LoginTime") + .HasColumnType("timestamp with time zone") + .HasComment("登录时间"); + + b.Property("LoginType") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("OperatingSystem") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("操作系统信息"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UserAgent") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("设备信息"); + + b.Property("UserId") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasComment("用户ID"); + + b.HasKey("Id"); + + b.HasIndex("IpAddress") + .HasDatabaseName("IX_LoginLogs_IpAddress"); + + b.HasIndex("LoginTime") + .HasDatabaseName("IX_LoginLogs_LoginTime"); + + b.HasIndex("UserId") + .HasDatabaseName("IX_LoginLogs_UserId"); + + b.HasIndex("UserId", "LoginTime") + .HasDatabaseName("IX_LoginLogs_UserId_LoginTime"); + + b.ToTable("tb_login_logs", null, t => + { + t.HasComment("用户登录日志表"); + }); + }); + + modelBuilder.Entity("X1.Domain.Entities.Logging.ProtocolLog", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键ID"); + + b.Property("CellID") + .HasColumnType("integer") + .HasComment("小区ID"); + + b.Property("DeviceCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("设备代码"); + + b.Property("Direction") + .HasColumnType("integer") + .HasComment("日志方向类型"); + + b.Property("IMSI") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("国际移动用户识别码"); + + b.Property("Info") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("信息字段"); + + b.Property("LayerType") + .HasMaxLength(50) + .HasColumnType("integer") + .HasComment("协议层类型"); + + b.Property("Message") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)") + .HasComment("消息字段"); + + b.Property("MessageDetailJson") + .HasColumnType("text") + .HasComment("消息详情集合(JSON格式存储)"); + + b.Property("MessageId") + .HasColumnType("bigint") + .HasComment("消息ID"); + + b.Property("PLMN") + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasComment("公共陆地移动网络标识"); + + b.Property("RuntimeCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("运行时代码"); + + b.Property("TimeMs") + .HasColumnType("bigint") + .HasComment("时间间隔(毫秒)"); + + b.Property("Timestamp") + .HasColumnType("bigint") + .HasComment("时间戳"); + + b.Property("UEID") + .HasColumnType("integer") + .HasComment("用户设备ID"); + + b.HasKey("Id"); + + b.HasIndex("DeviceCode") + .HasDatabaseName("IX_ProtocolLog_DeviceCode"); + + b.HasIndex("LayerType") + .HasDatabaseName("IX_ProtocolLog_LayerType"); + + b.HasIndex("MessageId") + .HasDatabaseName("IX_ProtocolLog_MessageId"); + + b.HasIndex("RuntimeCode") + .HasDatabaseName("IX_ProtocolLog_RuntimeCode"); + + b.HasIndex("Timestamp") + .HasDatabaseName("IX_ProtocolLog_Timestamp"); + + b.HasIndex("DeviceCode", "RuntimeCode") + .HasDatabaseName("IX_ProtocolLog_DeviceCode_RuntimeCode"); + + b.HasIndex("DeviceCode", "Timestamp") + .HasDatabaseName("IX_ProtocolLog_DeviceCode_Timestamp"); + + b.ToTable("tb_protocol_logs", null, t => + { + t.HasComment("协议日志表"); + }); + }); + + modelBuilder.Entity("X1.Domain.Entities.NetworkProfile.CoreNetworkConfig", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("配置ID"); + + b.Property("ConfigContent") + .IsRequired() + .HasColumnType("text") + .HasComment("配置内容(JSON格式)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("配置描述"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsDisabled") + .HasColumnType("boolean") + .HasComment("是否禁用"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("配置名称"); + + b.Property("UpdatedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasComment("更新时间"); + + b.Property("UpdatedBy") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .HasDatabaseName("IX_CoreNetworkConfigs_Name"); + + b.ToTable("tb_core_network_configs", null, t => + { + t.HasComment("核心网配置表"); + }); + }); + + modelBuilder.Entity("X1.Domain.Entities.NetworkProfile.IMS_Configuration", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("配置ID"); + + b.Property("ConfigContent") + .IsRequired() + .HasColumnType("text") + .HasComment("配置内容(JSON格式)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("配置描述"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsDisabled") + .HasColumnType("boolean") + .HasComment("是否禁用"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("配置名称"); + + b.Property("UpdatedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasComment("更新时间"); + + b.Property("UpdatedBy") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .HasDatabaseName("IX_IMS_Configurations_Name"); + + b.ToTable("tb_ims_configurations", null, t => + { + t.HasComment("IMS配置表"); + }); + }); + + modelBuilder.Entity("X1.Domain.Entities.NetworkProfile.NetworkStackConfig", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("配置ID"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("描述"); + + b.Property("IsActive") + .HasColumnType("boolean") + .HasComment("是否激活"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("NetworkStackCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("网络栈编码"); + + b.Property("NetworkStackName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("网络栈名称"); + + b.Property("RanId") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("RAN配置ID"); + + b.Property("UpdatedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasComment("更新时间"); + + b.Property("UpdatedBy") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("IsActive") + .HasDatabaseName("IX_NetworkStackConfigs_IsActive"); + + b.HasIndex("NetworkStackCode") + .IsUnique() + .HasDatabaseName("IX_NetworkStackConfigs_NetworkStackCode"); + + b.HasIndex("NetworkStackName") + .IsUnique() + .HasDatabaseName("IX_NetworkStackConfigs_NetworkStackName"); + + b.HasIndex("RanId") + .HasDatabaseName("IX_NetworkStackConfigs_RanId"); + + b.ToTable("tb_network_stack_configs", null, t => + { + t.HasComment("网络栈配置表"); + }); + }); + + modelBuilder.Entity("X1.Domain.Entities.NetworkProfile.RAN_Configuration", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("配置ID"); + + b.Property("ConfigContent") + .IsRequired() + .HasColumnType("text") + .HasComment("配置内容(JSON格式)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间"); + + b.Property("CreatedBy") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("配置描述"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsDisabled") + .HasColumnType("boolean") + .HasComment("是否禁用"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("配置名称"); + + b.Property("UpdatedAt") + .IsRequired() + .HasColumnType("timestamp with time zone") + .HasComment("更新时间"); + + b.Property("UpdatedBy") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .HasDatabaseName("IX_RAN_Configurations_Name"); + + b.ToTable("tb_ran_configurations", null, t => + { + t.HasComment("RAN配置表"); + }); + }); + + modelBuilder.Entity("X1.Domain.Entities.NetworkProfile.Stack_CoreIMS_Binding", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("绑定关系ID"); + + b.Property("CnId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("核心网配置ID"); + + b.Property("ImsId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("IMS配置ID"); + + b.Property("Index") + .HasColumnType("integer") + .HasComment("索引"); + + b.Property("NetworkStackConfigId") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("网络栈配置ID"); + + b.HasKey("Id"); + + b.HasIndex("CnId") + .HasDatabaseName("IX_Stack_CoreIMS_Bindings_CnId"); + + b.HasIndex("ImsId") + .HasDatabaseName("IX_Stack_CoreIMS_Bindings_ImsId"); + + b.HasIndex("NetworkStackConfigId") + .HasDatabaseName("IX_Stack_CoreIMS_Bindings_NetworkStackConfigId"); + + b.HasIndex("NetworkStackConfigId", "Index") + .IsUnique() + .HasDatabaseName("IX_Stack_CoreIMS_Bindings_NetworkStackConfigId_Index"); + + b.ToTable("tb_stack_core_ims_bindings", null, t => + { + t.HasComment("栈与核心网/IMS绑定关系表"); + }); + }); + + modelBuilder.Entity("X1.Domain.Entities.Permission", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("character varying(200)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.HasKey("Id"); + + b.ToTable("tb_permissions", (string)null); + }); + + modelBuilder.Entity("X1.Domain.Entities.RolePermission", b => + { + b.Property("RoleId") + .HasColumnType("text"); + + b.Property("PermissionId") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("RoleId", "PermissionId"); + + b.HasIndex("PermissionId"); + + b.ToTable("tb_role_permissions", (string)null); + }); + + modelBuilder.Entity("X1.Domain.Entities.Terminal.AdbOperation", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("Command") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)") + .HasComment("执行的ADB命令"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间"); + + b.Property("CreatedBy") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("创建人"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("ADB操作的描述"); + + b.Property("DeviceId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("设备ID"); + + b.Property("IsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasComment("是否启用"); + + b.Property("Path") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("命令执行时所依赖的路径(当启用绝对路径时必填)"); + + b.Property("ScreenshotData") + .HasColumnType("BYTEA") + .HasComment("操作截图数据(字节数组)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("更新时间"); + + b.Property("UpdatedBy") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("更新人"); + + b.Property("UseAbsolutePath") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false) + .HasComment("是否启用绝对路径"); + + b.Property("WaitTimeMs") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0) + .HasComment("执行命令完需要等待的时间(毫秒)"); + + b.HasKey("Id"); + + b.HasIndex("Command") + .HasDatabaseName("IX_AdbOperations_Command"); + + b.HasIndex("CreatedAt") + .HasDatabaseName("IX_AdbOperations_CreatedAt"); + + b.HasIndex("DeviceId") + .HasDatabaseName("IX_AdbOperations_DeviceId"); + + b.HasIndex("IsEnabled") + .HasDatabaseName("IX_AdbOperations_IsEnabled"); + + b.ToTable("tb_adboperations", (string)null); + }); + + modelBuilder.Entity("X1.Domain.Entities.Terminal.AtOperation", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("BaudRate") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(115200) + .HasComment("波特率"); + + b.Property("Command") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("character varying(1000)") + .HasComment("AT命令内容"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("创建时间"); + + b.Property("CreatedBy") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("创建人"); + + b.Property("DataBits") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(8) + .HasComment("数据位"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("操作描述"); + + b.Property("DeviceId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("设备ID"); + + b.Property("IsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true) + .HasComment("是否启用"); + + b.Property("Parameters") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(2000) + .HasColumnType("character varying(2000)") + .HasDefaultValue("") + .HasComment("命令参数(JSON格式)"); + + b.Property("Parity") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasDefaultValue("NONE") + .HasComment("校验位"); + + b.Property("Port") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("串口端口"); + + b.Property("ScreenshotData") + .HasColumnType("BYTEA") + .HasComment("操作截图数据(字节数组)"); + + b.Property("StopBits") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasDefaultValue("1") + .HasComment("停止位"); + + b.Property("Timeout") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(30) + .HasComment("超时时间(秒)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasComment("更新时间"); + + b.Property("UpdatedBy") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("更新人"); + + b.HasKey("Id"); + + b.HasIndex("Command") + .HasDatabaseName("IX_AtOperations_Command"); + + b.HasIndex("CreatedAt") + .HasDatabaseName("IX_AtOperations_CreatedAt"); + + b.HasIndex("DeviceId") + .HasDatabaseName("IX_AtOperations_DeviceId"); + + b.HasIndex("IsEnabled") + .HasDatabaseName("IX_AtOperations_IsEnabled"); + + b.ToTable("tb_atoperations", (string)null); + }); + + modelBuilder.Entity("X1.Domain.Entities.Terminal.TerminalDevice", b => + { + b.Property("Id") + .HasMaxLength(450) + .HasColumnType("character varying(450)"); + + b.Property("Alias") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("AndroidVersion") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("BootSerial") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Brand") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("BuildId") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("BuildType") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(0); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("Device") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Hardware") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("HardwarePlatform") + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("IsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("LastConnectedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("LastDisconnectedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("Locale") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Model") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SdkVersion") + .HasMaxLength(10) + .HasColumnType("character varying(10)"); + + b.Property("Serial") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ServiceSerial") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("Status") + .IsRequired() + .HasColumnType("text"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("AndroidVersion"); + + b.HasIndex("Brand"); + + b.HasIndex("Hardware"); + + b.HasIndex("IsEnabled"); + + b.HasIndex("LastConnectedAt"); + + b.HasIndex("Model"); + + b.HasIndex("Serial") + .IsUnique(); + + b.HasIndex("ServiceSerial"); + + b.HasIndex("Status"); + + b.ToTable("tb_terminal_devices", (string)null); + }); + + modelBuilder.Entity("X1.Domain.Entities.Terminal.TerminalService", b => + { + b.Property("Id") + .HasMaxLength(450) + .HasColumnType("character varying(450)"); + + b.Property("AgentPort") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("character varying(450)"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)"); + + b.Property("IpAddress") + .IsRequired() + .HasMaxLength(45) + .HasColumnType("character varying(45)"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsEnabled") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(true); + + b.Property("IsServiceStarted") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("SerialNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ServiceCode") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("ServiceType") + .ValueGeneratedOnAdd() + .HasColumnType("integer") + .HasDefaultValue(1); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("UpdatedBy") + .HasMaxLength(450) + .HasColumnType("character varying(450)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("IpAddress"); + + b.HasIndex("IsEnabled"); + + b.HasIndex("IsServiceStarted"); + + b.HasIndex("SerialNumber") + .IsUnique(); + + b.HasIndex("ServiceCode") + .IsUnique(); + + b.HasIndex("ServiceType"); + + b.ToTable("tb_terminal_services", (string)null); + }); + + modelBuilder.Entity("X1.Domain.Entities.TestCase.CaseStepConfig", b => + { + b.Property("Id") + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdat"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("createdby"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasColumnName("description"); + + b.Property("FormType") + .HasColumnType("integer") + .HasColumnName("formtype"); + + b.Property("Icon") + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("icon"); + + b.Property("IsDeleted") + .HasColumnType("boolean"); + + b.Property("IsEnabled") + .HasColumnType("boolean") + .HasColumnName("isenabled"); + + b.Property("Mapping") + .HasColumnType("integer") + .HasColumnName("mapping"); + + b.Property("StepName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("stepname"); + + b.Property("StepType") + .HasColumnType("integer") + .HasColumnName("steptype"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updatedat"); + + b.Property("UpdatedBy") + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("updatedby"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("FormType"); + + b.HasIndex("IsEnabled"); + + b.HasIndex("Mapping"); + + b.HasIndex("StepType"); + + b.ToTable("tb_casestepconfig", (string)null); + }); + + modelBuilder.Entity("X1.Domain.Entities.TestCase.ImsiRegistrationRecord", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("GETUTCDATE()"); + + b.Property("IsDualSim") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("NodeId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Sim1CellId") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Sim1Plmn") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Sim1RegistrationWaitTime") + .HasColumnType("integer"); + + b.Property("Sim2CellId") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Sim2Plmn") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Sim2RegistrationWaitTime") + .HasColumnType("integer"); + + b.Property("TestCaseId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt") + .HasDatabaseName("IX_tb_imsi_registration_records_createdat"); + + b.HasIndex("NodeId") + .HasDatabaseName("IX_tb_imsi_registration_records_nodeid"); + + b.HasIndex("TestCaseId") + .HasDatabaseName("IX_tb_imsi_registration_records_testcaseid"); + + b.HasIndex("TestCaseId", "NodeId") + .IsUnique() + .HasDatabaseName("IX_tb_imsi_registration_records_testcaseid_nodeid"); + + b.ToTable("tb_imsi_registration_records", null, t => + { + t.HasComment("IMSI注册记录表"); + }); + }); + + modelBuilder.Entity("X1.Domain.Entities.TestCase.TestCaseEdge", b => + { + b.Property("Id") + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("id"); + + b.Property("Condition") + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("condition"); + + b.Property("EdgeId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("edgeid"); + + b.Property("EdgeType") + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasColumnName("edgetype"); + + b.Property("IsAnimated") + .HasColumnType("boolean") + .HasColumnName("isanimated"); + + b.Property("SourceHandle") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("SourceNodeId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("sourcenodeid"); + + b.Property("Style") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasColumnName("style"); + + b.Property("TargetHandle") + .HasMaxLength(50) + .HasColumnType("character varying(50)"); + + b.Property("TargetNodeId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("targetnodeid"); + + b.Property("TestCaseId") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("testcaseid"); + + b.HasKey("Id"); + + b.HasIndex("EdgeId") + .HasDatabaseName("IX_testcaseedge_edgeid"); + + b.HasIndex("SourceNodeId") + .HasDatabaseName("IX_testcaseedge_sourcenodeid"); + + b.HasIndex("TargetNodeId") + .HasDatabaseName("IX_testcaseedge_targetnodeid"); + + b.HasIndex("TestCaseId") + .HasDatabaseName("IX_testcaseedge_testcaseid"); + + b.ToTable("tb_testcaseedge", (string)null); + }); + + modelBuilder.Entity("X1.Domain.Entities.TestCase.TestCaseFlow", b => + { + b.Property("Id") + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("createdat"); + + b.Property("CreatedBy") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("createdby"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("character varying(1000)") + .HasColumnName("description"); + + b.Property("IsEnabled") + .HasColumnType("boolean") + .HasColumnName("isenabled"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("character varying(200)") + .HasColumnName("name"); + + b.Property("Type") + .HasColumnType("integer") + .HasColumnName("type"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone") + .HasColumnName("updatedat"); + + b.Property("UpdatedBy") + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("updatedby"); + + b.Property("ViewportX") + .HasColumnType("double precision") + .HasColumnName("viewport_x"); + + b.Property("ViewportY") + .HasColumnType("double precision") + .HasColumnName("viewport_y"); + + b.Property("ViewportZoom") + .HasColumnType("double precision") + .HasColumnName("viewport_zoom"); + + b.HasKey("Id"); + + b.HasIndex("IsEnabled") + .HasDatabaseName("IX_testcaseflow_isenabled"); + + b.HasIndex("Name") + .HasDatabaseName("IX_testcaseflow_name"); + + b.HasIndex("Type") + .HasDatabaseName("IX_testcaseflow_type"); + + b.ToTable("tb_testcaseflow", (string)null); + }); + + modelBuilder.Entity("X1.Domain.Entities.TestCase.TestCaseNode", b => + { + b.Property("Id") + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("id"); + + b.Property("Height") + .HasColumnType("double precision") + .HasColumnName("height"); + + b.Property("IsDragging") + .HasColumnType("boolean") + .HasColumnName("isdragging"); + + b.Property("IsSelected") + .HasColumnType("boolean") + .HasColumnName("isselected"); + + b.Property("NodeId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("nodeid"); + + b.Property("PositionAbsoluteX") + .HasColumnType("double precision") + .HasColumnName("positionabsolutex"); + + b.Property("PositionAbsoluteY") + .HasColumnType("double precision") + .HasColumnName("positionabsolutey"); + + b.Property("PositionX") + .HasColumnType("double precision") + .HasColumnName("positionx"); + + b.Property("PositionY") + .HasColumnType("double precision") + .HasColumnName("positiony"); + + b.Property("SequenceNumber") + .HasColumnType("integer") + .HasColumnName("sequencenumber"); + + b.Property("StepId") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("stepid"); + + b.Property("TestCaseId") + .IsRequired() + .HasMaxLength(450) + .HasColumnType("character varying(450)") + .HasColumnName("testcaseid"); + + b.Property("Width") + .HasColumnType("double precision") + .HasColumnName("width"); + + b.HasKey("Id"); + + b.HasIndex("NodeId") + .HasDatabaseName("IX_testcasenode_nodeid"); + + b.HasIndex("SequenceNumber") + .HasDatabaseName("IX_testcasenode_sequencenumber"); + + b.HasIndex("StepId"); + + b.HasIndex("TestCaseId") + .HasDatabaseName("IX_testcasenode_testcaseid"); + + b.ToTable("tb_testcasenode", (string)null); + }); + + modelBuilder.Entity("X1.Domain.Entities.UserRole", b => + { + b.Property("UserId") + .HasColumnType("text"); + + b.Property("RoleId") + .HasColumnType("text"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("tb_user_roles", null, t => + { + t.HasComment("用户角色关系表"); + }); + }); + + modelBuilder.Entity("X1.Domain.Entities.Device.CellularDeviceRuntime", b => + { + b.HasOne("X1.Domain.Entities.Device.CellularDevice", "Device") + .WithOne("Runtime") + .HasForeignKey("X1.Domain.Entities.Device.CellularDeviceRuntime", "DeviceCode") + .HasPrincipalKey("X1.Domain.Entities.Device.CellularDevice", "DeviceCode") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Device"); + }); + + modelBuilder.Entity("X1.Domain.Entities.Device.ProtocolVersion", b => + { + b.HasOne("X1.Domain.Entities.Device.CellularDevice", null) + .WithMany("ProtocolVersions") + .HasForeignKey("SerialNumber") + .HasPrincipalKey("SerialNumber") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("X1.Domain.Entities.Logging.LoginLog", b => + { + b.HasOne("X1.Domain.Entities.AppUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + }); + + modelBuilder.Entity("X1.Domain.Entities.NetworkProfile.NetworkStackConfig", b => + { + b.HasOne("X1.Domain.Entities.NetworkProfile.RAN_Configuration", null) + .WithMany() + .HasForeignKey("RanId") + .OnDelete(DeleteBehavior.SetNull); + }); + + modelBuilder.Entity("X1.Domain.Entities.NetworkProfile.Stack_CoreIMS_Binding", b => + { + b.HasOne("X1.Domain.Entities.NetworkProfile.CoreNetworkConfig", "CoreNetworkConfig") + .WithMany() + .HasForeignKey("CnId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("X1.Domain.Entities.NetworkProfile.IMS_Configuration", "IMSConfiguration") + .WithMany() + .HasForeignKey("ImsId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("X1.Domain.Entities.NetworkProfile.NetworkStackConfig", "NetworkStackConfig") + .WithMany("StackCoreIMSBindings") + .HasForeignKey("NetworkStackConfigId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CoreNetworkConfig"); + + b.Navigation("IMSConfiguration"); + + b.Navigation("NetworkStackConfig"); + }); + + modelBuilder.Entity("X1.Domain.Entities.RolePermission", b => + { + b.HasOne("X1.Domain.Entities.Permission", "Permission") + .WithMany("RolePermissions") + .HasForeignKey("PermissionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("X1.Domain.Entities.AppRole", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Permission"); + + b.Navigation("Role"); + }); + + modelBuilder.Entity("X1.Domain.Entities.TestCase.ImsiRegistrationRecord", b => + { + b.HasOne("X1.Domain.Entities.TestCase.TestCaseNode", "TestCaseNode") + .WithMany() + .HasForeignKey("NodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("X1.Domain.Entities.TestCase.TestCaseFlow", "TestCase") + .WithMany() + .HasForeignKey("TestCaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TestCase"); + + b.Navigation("TestCaseNode"); + }); + + modelBuilder.Entity("X1.Domain.Entities.TestCase.TestCaseEdge", b => + { + b.HasOne("X1.Domain.Entities.TestCase.TestCaseFlow", "TestCase") + .WithMany("Edges") + .HasForeignKey("TestCaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TestCase"); + }); + + modelBuilder.Entity("X1.Domain.Entities.TestCase.TestCaseNode", b => + { + b.HasOne("X1.Domain.Entities.TestCase.CaseStepConfig", "StepConfig") + .WithMany() + .HasForeignKey("StepId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("X1.Domain.Entities.TestCase.TestCaseFlow", "TestCase") + .WithMany("Nodes") + .HasForeignKey("TestCaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("StepConfig"); + + b.Navigation("TestCase"); + }); + + modelBuilder.Entity("X1.Domain.Entities.UserRole", b => + { + b.HasOne("X1.Domain.Entities.AppRole", "Role") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("X1.Domain.Entities.AppUser", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Role"); + + b.Navigation("User"); + }); + + modelBuilder.Entity("X1.Domain.Entities.Device.CellularDevice", b => + { + b.Navigation("ProtocolVersions"); + + b.Navigation("Runtime"); + }); + + modelBuilder.Entity("X1.Domain.Entities.NetworkProfile.NetworkStackConfig", b => + { + b.Navigation("StackCoreIMSBindings"); + }); + + modelBuilder.Entity("X1.Domain.Entities.Permission", b => + { + b.Navigation("RolePermissions"); + }); + + modelBuilder.Entity("X1.Domain.Entities.TestCase.TestCaseFlow", b => + { + b.Navigation("Edges"); + + b.Navigation("Nodes"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/X1.Infrastructure/Migrations/20250822184324_UpdateCaseStepConfigMappingFieldType.cs b/src/X1.Infrastructure/Migrations/20250822184324_UpdateCaseStepConfigMappingFieldType.cs new file mode 100644 index 0000000..f148527 --- /dev/null +++ b/src/X1.Infrastructure/Migrations/20250822184324_UpdateCaseStepConfigMappingFieldType.cs @@ -0,0 +1,188 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace X1.Infrastructure.Migrations +{ + /// + public partial class UpdateCaseStepConfigMappingFieldType : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "ix_tb_casestepconfig_stepname", + table: "tb_casestepconfig"); + + migrationBuilder.RenameIndex( + name: "ix_tb_casestepconfig_steptype", + table: "tb_casestepconfig", + newName: "IX_tb_casestepconfig_steptype"); + + migrationBuilder.RenameIndex( + name: "ix_tb_casestepconfig_isenabled", + table: "tb_casestepconfig", + newName: "IX_tb_casestepconfig_isenabled"); + + // 首先更新现有的mapping数据,将字符串值转换为对应的整数值 + migrationBuilder.Sql(@" + UPDATE tb_casestepconfig + SET mapping = CASE + WHEN mapping = 'None' THEN '0' + WHEN mapping = 'StartFlow' THEN '1' + WHEN mapping = 'EndFlow' THEN '2' + WHEN mapping = 'EnableFlightMode' THEN '3' + WHEN mapping = 'DisableFlightMode' THEN '4' + WHEN mapping = 'ImsiRegistration' THEN '5' + WHEN mapping = 'MoCall' THEN '6' + WHEN mapping = 'MtCall' THEN '7' + WHEN mapping = 'HangUpCall' THEN '8' + WHEN mapping = 'PingTest' THEN '9' + WHEN mapping = 'IperfTest' THEN '10' + ELSE '0' + END + WHERE mapping IS NOT NULL; + "); + + // 然后修改列类型,使用USING子句明确指定转换 + migrationBuilder.Sql(@" + ALTER TABLE tb_casestepconfig + ALTER COLUMN mapping TYPE integer + USING mapping::integer; + "); + + migrationBuilder.CreateTable( + name: "tb_imsi_registration_records", + columns: table => new + { + Id = table.Column(type: "text", nullable: false), + TestCaseId = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + NodeId = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + IsDualSim = table.Column(type: "boolean", nullable: false, defaultValue: false), + Sim1Plmn = table.Column(type: "character varying(20)", maxLength: 20, nullable: true), + Sim1CellId = table.Column(type: "character varying(20)", maxLength: 20, nullable: true), + Sim1RegistrationWaitTime = table.Column(type: "integer", nullable: true), + Sim2Plmn = table.Column(type: "character varying(20)", maxLength: 20, nullable: true), + Sim2CellId = table.Column(type: "character varying(20)", maxLength: 20, nullable: true), + Sim2RegistrationWaitTime = table.Column(type: "integer", nullable: true), + CreatedAt = table.Column(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"), + UpdatedAt = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_tb_imsi_registration_records", x => x.Id); + table.ForeignKey( + name: "FK_tb_imsi_registration_records_tb_testcaseflow_TestCaseId", + column: x => x.TestCaseId, + principalTable: "tb_testcaseflow", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_tb_imsi_registration_records_tb_testcasenode_NodeId", + column: x => x.NodeId, + principalTable: "tb_testcasenode", + principalColumn: "id", + onDelete: ReferentialAction.Cascade); + }, + comment: "IMSI注册记录表"); + + migrationBuilder.CreateIndex( + name: "IX_tb_casestepconfig_createdat", + table: "tb_casestepconfig", + column: "createdat"); + + migrationBuilder.CreateIndex( + name: "IX_tb_casestepconfig_formtype", + table: "tb_casestepconfig", + column: "formtype"); + + migrationBuilder.CreateIndex( + name: "IX_tb_casestepconfig_mapping", + table: "tb_casestepconfig", + column: "mapping"); + + migrationBuilder.CreateIndex( + name: "IX_tb_imsi_registration_records_createdat", + table: "tb_imsi_registration_records", + column: "CreatedAt"); + + migrationBuilder.CreateIndex( + name: "IX_tb_imsi_registration_records_nodeid", + table: "tb_imsi_registration_records", + column: "NodeId"); + + migrationBuilder.CreateIndex( + name: "IX_tb_imsi_registration_records_testcaseid", + table: "tb_imsi_registration_records", + column: "TestCaseId"); + + migrationBuilder.CreateIndex( + name: "IX_tb_imsi_registration_records_testcaseid_nodeid", + table: "tb_imsi_registration_records", + columns: new[] { "TestCaseId", "NodeId" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "tb_imsi_registration_records"); + + migrationBuilder.DropIndex( + name: "IX_tb_casestepconfig_createdat", + table: "tb_casestepconfig"); + + migrationBuilder.DropIndex( + name: "IX_tb_casestepconfig_formtype", + table: "tb_casestepconfig"); + + migrationBuilder.DropIndex( + name: "IX_tb_casestepconfig_mapping", + table: "tb_casestepconfig"); + + migrationBuilder.RenameIndex( + name: "IX_tb_casestepconfig_steptype", + table: "tb_casestepconfig", + newName: "ix_tb_casestepconfig_steptype"); + + migrationBuilder.RenameIndex( + name: "IX_tb_casestepconfig_isenabled", + table: "tb_casestepconfig", + newName: "ix_tb_casestepconfig_isenabled"); + + // 首先将整数值转换回字符串值 + migrationBuilder.Sql(@" + UPDATE tb_casestepconfig + SET mapping = CASE + WHEN mapping = 0 THEN 'None' + WHEN mapping = 1 THEN 'StartFlow' + WHEN mapping = 2 THEN 'EndFlow' + WHEN mapping = 3 THEN 'EnableFlightMode' + WHEN mapping = 4 THEN 'DisableFlightMode' + WHEN mapping = 5 THEN 'ImsiRegistration' + WHEN mapping = 6 THEN 'MoCall' + WHEN mapping = 7 THEN 'MtCall' + WHEN mapping = 8 THEN 'HangUpCall' + WHEN mapping = 9 THEN 'PingTest' + WHEN mapping = 10 THEN 'IperfTest' + ELSE 'None' + END + WHERE mapping IS NOT NULL; + "); + + // 然后修改列类型,使用USING子句明确指定转换 + migrationBuilder.Sql(@" + ALTER TABLE tb_casestepconfig + ALTER COLUMN mapping TYPE character varying(200) + USING mapping::character varying(200); + "); + + migrationBuilder.CreateIndex( + name: "ix_tb_casestepconfig_stepname", + table: "tb_casestepconfig", + column: "stepname"); + } + } +} diff --git a/src/X1.Infrastructure/Migrations/AppDbContextModelSnapshot.cs b/src/X1.Infrastructure/Migrations/AppDbContextModelSnapshot.cs index 24ac683..4407ecf 100644 --- a/src/X1.Infrastructure/Migrations/AppDbContextModelSnapshot.cs +++ b/src/X1.Infrastructure/Migrations/AppDbContextModelSnapshot.cs @@ -1443,10 +1443,8 @@ namespace X1.Infrastructure.Migrations .HasColumnType("boolean") .HasColumnName("isenabled"); - b.Property("Mapping") - .IsRequired() - .HasMaxLength(200) - .HasColumnType("character varying(200)") + b.Property("Mapping") + .HasColumnType("integer") .HasColumnName("mapping"); b.Property("StepName") @@ -1470,18 +1468,90 @@ namespace X1.Infrastructure.Migrations b.HasKey("Id"); - b.HasIndex("IsEnabled") - .HasDatabaseName("ix_tb_casestepconfig_isenabled"); + b.HasIndex("CreatedAt"); + + b.HasIndex("FormType"); - b.HasIndex("StepName") - .HasDatabaseName("ix_tb_casestepconfig_stepname"); + b.HasIndex("IsEnabled"); - b.HasIndex("StepType") - .HasDatabaseName("ix_tb_casestepconfig_steptype"); + b.HasIndex("Mapping"); + + b.HasIndex("StepType"); b.ToTable("tb_casestepconfig", (string)null); }); + modelBuilder.Entity("X1.Domain.Entities.TestCase.ImsiRegistrationRecord", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasDefaultValueSql("GETUTCDATE()"); + + b.Property("IsDualSim") + .ValueGeneratedOnAdd() + .HasColumnType("boolean") + .HasDefaultValue(false); + + b.Property("NodeId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("Sim1CellId") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Sim1Plmn") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Sim1RegistrationWaitTime") + .HasColumnType("integer"); + + b.Property("Sim2CellId") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Sim2Plmn") + .HasMaxLength(20) + .HasColumnType("character varying(20)"); + + b.Property("Sim2RegistrationWaitTime") + .HasColumnType("integer"); + + b.Property("TestCaseId") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt") + .HasDatabaseName("IX_tb_imsi_registration_records_createdat"); + + b.HasIndex("NodeId") + .HasDatabaseName("IX_tb_imsi_registration_records_nodeid"); + + b.HasIndex("TestCaseId") + .HasDatabaseName("IX_tb_imsi_registration_records_testcaseid"); + + b.HasIndex("TestCaseId", "NodeId") + .IsUnique() + .HasDatabaseName("IX_tb_imsi_registration_records_testcaseid_nodeid"); + + b.ToTable("tb_imsi_registration_records", null, t => + { + t.HasComment("IMSI注册记录表"); + }); + }); + modelBuilder.Entity("X1.Domain.Entities.TestCase.TestCaseEdge", b => { b.Property("Id") @@ -1808,6 +1878,25 @@ namespace X1.Infrastructure.Migrations b.Navigation("Role"); }); + modelBuilder.Entity("X1.Domain.Entities.TestCase.ImsiRegistrationRecord", b => + { + b.HasOne("X1.Domain.Entities.TestCase.TestCaseNode", "TestCaseNode") + .WithMany() + .HasForeignKey("NodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("X1.Domain.Entities.TestCase.TestCaseFlow", "TestCase") + .WithMany() + .HasForeignKey("TestCaseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("TestCase"); + + b.Navigation("TestCaseNode"); + }); + modelBuilder.Entity("X1.Domain.Entities.TestCase.TestCaseEdge", b => { b.HasOne("X1.Domain.Entities.TestCase.TestCaseFlow", "TestCase") diff --git a/src/X1.WebUI/src/constants/stepConfigs.ts b/src/X1.WebUI/src/constants/stepConfigs.ts index 6a2d7eb..29096ac 100644 --- a/src/X1.WebUI/src/constants/stepConfigs.ts +++ b/src/X1.WebUI/src/constants/stepConfigs.ts @@ -68,7 +68,7 @@ export const PROCESSING_STEPS = [ icon: 'wifi', label: '开启飞行', stepName: 'EnableFlightMode', - mapping: 'EnableFlightModeController', + mapping: 3, // StepMapping.EnableFlightMode formType: 0, // FormType.None color: 'bg-green-50 dark:bg-green-950/30 border-green-200 dark:border-green-600/50 text-green-700 dark:text-green-300', selectedColor: 'bg-green-100 dark:bg-green-900/50 border-green-300 dark:border-green-500 text-green-800 dark:text-green-200' @@ -77,7 +77,7 @@ export const PROCESSING_STEPS = [ icon: 'wifi-off', label: '关闭飞行', stepName: 'DisableFlightMode', - mapping: 'DisableFlightModeController', + mapping: 4, // StepMapping.DisableFlightMode formType: 0, // FormType.None color: 'bg-red-50 dark:bg-red-950/30 border-red-200 dark:border-red-600/50 text-red-700 dark:text-red-300', selectedColor: 'bg-red-100 dark:bg-red-900/50 border-red-300 dark:border-red-500 text-red-800 dark:text-red-200' @@ -87,17 +87,17 @@ export const PROCESSING_STEPS = [ icon: 'signal', label: 'IMSI注册', stepName: 'ImsiRegistration', - mapping: 'ImsiRegistrationController', + mapping: 5, // StepMapping.ImsiRegistration formType: 1, // FormType.Registration color: 'bg-purple-50 dark:bg-purple-950/30 border-purple-200 dark:border-purple-600/50 text-purple-700 dark:text-purple-300', - selectedColor: 'bg-purple-100 dark:bg-purple-900/50 border-purple-300 dark:border-purple-500 text-purple-800 dark:text-purple-200' + selectedColor: 'bg-purple-100 dark:bg-purple-900/50 border-purple-300 dark:border-purple-505 text-purple-800 dark:text-purple-200' }, // 通话操作 { icon: 'phone-call', label: 'MoCall', stepName: 'MoCall', - mapping: 'MoCallController', + mapping: 6, // StepMapping.MoCall formType: 4, // FormType.Call color: 'bg-orange-50 dark:bg-orange-950/30 border-orange-200 dark:border-orange-600/50 text-orange-700 dark:text-orange-300', selectedColor: 'bg-orange-100 dark:bg-orange-900/50 border-orange-300 dark:border-orange-500 text-orange-800 dark:text-orange-200' @@ -106,7 +106,7 @@ export const PROCESSING_STEPS = [ icon: 'phone', label: 'MTCall', stepName: 'MtCall', - mapping: 'MtCallController', + mapping: 7, // StepMapping.MtCall formType: 4, // FormType.Call color: 'bg-indigo-50 dark:bg-indigo-950/30 border-indigo-200 dark:border-indigo-600/50 text-indigo-700 dark:text-indigo-300', selectedColor: 'bg-indigo-100 dark:bg-indigo-900/50 border-indigo-300 dark:border-indigo-500 text-indigo-800 dark:text-indigo-200' @@ -115,7 +115,7 @@ export const PROCESSING_STEPS = [ icon: 'phone-off', label: '挂电话', stepName: 'HangUpCall', - mapping: 'HangUpCallController', + mapping: 8, // StepMapping.HangUpCall formType: 4, // FormType.Call color: 'bg-gray-50 dark:bg-gray-950/30 border-gray-200 dark:border-gray-600/50 text-gray-700 dark:text-gray-300', selectedColor: 'bg-gray-100 dark:bg-gray-900/50 border-gray-300 dark:border-gray-500 text-gray-800 dark:text-gray-200' @@ -125,7 +125,7 @@ export const PROCESSING_STEPS = [ icon: 'network', label: 'Ping', stepName: 'PingTest', - mapping: 'PingTestController', + mapping: 9, // StepMapping.PingTest formType: 2, // FormType.Ping color: 'bg-teal-50 dark:bg-teal-950/30 border-teal-200 dark:border-teal-600/50 text-teal-700 dark:text-teal-300', selectedColor: 'bg-teal-100 dark:bg-teal-900/50 border-teal-300 dark:border-teal-500 text-teal-800 dark:text-teal-200' @@ -134,7 +134,7 @@ export const PROCESSING_STEPS = [ icon: 'activity', label: 'Iperf', stepName: 'IperfTest', - mapping: 'IperfTestController', + mapping: 10, // StepMapping.IperfTest formType: 3, // FormType.Iperf color: 'bg-cyan-50 dark:bg-cyan-950/30 border-cyan-200 dark:border-cyan-600/50 text-cyan-700 dark:text-cyan-300', selectedColor: 'bg-cyan-100 dark:bg-cyan-900/50 border-cyan-300 dark:border-cyan-500 text-cyan-800 dark:text-cyan-200' @@ -220,7 +220,7 @@ export const getProcessingStepConfig = (stepName: string) => { return PROCESSING_STEPS.find(ps => ps.stepName === stepName); }; -export const getProcessingStepByMapping = (mapping: string) => { +export const getProcessingStepByMapping = (mapping: number | string) => { return PROCESSING_STEPS.find(ps => ps.mapping === mapping); }; diff --git a/src/X1.WebUI/src/pages/teststeps/TestStepDrawer.tsx b/src/X1.WebUI/src/pages/teststeps/TestStepDrawer.tsx new file mode 100644 index 0000000..8912bef --- /dev/null +++ b/src/X1.WebUI/src/pages/teststeps/TestStepDrawer.tsx @@ -0,0 +1,503 @@ +import React, { useEffect, useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Textarea } from '@/components/ui/textarea'; +import { Checkbox } from '@/components/ui/checkbox'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Drawer, DrawerHeader, DrawerContent, DrawerFooter } from '@/components/ui/drawer'; +import { CreateTestStepRequest, UpdateTestStepRequest, FormTypeDto, StepTypeDto, StepMappingDto } from '@/services/teststepsService'; +import { + STEP_TYPES, + PROCESSING_STEPS, + getIconComponent, + getFormTypeIcon, + getDefaultStepName, + getDefaultIcon, + getDefaultFormType +} from '@/constants/stepConfigs'; +import { X } from 'lucide-react'; + +interface TestStepDrawerProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onSubmit: (data: CreateTestStepRequest | UpdateTestStepRequest) => void; + initialData?: Partial & { id?: string }; + isEdit?: boolean; + isSubmitting?: boolean; + existingSteps?: Array<{ stepType: number; stepName: string }>; + formTypes: FormTypeDto[]; + stepTypes: StepTypeDto[]; + stepMappings: StepMappingDto[]; + loadingFormTypes: boolean; +} + +export default function TestStepDrawer({ + open, + onOpenChange, + onSubmit, + initialData, + isEdit = false, + isSubmitting = false, + existingSteps = [], + formTypes, + stepTypes, + stepMappings, + loadingFormTypes +}: TestStepDrawerProps) { + + // 检查步骤类型是否已存在 + const isStepTypeExists = (stepType: number): boolean => { + return existingSteps.some(step => step.stepType === stepType); + }; + + // 检查处理步骤名称是否已存在 + const isProcessingStepNameExists = (stepName: string): boolean => { + return existingSteps.some(step => step.stepType === 3 && step.stepName === stepName); + }; + + // 根据表单类型获取支持的步骤映射 + const getSupportedStepMappings = (formType: number | undefined) => { + // 根据步骤类型和表单类型过滤步骤映射 + if (formData.stepType === 1) { + // 开始步骤:可以选择 StartFlowController(1) 和 EmptyController(0) + return stepMappings.filter(mapping => + mapping.value === 1 || mapping.value === 0 + ); + } else if (formData.stepType === 2) { + // 结束步骤:可以选择 EndFlowController(2) 和 EmptyController(0) + return stepMappings.filter(mapping => + mapping.value === 2 || mapping.value === 0 + ); + } else if (formData.stepType === 3) { + // 处理步骤:根据表单类型过滤,排除通用控制器 + const excludedControllerValues = [0, 1, 2]; // EmptyController, StartFlowController, EndFlowController + + if (formType === undefined || formType === 0) { + return stepMappings.filter(mapping => + !excludedControllerValues.includes(mapping.value) + ); + } + + return stepMappings.filter(mapping => { + if (excludedControllerValues.includes(mapping.value)) { + return false; + } + const processingStep = PROCESSING_STEPS.find(ps => ps.mapping === mapping.value); + return processingStep && processingStep.formType === formType; + }); + } else { + // 判断步骤和其他类型:只显示空控制器 + return stepMappings.filter(mapping => mapping.value === 0); + } + }; + + // 根据步骤映射获取对应的处理步骤配置 + const getProcessingStepByMapping = (mappingValue: number) => { + return PROCESSING_STEPS.find(ps => ps.mapping === mappingValue); + }; + + const [formData, setFormData] = useState({ + stepName: initialData?.stepName || getDefaultStepName(initialData?.stepType || 1), + stepType: initialData?.stepType || 1, + mapping: initialData?.mapping || 0, + description: initialData?.description || '', + icon: initialData?.icon || getDefaultIcon(initialData?.stepType || 1), + isEnabled: initialData?.isEnabled ?? true, + formType: initialData?.formType || getDefaultFormType(initialData?.stepType || 1) + }); + + // 当抽屉打开时,初始化表单数据 + useEffect(() => { + if (open) { + if (initialData && isEdit) { + setFormData({ + stepName: initialData.stepName || getDefaultStepName(initialData.stepType || 1), + stepType: initialData.stepType || 1, + mapping: initialData.mapping || 0, + description: initialData.description || '', + icon: initialData.icon || getDefaultIcon(initialData.stepType || 1), + isEnabled: initialData.isEnabled ?? true, + formType: initialData.formType || getDefaultFormType(initialData.stepType || 1) + }); + } else { + // 重置表单 + setFormData({ + stepName: getDefaultStepName(1), + stepType: 1, + mapping: 0, + description: '', + icon: getDefaultIcon(1), + isEnabled: true, + formType: getDefaultFormType(1) + }); + } + } + }, [open, initialData, isEdit]); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (isSubmitting) return; + + if (isEdit) { + // 编辑模式:只提交可修改的字段 + const updateData: UpdateTestStepRequest = { + id: initialData?.id || '', + description: formData.description, + icon: formData.icon, + mapping: formData.mapping, + isEnabled: formData.isEnabled, + formType: formData.formType + }; + onSubmit(updateData); + } else { + // 创建模式:提交所有字段 + onSubmit(formData); + } + }; + + // 处理步骤类型变化 + const handleStepTypeChange = (stepType: number) => { + const newFormData = { + ...formData, + stepType: stepType, + stepName: getDefaultStepName(stepType), + icon: getDefaultIcon(stepType), + formType: getDefaultFormType(stepType), // 根据步骤类型设置默认表单类型 + mapping: stepType === 3 ? 0 : 0 // 默认使用空控制器(StepMapping.None) + }; + setFormData(newFormData); + }; + + return ( + +
+ +
+

+ {isEdit ? '编辑测试步骤' : '创建测试步骤'} +

+ +
+
+ +
+ + {!isEdit && ( +
+ + setFormData({ ...formData, stepName: e.target.value })} + placeholder="请输入步骤名称" + required + disabled={isSubmitting || (formData.stepType !== 3)} + /> + {(formData.stepType === 1 || formData.stepType === 2 || formData.stepType === 4) && ( +

+ {formData.stepType === 1 && "开始步骤使用固定名称:StartStep"} + {formData.stepType === 2 && "结束步骤使用固定名称:EndStep"} + {formData.stepType === 4 && "判断步骤使用固定名称:DecisionStep"} +

+ )} +
+ )} + + {!isEdit && ( +
+ +
+ {STEP_TYPES.map((type) => { + // 处理步骤(类型3)可以创建多个,其他类型只能创建一个 + const isDisabled = isSubmitting || (!isEdit && type.value !== 3 && isStepTypeExists(type.value)); + return ( + + ); + })} +
+ {/* 显示已存在步骤类型的提示 - 只显示特殊步骤类型(1,2,4)的限制 */} + {!isEdit && (isStepTypeExists(1) || isStepTypeExists(2) || isStepTypeExists(4)) && ( +
+

+ 以下步骤类型已存在,无法重复创建: + {isStepTypeExists(1) && 开始步骤} + {isStepTypeExists(2) && 结束步骤} + {isStepTypeExists(4) && 判断步骤} +

+
+ )} +
+ )} + +
+ +
+ setFormData({ ...formData, icon: e.target.value })} + placeholder="请输入图标名称或路径" + disabled={isSubmitting || formData.stepType !== 3} + className="flex-1" + /> + {formData.icon && ( +
+
+ {getIconComponent(formData.icon)} +
+
+ )} +
+ {formData.stepType === 3 && ( +
+ +
+ {PROCESSING_STEPS.map((iconOption) => { + // 检查该处理类型是否已存在 + const isProcessingTypeExists = isProcessingStepNameExists(iconOption.stepName); + return ( + + ); + })} +
+
+ )} + {formData.stepType !== 3 && ( +

+ {formData.stepType === 1 && "开始步骤使用固定图标"} + {formData.stepType === 2 && "结束步骤使用固定图标"} + {formData.stepType === 4 && "判断步骤使用固定图标"} +

+ )} +
+ + {/* 表单类型选择 */} +
+ +
+ + {formData.formType !== undefined && ( +
+
+ {getFormTypeIcon(formData.formType)} +
+
+ )} +
+

+ {formData.stepType === 1 && "开始步骤默认使用无表单类型"} + {formData.stepType === 2 && "结束步骤默认使用无表单类型"} + {formData.stepType === 4 && "判断步骤默认使用无表单类型"} + {formData.stepType === 3 && "处理步骤可以选择不同的表单类型"} +

+
+ + {/* 步骤映射选择 */} +
+ +
+ + {formData.mapping && ( +
+
+ M +
+
+ )} +
+

+ {formData.stepType === 1 && "开始步骤可选择启动流程控制器或空控制器"} + {formData.stepType === 2 && "结束步骤可选择结束流程控制器或空控制器"} + {formData.stepType === 3 && "处理步骤选择对应的具体操作控制器"} + {formData.stepType === 4 && "判断步骤使用空控制器,不执行具体操作"} + {formData.mapping && formData.mapping !== 0 && ( + + (已选择: {stepMappings.find(m => m.value === formData.mapping)?.name || formData.mapping}) + + )} +

+
+ +
+ +