From 44af041d1aca0e8c3e6e31b0f0e35660082c6a82 Mon Sep 17 00:00:00 2001 From: backuppc Date: Tue, 25 Nov 2025 16:06:35 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20MachineBridge=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20fetch=20API=EB=A5=BC=20HostObject=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C=EB=A1=9C=20=EC=A0=84=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - WebView2 HostObject 기반 MachineBridge 브릿지 클래스 추가 - MachineBridge.cs (메인), Login, Dashboard, Todo, Common, Jobreport, Kuntae, Project 모듈 - WebSocketServer.cs 추가 (실시간 통신용) - fDashboardNew 다이얼로그 추가 - Jobreport/index.html, Project/index.html의 fetch API를 machine HostObject 호출로 전환 - DashBoardController.cs의 gcode null 처리 추가 - 사용하지 않는 파일 삭제 (navigation.html, common-nav.js, navigation.js, _add_to_project.py, _project_updater.js) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Project/Dialog/fDashboardNew.Designer.cs | 63 ++ Project/Dialog/fDashboardNew.cs | 119 ++++ Project/Dialog/fDashboardNew.resx | 120 ++++ Project/EETGW.csproj | 24 +- .../Web/Controllers/DashBoardController.cs | 2 +- .../Web/MachineBridge/MachineBridge.Common.cs | 233 +++++++ .../MachineBridge/MachineBridge.Dashboard.cs | 268 +++++++ .../MachineBridge/MachineBridge.Jobreport.cs | 260 +++++++ .../Web/MachineBridge/MachineBridge.Kuntae.cs | 95 +++ .../Web/MachineBridge/MachineBridge.Login.cs | 293 ++++++++ .../MachineBridge/MachineBridge.Project.cs | 210 ++++++ .../Web/MachineBridge/MachineBridge.Todo.cs | 280 ++++++++ Project/Web/MachineBridge/MachineBridge.cs | 42 ++ Project/Web/WebSocketServer.cs | 221 ++++++ Project/Web/wwwroot/Jobreport/index.html | 653 +++++++----------- Project/Web/wwwroot/Project/index.html | 288 +++----- Project/Web/wwwroot/common/navigation.html | 158 ----- Project/Web/wwwroot/js/common-nav.js | 111 --- Project/Web/wwwroot/js/navigation.js | 195 ------ Project/_add_to_project.py | 158 ----- Project/_project_updater.js | 177 ----- Project/fMain.Designer.cs | 2 + Project/fMain.cs | 84 ++- Project/fMain.resx | 20 +- 24 files changed, 2622 insertions(+), 1454 deletions(-) create mode 100644 Project/Dialog/fDashboardNew.Designer.cs create mode 100644 Project/Dialog/fDashboardNew.cs create mode 100644 Project/Dialog/fDashboardNew.resx create mode 100644 Project/Web/MachineBridge/MachineBridge.Common.cs create mode 100644 Project/Web/MachineBridge/MachineBridge.Dashboard.cs create mode 100644 Project/Web/MachineBridge/MachineBridge.Jobreport.cs create mode 100644 Project/Web/MachineBridge/MachineBridge.Kuntae.cs create mode 100644 Project/Web/MachineBridge/MachineBridge.Login.cs create mode 100644 Project/Web/MachineBridge/MachineBridge.Project.cs create mode 100644 Project/Web/MachineBridge/MachineBridge.Todo.cs create mode 100644 Project/Web/MachineBridge/MachineBridge.cs create mode 100644 Project/Web/WebSocketServer.cs delete mode 100644 Project/Web/wwwroot/common/navigation.html delete mode 100644 Project/Web/wwwroot/js/common-nav.js delete mode 100644 Project/Web/wwwroot/js/navigation.js delete mode 100644 Project/_add_to_project.py delete mode 100644 Project/_project_updater.js diff --git a/Project/Dialog/fDashboardNew.Designer.cs b/Project/Dialog/fDashboardNew.Designer.cs new file mode 100644 index 0000000..77bec37 --- /dev/null +++ b/Project/Dialog/fDashboardNew.Designer.cs @@ -0,0 +1,63 @@ +namespace Project.Dialog +{ + partial class fDashboardNew + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label1 = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // label1 + // + this.label1.Dock = System.Windows.Forms.DockStyle.Fill; + this.label1.Font = new System.Drawing.Font("맑은 고딕", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129))); + this.label1.ForeColor = System.Drawing.Color.DimGray; + this.label1.Location = new System.Drawing.Point(0, 0); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(1063, 567); + this.label1.TabIndex = 0; + this.label1.Text = "요약 화면 구성 중 입니다.\r\n\r\n업무일지는 \"관리->업무일지->목록\" 을 사용하세요"; + this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; + this.label1.Click += new System.EventHandler(this.label1_Click); + // + // fDashboard + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(1063, 567); + this.Controls.Add(this.label1); + this.Name = "fDashboard"; + this.Text = "요약"; + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Label label1; + } +} \ No newline at end of file diff --git a/Project/Dialog/fDashboardNew.cs b/Project/Dialog/fDashboardNew.cs new file mode 100644 index 0000000..031f011 --- /dev/null +++ b/Project/Dialog/fDashboardNew.cs @@ -0,0 +1,119 @@ +using FCM0000.Mail; +using FCOMMON; +using Microsoft.Web.WebView2.Core; +using Microsoft.Web.WebView2.WinForms; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Project.Dialog +{ + public partial class fDashboardNew : fBase + { + private Web.WebSocketServer _wsServer; + private WebView2 webView; + public fDashboardNew() + { + InitializeComponent(); + + + InitializeWebView2(); + + try + { + _wsServer = new Web.WebSocketServer("http://localhost:8081/", this); + } + catch (Exception ex) + { + MessageBox.Show("Failed to start WebSocket Server (Port 8081). Run as Admin or allow port.\n" + ex.Message); + } + } + bool loadok = false; + public void RefreshView() + { + if (loadok) + webView.Reload(); + } + private void InitializeWebView2() + { + // 수동으로 WebView2 컨트롤 생성 + this.webView = new WebView2(); + + // 기본 속성 설정 + this.webView.CreationProperties = null; + this.webView.DefaultBackgroundColor = Color.White; + this.webView.Dock = DockStyle.Fill; + this.webView.Location = new Point(0, 0); + this.webView.Name = "webView21"; + this.webView.Size = new Size(800, 600); + this.webView.TabIndex = 0; + this.webView.ZoomFactor = 1D; + + // 폼에 추가 + this.Controls.Add(this.webView); + + // 비동기 초기화 + InitializeAsync(); + } + private async void InitializeAsync() + { + try + { + // Fixed Version 경로 설정 + string runtimePath = Path.Combine(Application.StartupPath, "WebView2Runtime"); + + if (Directory.Exists(runtimePath)) + { + var env = await CoreWebView2Environment.CreateAsync(runtimePath); + await this.webView.EnsureCoreWebView2Async(env); + } + else + { + // 시스템에 설치된 WebView2 사용 + await this.webView.EnsureCoreWebView2Async(); + } + + var wwwroot = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Web", "wwwroot"); + webView.CoreWebView2.SetVirtualHostNameToFolderMapping( + "hmi.local", + wwwroot, + CoreWebView2HostResourceAccessKind.Allow); + + + // 2. Inject Native Object + webView.CoreWebView2.AddHostObjectToScript("machine", new Web.MachineBridge(this)); + + Pub.WebServiceURL = "http://hmi.local"; + + // OWIN 서버의 DashBoard 페이지로 연결 + if (FCOMMON.info.Login.no.isEmpty()) + webView.Source = new Uri($"{Pub.WebServiceURL}/login.html"); + else + webView.Source = new Uri($"{Pub.WebServiceURL}/DashBoard"); + + label1.Visible = false; + loadok = true; + } + catch (Exception ex) + { + MessageBox.Show($"WebView2 초기화 실패: {ex.Message}"); + } + } + protected override void OnLoad(EventArgs e) + { + base.OnLoad(e); + EnsureVisibleAndUsableSize(); + } + private void label1_Click(object sender, EventArgs e) + { + + } + } +} diff --git a/Project/Dialog/fDashboardNew.resx b/Project/Dialog/fDashboardNew.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/Project/Dialog/fDashboardNew.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Project/EETGW.csproj b/Project/EETGW.csproj index 14ae08e..b3324b6 100644 --- a/Project/EETGW.csproj +++ b/Project/EETGW.csproj @@ -259,6 +259,12 @@ fCommon.cs + + Form + + + fDashboardNew.cs + Form @@ -424,6 +430,14 @@ + + + + + + + + @@ -432,6 +446,7 @@ + Form @@ -495,6 +510,9 @@ fCommon.cs + + fDashboardNew.cs + fHolyday.cs @@ -686,9 +704,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -707,9 +722,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest diff --git a/Project/Web/Controllers/DashBoardController.cs b/Project/Web/Controllers/DashBoardController.cs index 8ad5471..80979c2 100644 --- a/Project/Web/Controllers/DashBoardController.cs +++ b/Project/Web/Controllers/DashBoardController.cs @@ -287,7 +287,7 @@ namespace Project.Web.Controllers var cs = Properties.Settings.Default.gwcs;// "Data Source=K4FASQL.kr.ds.amkor.com,50150;Initial Catalog=EE;Persist Security Info=True;User ID=eeadm;Password=uJnU8a8q&DJ+ug-D!"; var cn = new System.Data.SqlClient.SqlConnection(cs); var cmd = new System.Data.SqlClient.SqlCommand(sql, cn); - cmd.Parameters.AddWithValue("gcode", FCOMMON.info.Login.gcode); + cmd.Parameters.AddWithValue("gcode", FCOMMON.info.Login.gcode ?? string.Empty); var da = new System.Data.SqlClient.SqlDataAdapter(cmd); var dt = new System.Data.DataTable(); da.Fill(dt); diff --git a/Project/Web/MachineBridge/MachineBridge.Common.cs b/Project/Web/MachineBridge/MachineBridge.Common.cs new file mode 100644 index 0000000..c6fbb17 --- /dev/null +++ b/Project/Web/MachineBridge/MachineBridge.Common.cs @@ -0,0 +1,233 @@ +using System; +using System.Data; +using System.Data.SqlClient; +using Newtonsoft.Json; +using FCOMMON; + +namespace Project.Web +{ + public partial class MachineBridge + { + #region Common API + + /// + /// 네비게이션 메뉴 조회 + /// + public string GetNavigationMenu() + { + try + { + var menuItems = new[] + { + new { key = "dashboard", title = "대시보드", url = "/DashBoard/index.html", icon = "M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2H5a2 2 0 00-2-2z M8 5a2 2 0 012-2h4a2 2 0 012 2v2H8V5z", isVisible = true, sortOrder = 1 }, + new { key = "common", title = "공용코드", url = "/Common.html", icon = "M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z", isVisible = true, sortOrder = 2 }, + new { key = "jobreport", title = "업무일지", url = "/Jobreport/index.html", icon = "M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2", isVisible = true, sortOrder = 3 }, + new { key = "kuntae", title = "근태관리", url = "/Kuntae/index.html", icon = "M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z", isVisible = true, sortOrder = 4 }, + new { key = "todo", title = "할일관리", url = "/Todo/index.html", icon = "M9 5H7a2 2 0 00-2 2v10a2 2 0 002 2h8a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2M12 12l2 2 4-4", isVisible = true, sortOrder = 5 }, + new { key = "project", title = "프로젝트", url = "/Project/index.html", icon = "M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10", isVisible = true, sortOrder = 6 } + }; + + return JsonConvert.SerializeObject(new { Success = true, Data = menuItems, Message = "메뉴 정보를 성공적으로 가져왔습니다." }); + } + catch (Exception ex) + { + return JsonConvert.SerializeObject(new { Success = false, Data = (object)null, Message = "메뉴 정보를 가져오는 중 오류가 발생했습니다: " + ex.Message }); + } + } + + /// + /// 공용코드 그룹 목록 조회 + /// + public string Common_GetGroups() + { + try + { + var sql = "select code, svalue, memo from common WITH (nolock) " + + "where gcode = @gcode and grp = '99' " + + "order by code"; + + var cs = Properties.Settings.Default.gwcs; + var cn = new SqlConnection(cs); + var cmd = new SqlCommand(sql, cn); + cmd.Parameters.AddWithValue("@gcode", info.Login.gcode); + + var da = new SqlDataAdapter(cmd); + var dt = new DataTable(); + da.Fill(dt); + da.Dispose(); + cmd.Dispose(); + cn.Dispose(); + + return JsonConvert.SerializeObject(dt, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + } + catch (Exception ex) + { + Console.WriteLine($"Common_GetGroups 오류: {ex.Message}"); + return "[]"; + } + } + + /// + /// 공용코드 목록 조회 + /// + public string Common_GetList(string grp) + { + try + { + if (string.IsNullOrEmpty(grp)) grp = "99"; + + var sql = "select * from common where gcode = @gcode and grp = @grp order by code, svalue"; + + var cs = Properties.Settings.Default.gwcs; + var cn = new SqlConnection(cs); + var cmd = new SqlCommand(sql, cn); + cmd.Parameters.AddWithValue("@gcode", info.Login.gcode); + cmd.Parameters.AddWithValue("@grp", grp); + + var da = new SqlDataAdapter(cmd); + var dt = new DataTable(); + da.Fill(dt); + da.Dispose(); + cmd.Dispose(); + cn.Dispose(); + + return JsonConvert.SerializeObject(dt, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + } + catch (Exception ex) + { + Console.WriteLine($"Common_GetList 오류: {ex.Message}"); + return "[]"; + } + } + + /// + /// 공용코드 저장 + /// + public string Common_Save(int idx, string grp, string code, string svalue, int ivalue, float fvalue, string svalue2, string memo) + { + try + { + var cs = Properties.Settings.Default.gwcs; + var cn = new SqlConnection(cs); + var sql = string.Empty; + var cmd = new SqlCommand(); + cmd.Connection = cn; + + if (idx > 0) + { + // 업데이트 + sql = @"UPDATE common SET + grp = @grp, + code = @code, + svalue = @svalue, + ivalue = @ivalue, + fvalue = @fvalue, + svalue2 = @svalue2, + memo = @memo, + wuid = @wuid, + wdate = GETDATE() + WHERE idx = @idx AND gcode = @gcode"; + } + else + { + // 신규 추가 + sql = @"INSERT INTO common (gcode, grp, code, svalue, ivalue, fvalue, svalue2, memo, wuid, wdate) + VALUES (@gcode, @grp, @code, @svalue, @ivalue, @fvalue, @svalue2, @memo, @wuid, GETDATE())"; + } + + cmd.CommandText = sql; + cmd.Parameters.AddWithValue("@gcode", info.Login.gcode); + cmd.Parameters.AddWithValue("@grp", grp ?? ""); + cmd.Parameters.AddWithValue("@code", code ?? ""); + cmd.Parameters.AddWithValue("@svalue", svalue ?? ""); + cmd.Parameters.AddWithValue("@ivalue", ivalue); + cmd.Parameters.AddWithValue("@fvalue", fvalue); + cmd.Parameters.AddWithValue("@svalue2", svalue2 ?? ""); + cmd.Parameters.AddWithValue("@memo", memo ?? ""); + cmd.Parameters.AddWithValue("@wuid", info.Login.no); + + if (idx > 0) + { + cmd.Parameters.AddWithValue("@idx", idx); + } + + cn.Open(); + var result = cmd.ExecuteNonQuery(); + cn.Close(); + + cmd.Dispose(); + cn.Dispose(); + + return JsonConvert.SerializeObject(new { Success = result > 0, Message = result > 0 ? "저장되었습니다." : "저장에 실패했습니다." }); + } + catch (Exception ex) + { + return JsonConvert.SerializeObject(new { Success = false, Message = "오류가 발생했습니다: " + ex.Message }); + } + } + + /// + /// 공용코드 삭제 + /// + public string Common_Delete(int idx) + { + try + { + var cs = Properties.Settings.Default.gwcs; + var cn = new SqlConnection(cs); + var sql = "DELETE FROM common WHERE idx = @idx AND gcode = @gcode"; + var cmd = new SqlCommand(sql, cn); + + cmd.Parameters.AddWithValue("@idx", idx); + cmd.Parameters.AddWithValue("@gcode", info.Login.gcode); + + cn.Open(); + var result = cmd.ExecuteNonQuery(); + cn.Close(); + + cmd.Dispose(); + cn.Dispose(); + + return JsonConvert.SerializeObject(new { Success = result > 0, Message = result > 0 ? "삭제되었습니다." : "삭제에 실패했습니다." }); + } + catch (Exception ex) + { + return JsonConvert.SerializeObject(new { Success = false, Message = "오류가 발생했습니다: " + ex.Message }); + } + } + + /// + /// 현재 로그인 사용자 정보 조회 + /// + public string GetCurrentUser() + { + try + { + if (string.IsNullOrEmpty(info.Login.no)) + { + return JsonConvert.SerializeObject(new { Success = false, Message = "로그인되지 않은 상태입니다." }); + } + + return JsonConvert.SerializeObject(new + { + Success = true, + Data = new + { + id = info.Login.no, + name = info.Login.nameK, + userName = info.Login.nameK, + email = info.Login.email, + dept = info.Login.dept, + gcode = info.Login.gcode + } + }); + } + catch (Exception ex) + { + return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message }); + } + } + + #endregion + } +} diff --git a/Project/Web/MachineBridge/MachineBridge.Dashboard.cs b/Project/Web/MachineBridge/MachineBridge.Dashboard.cs new file mode 100644 index 0000000..36031e9 --- /dev/null +++ b/Project/Web/MachineBridge/MachineBridge.Dashboard.cs @@ -0,0 +1,268 @@ +using System; +using System.Data; +using System.Data.SqlClient; +using Newtonsoft.Json; +using FCOMMON; + +namespace Project.Web +{ + public partial class MachineBridge + { + #region Dashboard API + + /// + /// 오늘 휴가 인원 수 조회 + /// + public string TodayCountH() + { + try + { + var sql = "select count(*) from EETGW_HolydayRequest WITH (nolock) " + + " where gcode = @gcode and isnull(conf,0) = 1 " + + " and sdate <= convert(varchar(10),GETDATE(),120) and edate >= convert(varchar(10),GETDATE(),120)"; + + var cn = DBM.getCn(); + cn.Open(); + var cmd = new SqlCommand(sql, cn); + cmd.Parameters.Add("gcode", SqlDbType.VarChar).Value = info.Login.gcode; + var cnt = (int)cmd.ExecuteScalar(); + cmd.Dispose(); + cn.Dispose(); + + return cnt.ToString(); + } + catch (Exception ex) + { + Console.WriteLine($"TodayCountH 오류: {ex.Message}"); + return "0"; + } + } + + /// + /// 휴가요청 대기 건수 조회 + /// + public string GetHolydayRequestCount() + { + try + { + var sql = "select count(*) from EETGW_HolydayRequest WITH (nolock) " + + " where gcode = @gcode and isnull(conf,0) = 0"; + + var cn = DBM.getCn(); + cn.Open(); + var cmd = new SqlCommand(sql, cn); + cmd.Parameters.Add("gcode", SqlDbType.VarChar).Value = info.Login.gcode; + var cnt = (int)cmd.ExecuteScalar(); + cmd.Dispose(); + cn.Dispose(); + + return JsonConvert.SerializeObject(new { HOLY = cnt, Message = "" }); + } + catch (Exception ex) + { + return JsonConvert.SerializeObject(new { HOLY = 0, Message = ex.Message }); + } + } + + /// + /// 현재 출근 대상 인원 수 조회 + /// + public string GetCurrentUserCount() + { + try + { + var sql = "select count(*) from vGroupUser WITH (nolock) " + + " where gcode = @gcode and useUserState = 1 and useJobReport = 1" + + " and id not in (select uid from vEETGW_TodayNoneWorkUser where gcode = @gcode and kunmu = 0)"; + + var cn = DBM.getCn(); + cn.Open(); + var cmd = new SqlCommand(sql, cn); + cmd.Parameters.Add("gcode", SqlDbType.VarChar).Value = info.Login.gcode; + var cnt = (int)cmd.ExecuteScalar(); + cmd.Dispose(); + cn.Dispose(); + + return JsonConvert.SerializeObject(new { Count = cnt, Message = "" }); + } + catch (Exception ex) + { + return JsonConvert.SerializeObject(new { Count = 0, Message = ex.Message }); + } + } + + /// + /// 구매요청 대기 건수 조회 (NR, CR) + /// + public string GetPurchaseWaitCount() + { + try + { + DBM.GetPurchaseWaitCount(info.Login.gcode, out int cnt1, out int cnt2); + return JsonConvert.SerializeObject(new { NR = cnt1, CR = cnt2, Message = "" }); + } + catch (Exception ex) + { + return JsonConvert.SerializeObject(new { NR = 0, CR = 0, Message = ex.Message }); + } + } + + /// + /// 휴가자 목록 조회 + /// + public string GetHolyUser() + { + try + { + var sql = " select uid,type,cate,sdate,edate,title,dbo.getusername(uid) as name " + + " from vEETGW_TodayNoneWorkUser WITH (nolock)" + + " where gcode = @gcode and kunmu=0"; + + var cs = Properties.Settings.Default.gwcs; + var cn = new SqlConnection(cs); + var cmd = new SqlCommand(sql, cn); + cmd.Parameters.AddWithValue("gcode", info.Login.gcode ?? ""); + var da = new SqlDataAdapter(cmd); + var dt = new DataTable(); + da.Fill(dt); + da.Dispose(); + cmd.Dispose(); + cn.Dispose(); + + return JsonConvert.SerializeObject(dt, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + } + catch (Exception ex) + { + Console.WriteLine($"GetHolyUser 오류: {ex.Message}"); + return "[]"; + } + } + + /// + /// 휴가요청 목록 조회 + /// + public string GetHolyRequestUser() + { + try + { + var sql = " select uid,cate,sdate,edate,HolyReason,Users.name,holydays,holytimes,remark " + + " from EETGW_HolydayRequest WITH (nolock) INNER JOIN " + + " Users ON EETGW_HolydayRequest.uid = Users.id " + + " where EETGW_HolydayRequest.gcode = @gcode" + + " and isnull(conf,0) = 0"; + + var cs = Properties.Settings.Default.gwcs; + var cn = new SqlConnection(cs); + var cmd = new SqlCommand(sql, cn); + cmd.Parameters.AddWithValue("gcode", info.Login.gcode); + var da = new SqlDataAdapter(cmd); + var dt = new DataTable(); + da.Fill(dt); + da.Dispose(); + cmd.Dispose(); + cn.Dispose(); + + return JsonConvert.SerializeObject(dt, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + } + catch (Exception ex) + { + Console.WriteLine($"GetHolyRequestUser 오류: {ex.Message}"); + return "[]"; + } + } + + /// + /// 출근 대상자 목록 조회 + /// + public string GetPresentUserList() + { + try + { + var sql = "select * from vGroupUser WITH (nolock) " + + " where gcode = @gcode and useUserState = 1 and useJobReport = 1" + + " and id not in (select uid from vEETGW_TodayNoneWorkUser where gcode = @gcode and kunmu = 0)"; + + var cs = Properties.Settings.Default.gwcs; + var cn = new SqlConnection(cs); + var cmd = new SqlCommand(sql, cn); + cmd.Parameters.AddWithValue("gcode", info.Login.gcode); + var da = new SqlDataAdapter(cmd); + var dt = new DataTable(); + da.Fill(dt); + da.Dispose(); + cmd.Dispose(); + cn.Dispose(); + + return JsonConvert.SerializeObject(dt, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + } + catch (Exception ex) + { + Console.WriteLine($"GetPresentUserList 오류: {ex.Message}"); + return "[]"; + } + } + + /// + /// 구매요청(NR) 목록 조회 + /// + public string GetPurchaseNRList() + { + try + { + var sql = "select pdate, process, pumname, pumscale, pumunit, pumqtyreq, pumprice, pumamt from Purchase WITH (nolock) where gcode = @gcode and state = '---' order by pdate desc"; + + var cs = Properties.Settings.Default.gwcs; + var cn = new SqlConnection(cs); + var cmd = new SqlCommand(sql, cn); + cmd.Parameters.AddWithValue("gcode", info.Login.gcode); + var da = new SqlDataAdapter(cmd); + var dt = new DataTable(); + da.Fill(dt); + da.Dispose(); + cmd.Dispose(); + cn.Dispose(); + + return JsonConvert.SerializeObject(dt, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + } + catch (Exception ex) + { + Console.WriteLine($"GetPurchaseNRList 오류: {ex.Message}"); + return "[]"; + } + } + + /// + /// 구매요청(CR) 목록 조회 + /// + public string GetPurchaseCRList() + { + try + { + var sql = "select pdate, process, pumname, pumscale, pumunit, pumqtyreq, pumprice, pumamt " + + " from EETGW_PurchaseCR WITH (nolock) " + + " where gcode = @gcode and state = '---'" + + " order by pdate desc"; + + var cs = Properties.Settings.Default.gwcs; + var cn = new SqlConnection(cs); + var cmd = new SqlCommand(sql, cn); + cmd.Parameters.AddWithValue("gcode", info.Login.gcode); + var da = new SqlDataAdapter(cmd); + var dt = new DataTable(); + da.Fill(dt); + da.Dispose(); + cmd.Dispose(); + cn.Dispose(); + + return JsonConvert.SerializeObject(dt, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + } + catch (Exception ex) + { + Console.WriteLine($"GetPurchaseCRList 오류: {ex.Message}"); + return "[]"; + } + } + + #endregion + } +} diff --git a/Project/Web/MachineBridge/MachineBridge.Jobreport.cs b/Project/Web/MachineBridge/MachineBridge.Jobreport.cs new file mode 100644 index 0000000..088843c --- /dev/null +++ b/Project/Web/MachineBridge/MachineBridge.Jobreport.cs @@ -0,0 +1,260 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.SqlClient; +using Newtonsoft.Json; +using FCOMMON; + +namespace Project.Web +{ + public partial class MachineBridge + { + #region Jobreport API + + /// + /// 업무일지 목록 조회 + /// + public string Jobreport_GetList(string sd, string ed, string uid, string cate, string doit) + { + try + { + var sql = @"SELECT j.idx, j.jdate, j.uid, j.cate, j.title, j.doit, j.remark, j.jfrom, j.jto, + u.name as userName, j.wdate + FROM EETGW_Jobreport j WITH (nolock) + LEFT JOIN Users u ON j.uid = u.id + WHERE j.gcode = @gcode"; + + var parameters = new List(); + parameters.Add(new SqlParameter("@gcode", info.Login.gcode)); + + if (!string.IsNullOrEmpty(sd)) + { + sql += " AND j.jdate >= @sd"; + parameters.Add(new SqlParameter("@sd", sd)); + } + if (!string.IsNullOrEmpty(ed)) + { + sql += " AND j.jdate <= @ed"; + parameters.Add(new SqlParameter("@ed", ed)); + } + if (!string.IsNullOrEmpty(uid)) + { + sql += " AND j.uid = @uid"; + parameters.Add(new SqlParameter("@uid", uid)); + } + if (!string.IsNullOrEmpty(cate)) + { + sql += " AND j.cate = @cate"; + parameters.Add(new SqlParameter("@cate", cate)); + } + if (!string.IsNullOrEmpty(doit)) + { + sql += " AND j.doit = @doit"; + parameters.Add(new SqlParameter("@doit", doit)); + } + + sql += " ORDER BY j.jdate DESC, j.idx DESC"; + + var cs = Properties.Settings.Default.gwcs; + var cn = new SqlConnection(cs); + var cmd = new SqlCommand(sql, cn); + cmd.Parameters.AddRange(parameters.ToArray()); + + var da = new SqlDataAdapter(cmd); + var dt = new DataTable(); + da.Fill(dt); + da.Dispose(); + cmd.Dispose(); + cn.Dispose(); + + return JsonConvert.SerializeObject(new { Success = true, Data = dt }, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + } + catch (Exception ex) + { + return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message }); + } + } + + /// + /// 업무일지 사용자 목록 조회 + /// + public string Jobreport_GetUsers() + { + try + { + var sql = @"SELECT u.id, u.name + FROM vGroupUser u WITH (nolock) + WHERE u.gcode = @gcode AND u.useJobReport = 1 + ORDER BY u.name"; + + var cs = Properties.Settings.Default.gwcs; + var cn = new SqlConnection(cs); + var cmd = new SqlCommand(sql, cn); + cmd.Parameters.AddWithValue("@gcode", info.Login.gcode); + + var da = new SqlDataAdapter(cmd); + var dt = new DataTable(); + da.Fill(dt); + da.Dispose(); + cmd.Dispose(); + cn.Dispose(); + + return JsonConvert.SerializeObject(dt, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + } + catch (Exception ex) + { + Console.WriteLine($"Jobreport_GetUsers 오류: {ex.Message}"); + return "[]"; + } + } + + /// + /// 업무일지 상세 조회 + /// + public string Jobreport_GetDetail(int id) + { + try + { + var sql = @"SELECT j.*, u.name as userName + FROM EETGW_Jobreport j WITH (nolock) + LEFT JOIN Users u ON j.uid = u.id + WHERE j.idx = @idx AND j.gcode = @gcode"; + + var cs = Properties.Settings.Default.gwcs; + var cn = new SqlConnection(cs); + var cmd = new SqlCommand(sql, cn); + cmd.Parameters.AddWithValue("@idx", id); + cmd.Parameters.AddWithValue("@gcode", info.Login.gcode); + + var da = new SqlDataAdapter(cmd); + var dt = new DataTable(); + da.Fill(dt); + da.Dispose(); + cmd.Dispose(); + cn.Dispose(); + + if (dt.Rows.Count > 0) + { + return JsonConvert.SerializeObject(new { Success = true, Data = dt.Rows[0] }, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + } + + return JsonConvert.SerializeObject(new { Success = false, Message = "데이터를 찾을 수 없습니다." }); + } + catch (Exception ex) + { + return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message }); + } + } + + /// + /// 업무일지 추가 + /// + public string Jobreport_Add(string jdate, string cate, string title, string doit, string remark, string jfrom, string jto) + { + try + { + var sql = @"INSERT INTO EETGW_Jobreport (gcode, uid, jdate, cate, title, doit, remark, jfrom, jto, wuid, wdate) + VALUES (@gcode, @uid, @jdate, @cate, @title, @doit, @remark, @jfrom, @jto, @wuid, GETDATE()); + SELECT SCOPE_IDENTITY();"; + + var cs = Properties.Settings.Default.gwcs; + var cn = new SqlConnection(cs); + var cmd = new SqlCommand(sql, cn); + cmd.Parameters.AddWithValue("@gcode", info.Login.gcode); + cmd.Parameters.AddWithValue("@uid", info.Login.no); + cmd.Parameters.AddWithValue("@jdate", jdate ?? DateTime.Now.ToString("yyyy-MM-dd")); + cmd.Parameters.AddWithValue("@cate", cate ?? ""); + cmd.Parameters.AddWithValue("@title", title ?? ""); + cmd.Parameters.AddWithValue("@doit", doit ?? ""); + cmd.Parameters.AddWithValue("@remark", remark ?? ""); + cmd.Parameters.AddWithValue("@jfrom", jfrom ?? ""); + cmd.Parameters.AddWithValue("@jto", jto ?? ""); + cmd.Parameters.AddWithValue("@wuid", info.Login.no); + + cn.Open(); + var newId = Convert.ToInt32(cmd.ExecuteScalar()); + cn.Close(); + cmd.Dispose(); + cn.Dispose(); + + return JsonConvert.SerializeObject(new { Success = true, Message = "저장되었습니다.", Data = new { idx = newId } }); + } + catch (Exception ex) + { + return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message }); + } + } + + /// + /// 업무일지 수정 + /// + public string Jobreport_Edit(int idx, string jdate, string cate, string title, string doit, string remark, string jfrom, string jto) + { + try + { + var sql = @"UPDATE EETGW_Jobreport SET + jdate = @jdate, cate = @cate, title = @title, doit = @doit, + remark = @remark, jfrom = @jfrom, jto = @jto, + wuid = @wuid, wdate = GETDATE() + WHERE idx = @idx AND gcode = @gcode"; + + var cs = Properties.Settings.Default.gwcs; + var cn = new SqlConnection(cs); + var cmd = new SqlCommand(sql, cn); + cmd.Parameters.AddWithValue("@idx", idx); + cmd.Parameters.AddWithValue("@gcode", info.Login.gcode); + cmd.Parameters.AddWithValue("@jdate", jdate ?? DateTime.Now.ToString("yyyy-MM-dd")); + cmd.Parameters.AddWithValue("@cate", cate ?? ""); + cmd.Parameters.AddWithValue("@title", title ?? ""); + cmd.Parameters.AddWithValue("@doit", doit ?? ""); + cmd.Parameters.AddWithValue("@remark", remark ?? ""); + cmd.Parameters.AddWithValue("@jfrom", jfrom ?? ""); + cmd.Parameters.AddWithValue("@jto", jto ?? ""); + cmd.Parameters.AddWithValue("@wuid", info.Login.no); + + cn.Open(); + var result = cmd.ExecuteNonQuery(); + cn.Close(); + cmd.Dispose(); + cn.Dispose(); + + return JsonConvert.SerializeObject(new { Success = result > 0, Message = result > 0 ? "수정되었습니다." : "수정에 실패했습니다." }); + } + catch (Exception ex) + { + return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message }); + } + } + + /// + /// 업무일지 삭제 + /// + public string Jobreport_Delete(int idx) + { + try + { + var sql = "DELETE FROM EETGW_Jobreport WHERE idx = @idx AND gcode = @gcode"; + + var cs = Properties.Settings.Default.gwcs; + var cn = new SqlConnection(cs); + var cmd = new SqlCommand(sql, cn); + cmd.Parameters.AddWithValue("@idx", idx); + cmd.Parameters.AddWithValue("@gcode", info.Login.gcode); + + cn.Open(); + var result = cmd.ExecuteNonQuery(); + cn.Close(); + cmd.Dispose(); + cn.Dispose(); + + return JsonConvert.SerializeObject(new { Success = result > 0, Message = result > 0 ? "삭제되었습니다." : "삭제에 실패했습니다." }); + } + catch (Exception ex) + { + return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message }); + } + } + + #endregion + } +} diff --git a/Project/Web/MachineBridge/MachineBridge.Kuntae.cs b/Project/Web/MachineBridge/MachineBridge.Kuntae.cs new file mode 100644 index 0000000..b59cb95 --- /dev/null +++ b/Project/Web/MachineBridge/MachineBridge.Kuntae.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.SqlClient; +using Newtonsoft.Json; +using FCOMMON; + +namespace Project.Web +{ + public partial class MachineBridge + { + #region Kuntae API + + /// + /// 근태 목록 조회 + /// + public string Kuntae_GetList(string sd, string ed) + { + try + { + var sql = @"SELECT k.*, u.name as userName + FROM EETGW_Kuntae k WITH (nolock) + LEFT JOIN Users u ON k.uid = u.id + WHERE k.gcode = @gcode AND k.uid = @uid"; + + var parameters = new List(); + parameters.Add(new SqlParameter("@gcode", info.Login.gcode)); + parameters.Add(new SqlParameter("@uid", info.Login.no)); + + if (!string.IsNullOrEmpty(sd)) + { + sql += " AND k.kdate >= @sd"; + parameters.Add(new SqlParameter("@sd", sd)); + } + if (!string.IsNullOrEmpty(ed)) + { + sql += " AND k.kdate <= @ed"; + parameters.Add(new SqlParameter("@ed", ed)); + } + + sql += " ORDER BY k.kdate DESC"; + + var cs = Properties.Settings.Default.gwcs; + var cn = new SqlConnection(cs); + var cmd = new SqlCommand(sql, cn); + cmd.Parameters.AddRange(parameters.ToArray()); + + var da = new SqlDataAdapter(cmd); + var dt = new DataTable(); + da.Fill(dt); + da.Dispose(); + cmd.Dispose(); + cn.Dispose(); + + return JsonConvert.SerializeObject(new { Success = true, Data = dt }, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + } + catch (Exception ex) + { + return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message }); + } + } + + /// + /// 근태 삭제 + /// + public string Kuntae_Delete(int id) + { + try + { + var sql = "DELETE FROM EETGW_Kuntae WHERE idx = @idx AND gcode = @gcode AND uid = @uid"; + + var cs = Properties.Settings.Default.gwcs; + var cn = new SqlConnection(cs); + var cmd = new SqlCommand(sql, cn); + cmd.Parameters.AddWithValue("@idx", id); + cmd.Parameters.AddWithValue("@gcode", info.Login.gcode); + cmd.Parameters.AddWithValue("@uid", info.Login.no); + + cn.Open(); + var result = cmd.ExecuteNonQuery(); + cn.Close(); + cmd.Dispose(); + cn.Dispose(); + + return JsonConvert.SerializeObject(new { Success = result > 0, Message = result > 0 ? "삭제되었습니다." : "삭제에 실패했습니다." }); + } + catch (Exception ex) + { + return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message }); + } + } + + #endregion + } +} diff --git a/Project/Web/MachineBridge/MachineBridge.Login.cs b/Project/Web/MachineBridge/MachineBridge.Login.cs new file mode 100644 index 0000000..7761442 --- /dev/null +++ b/Project/Web/MachineBridge/MachineBridge.Login.cs @@ -0,0 +1,293 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Windows.Forms; +using Newtonsoft.Json; +using FCOMMON; + +namespace Project.Web +{ + public partial class MachineBridge + { + #region Login API + + /// + /// 로그인 처리 (fLogin_WB.cs의 button1_Click 로직 참고) + /// + public string Login(string gcode, string id, string password, bool rememberMe) + { + var result = new LoginResult(); + DateTime dt = DateTime.Now; + + try + { + if (string.IsNullOrEmpty(id)) + { + result.Success = false; + result.Message = "사용자 ID를 입력하세요."; + return JsonConvert.SerializeObject(result); + } + + if (string.IsNullOrEmpty(password)) + { + result.Success = false; + result.Message = "비밀번호를 입력하세요."; + return JsonConvert.SerializeObject(result); + } + + if (string.IsNullOrEmpty(gcode)) + { + result.Success = false; + result.Message = "소속 부서를 선택하세요."; + return JsonConvert.SerializeObject(result); + } + + // 부서명 조회 + var deptName = DBM.ExecuteScalar($"SELECT dept FROM UserGroup WHERE gcode = '{gcode}'") ?? ""; + + var encpass = Pub.MakePasswordEnc(password.Trim()); + + // 개발자 계정 처리 + if (id.ToUpper().Equals("DEV")) + { + if (!password.Equals("123")) + { + result.Success = false; + result.Message = "암호가 일치하지 않습니다."; + return JsonConvert.SerializeObject(result); + } + + SetDevLoginInfo(gcode, deptName); + } + else + { + // 일반 사용자 로그인 처리 + var taGrpUser = new dsMSSQLTableAdapters.EETGW_GroupUserTableAdapter(); + var drGrpUser = taGrpUser.GetbyID(gcode, id).FirstOrDefault(); + + if (drGrpUser == null) + { + result.Success = false; + result.Message = $"입력한 사용자 계정이 존재하지 않습니다.\n\n담당부서명:{deptName}\n부서코드:{gcode}\n\n접속 부서를 확인하시고 관리자 문의 하세요"; + return JsonConvert.SerializeObject(result); + } + + if (drGrpUser.level == 0) + { + result.Success = false; + result.Message = "해당 계정이 활성화되지 않았습니다.\n계정 담당자 문의하세요"; + return JsonConvert.SerializeObject(result); + } + + // 사용자 테이블에서 암호 확인 + var taUser = new dsMSSQLTableAdapters.UsersTableAdapter(); + var drUser = taUser.GetID(id).FirstOrDefault(); + + if (drUser == null) + { + result.Success = false; + result.Message = $"입력한 사용자 계정이 존재하지 않습니다.\n\n담당부서명:{deptName}\n부서코드:{gcode}\n\n접속 부서를 확인하시고 관리자 문의 하세요"; + return JsonConvert.SerializeObject(result); + } + + if (!drUser.password.Equals(encpass)) + { + result.Success = false; + result.Message = "암호가 일치하지 않습니다.\n암호를 분실했을 경우에는 계정담당자에 초기화를 요청 하세요"; + return JsonConvert.SerializeObject(result); + } + + // 버전 체크 + var MaxVersion = DBM.GetMaxVersion(); + if (!MaxVersion.isEmpty()) + { + var curversion = Application.ProductVersion; + var verchk = curversion.CompareTo(MaxVersion); + if (verchk < 0) + { + result.VersionWarning = "현재 구 버젼을 사용하고 있습니다.\n업데이트를 진행 하고 사용하시기 바랍니다"; + } + } + + // 로그인 정보 설정 + var gperm = DBM.ExecuteScalar($"SELECT ISNULL(permission,0) FROM UserGroup WHERE gcode = '{gcode}'"); + + info.Login.no = drUser.id; + info.Login.nameK = drUser.name; + info.Login.dept = deptName; + info.Login.level = drGrpUser.level; + info.Login.email = drUser.email; + info.Login.nameE = drUser.nameE; + info.Login.hp = drUser.hp; + info.Login.tel = drUser.tel; + info.Login.title = drUser.dept + "(" + drUser.grade + ")"; + info.NotShowJobReportview = Pub.setting.NotShowJobreportPRewView; + info.Login.gcode = gcode; + info.Login.process = drGrpUser.Process; + info.Login.permission = 0; + info.Login.gpermission = int.Parse(gperm); + info.ShowBuyerror = Pub.setting.Showbuyerror; + } + + // 설정 저장 (rememberMe 처리) + if (rememberMe) + { + var idlist = new List { id.Trim() }; + var vuserlist = ""; + foreach (var item in idlist) + vuserlist += ";" + item; + + Pub.setting.lastid = vuserlist; + Pub.setting.lastdpt = deptName; + Pub.setting.lastgcode = gcode; + Pub.setting.Save(); + } + + // 로그인 정보 기록 + AddLoginInfo(); + + // 자동 업무일지 생성 + Pub.MakeAutoJobReportbyLogin(); + Pub.MakeAutoJobReportByAuto(); + + info.Login.loginusetime = (DateTime.Now - dt).TotalMilliseconds; + + // fMain의 로그인 완료 후처리 호출 + CallMainFormLoginCompleted(); + + result.Success = true; + result.Message = "로그인 성공"; + result.RedirectUrl = "/DashBoard/index.html"; + result.UserName = info.Login.nameK; + } + catch (Exception ex) + { + result.Success = false; + result.Message = "데이터베이스 조회 실패. 다음 오류 메세지를 참고하세요.\n\n" + ex.Message; + } + + return JsonConvert.SerializeObject(result); + } + + /// + /// fMain의 OnLoginCompleted() 호출 + /// + private void CallMainFormLoginCompleted() + { + try + { + // Application.OpenForms에서 fMain 인스턴스 찾기 + foreach (Form form in Application.OpenForms) + { + if (form is fMain mainForm) + { + // UI 스레드에서 실행 + if (mainForm.InvokeRequired) + { + mainForm.Invoke(new Action(() => mainForm.OnLoginCompleted())); + } + else + { + mainForm.OnLoginCompleted(); + } + break; + } + } + } + catch (Exception ex) + { + Console.WriteLine($"OnLoginCompleted 호출 오류: {ex.Message}"); + } + } + + /// + /// 개발자 로그인 정보 설정 + /// + private void SetDevLoginInfo(string gcode, string deptName) + { + var gperm = DBM.ExecuteScalar($"SELECT ISNULL(permission,0) FROM UserGroup WHERE gcode = '{gcode}'"); + + info.Login.no = "dev"; + info.Login.nameK = "개발자"; + info.Login.dept = deptName; + info.Login.level = 10; + info.Login.email = ""; + info.Login.nameE = "DEVELOPER"; + info.Login.hp = ""; + info.Login.tel = ""; + info.Login.title = "업무일지 개발자"; + info.NotShowJobReportview = Pub.setting.NotShowJobreportPRewView; + info.Login.gcode = gcode; + info.Login.process = "개발자"; + info.Login.permission = 0; + info.Login.gpermission = int.Parse(gperm); + info.ShowBuyerror = Pub.setting.Showbuyerror; + } + + /// + /// 로그인 정보 기록 + /// + private void AddLoginInfo() + { + string ip = string.Empty; + string hostname = Dns.GetHostName(); + string fullname = Dns.GetHostEntry("").HostName; + + var host = Dns.GetHostEntry(hostname); + foreach (IPAddress r in host.AddressList) + { + string str = r.ToString(); + if (!string.IsNullOrEmpty(str) && str.StartsWith("10.")) + { + ip = str; + break; + } + } + + if (string.IsNullOrEmpty(ip) || string.IsNullOrEmpty(hostname)) return; + + try + { + var ta = new dsMSSQLTableAdapters.EETGW_LoginInfoTableAdapter(); + ta.Insert(info.Login.no, DateTime.Now, ip, fullname, info.Login.no, DateTime.Now); + } + catch (Exception ex) + { + Console.WriteLine(ex.Message); + } + } + + /// + /// 그룹 목록 조회 + /// + public string GetUserGroups() + { + var dt = DBM.GetUserGroups(); + return JsonConvert.SerializeObject(dt, new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore + }); + } + + /// + /// 이전 로그인 정보 조회 + /// + public string GetPreviousLoginInfo() + { + var result = new + { + Success = true, + Data = new + { + LastGcode = Pub.setting.lastgcode ?? "", + LastDept = Pub.setting.lastdpt ?? "", + LastId = Pub.setting.lastid?.TrimStart(';').Split(';').FirstOrDefault() ?? "" + } + }; + return JsonConvert.SerializeObject(result); + } + + #endregion + } +} diff --git a/Project/Web/MachineBridge/MachineBridge.Project.cs b/Project/Web/MachineBridge/MachineBridge.Project.cs new file mode 100644 index 0000000..d066cb2 --- /dev/null +++ b/Project/Web/MachineBridge/MachineBridge.Project.cs @@ -0,0 +1,210 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.SqlClient; +using Newtonsoft.Json; +using FCOMMON; + +namespace Project.Web +{ + public partial class MachineBridge + { + #region Project API + + /// + /// 프로젝트 목록 조회 + /// + public string Project_GetProjects(string status, string userFilter) + { + try + { + var sql = @"SELECT * FROM EETGW_Project WITH (nolock) + WHERE gcode = @gcode"; + + var parameters = new List(); + parameters.Add(new SqlParameter("@gcode", info.Login.gcode)); + + if (!string.IsNullOrEmpty(status)) + { + sql += " AND 상태 = @status"; + parameters.Add(new SqlParameter("@status", status)); + } + + if (userFilter == "my") + { + sql += " AND (프로젝트관리자 LIKE @userName OR 설계담당 LIKE @userName OR 전장담당 LIKE @userName OR 프로그램담당 LIKE @userName)"; + parameters.Add(new SqlParameter("@userName", "%" + info.Login.nameK + "%")); + } + + sql += " ORDER BY 시작일 DESC"; + + var cs = Properties.Settings.Default.gwcs; + var cn = new SqlConnection(cs); + var cmd = new SqlCommand(sql, cn); + cmd.Parameters.AddRange(parameters.ToArray()); + + var da = new SqlDataAdapter(cmd); + var dt = new DataTable(); + da.Fill(dt); + da.Dispose(); + cmd.Dispose(); + cn.Dispose(); + + return JsonConvert.SerializeObject(new { Success = true, Data = dt, CurrentUser = info.Login.nameK }, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + } + catch (Exception ex) + { + return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message }); + } + } + + /// + /// 프로젝트 상세 조회 + /// + public string Project_GetProject(int id) + { + try + { + var sql = "SELECT * FROM EETGW_Project WHERE idx = @idx AND gcode = @gcode"; + + var cs = Properties.Settings.Default.gwcs; + var cn = new SqlConnection(cs); + var cmd = new SqlCommand(sql, cn); + cmd.Parameters.AddWithValue("@idx", id); + cmd.Parameters.AddWithValue("@gcode", info.Login.gcode); + + var da = new SqlDataAdapter(cmd); + var dt = new DataTable(); + da.Fill(dt); + da.Dispose(); + cmd.Dispose(); + cn.Dispose(); + + if (dt.Rows.Count > 0) + { + return JsonConvert.SerializeObject(new { Success = true, Data = dt.Rows[0] }, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + } + + return JsonConvert.SerializeObject(new { Success = false, Message = "프로젝트를 찾을 수 없습니다." }); + } + catch (Exception ex) + { + return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message }); + } + } + + /// + /// 프로젝트 생성 + /// + public string Project_CreateProject(string name, string process, string sdate, string edate, string ddate, string odate, string userManager, string status, string memo) + { + try + { + var sql = @"INSERT INTO EETGW_Project (gcode, 프로젝트명, 프로젝트공정, 시작일, 완료일, 만료일, 출고일, 프로젝트관리자, 상태, memo, wuid, wdate) + VALUES (@gcode, @name, @process, @sdate, @edate, @ddate, @odate, @userManager, @status, @memo, @wuid, GETDATE()); + SELECT SCOPE_IDENTITY();"; + + var cs = Properties.Settings.Default.gwcs; + var cn = new SqlConnection(cs); + var cmd = new SqlCommand(sql, cn); + cmd.Parameters.AddWithValue("@gcode", info.Login.gcode); + cmd.Parameters.AddWithValue("@name", name ?? ""); + cmd.Parameters.AddWithValue("@process", process ?? ""); + cmd.Parameters.AddWithValue("@sdate", string.IsNullOrEmpty(sdate) ? (object)DBNull.Value : sdate); + cmd.Parameters.AddWithValue("@edate", string.IsNullOrEmpty(edate) ? (object)DBNull.Value : edate); + cmd.Parameters.AddWithValue("@ddate", string.IsNullOrEmpty(ddate) ? (object)DBNull.Value : ddate); + cmd.Parameters.AddWithValue("@odate", string.IsNullOrEmpty(odate) ? (object)DBNull.Value : odate); + cmd.Parameters.AddWithValue("@userManager", userManager ?? ""); + cmd.Parameters.AddWithValue("@status", status ?? "진행"); + cmd.Parameters.AddWithValue("@memo", memo ?? ""); + cmd.Parameters.AddWithValue("@wuid", info.Login.no); + + cn.Open(); + var newId = Convert.ToInt32(cmd.ExecuteScalar()); + cn.Close(); + cmd.Dispose(); + cn.Dispose(); + + return JsonConvert.SerializeObject(new { Success = true, Message = "프로젝트가 생성되었습니다.", Data = new { idx = newId } }); + } + catch (Exception ex) + { + return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message }); + } + } + + /// + /// 프로젝트 수정 + /// + public string Project_UpdateProject(int idx, string name, string process, string sdate, string edate, string ddate, string odate, string userManager, string status, string memo) + { + try + { + var sql = @"UPDATE EETGW_Project SET + 프로젝트명 = @name, 프로젝트공정 = @process, 시작일 = @sdate, 완료일 = @edate, + 만료일 = @ddate, 출고일 = @odate, 프로젝트관리자 = @userManager, 상태 = @status, memo = @memo, + wuid = @wuid, wdate = GETDATE() + WHERE idx = @idx AND gcode = @gcode"; + + var cs = Properties.Settings.Default.gwcs; + var cn = new SqlConnection(cs); + var cmd = new SqlCommand(sql, cn); + cmd.Parameters.AddWithValue("@idx", idx); + cmd.Parameters.AddWithValue("@gcode", info.Login.gcode); + cmd.Parameters.AddWithValue("@name", name ?? ""); + cmd.Parameters.AddWithValue("@process", process ?? ""); + cmd.Parameters.AddWithValue("@sdate", string.IsNullOrEmpty(sdate) ? (object)DBNull.Value : sdate); + cmd.Parameters.AddWithValue("@edate", string.IsNullOrEmpty(edate) ? (object)DBNull.Value : edate); + cmd.Parameters.AddWithValue("@ddate", string.IsNullOrEmpty(ddate) ? (object)DBNull.Value : ddate); + cmd.Parameters.AddWithValue("@odate", string.IsNullOrEmpty(odate) ? (object)DBNull.Value : odate); + cmd.Parameters.AddWithValue("@userManager", userManager ?? ""); + cmd.Parameters.AddWithValue("@status", status ?? "진행"); + cmd.Parameters.AddWithValue("@memo", memo ?? ""); + cmd.Parameters.AddWithValue("@wuid", info.Login.no); + + cn.Open(); + var result = cmd.ExecuteNonQuery(); + cn.Close(); + cmd.Dispose(); + cn.Dispose(); + + return JsonConvert.SerializeObject(new { Success = result > 0, Message = result > 0 ? "수정되었습니다." : "수정에 실패했습니다." }); + } + catch (Exception ex) + { + return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message }); + } + } + + /// + /// 프로젝트 삭제 + /// + public string Project_DeleteProject(int id) + { + try + { + var sql = "DELETE FROM EETGW_Project WHERE idx = @idx AND gcode = @gcode"; + + var cs = Properties.Settings.Default.gwcs; + var cn = new SqlConnection(cs); + var cmd = new SqlCommand(sql, cn); + cmd.Parameters.AddWithValue("@idx", id); + cmd.Parameters.AddWithValue("@gcode", info.Login.gcode); + + cn.Open(); + var result = cmd.ExecuteNonQuery(); + cn.Close(); + cmd.Dispose(); + cn.Dispose(); + + return JsonConvert.SerializeObject(new { Success = result > 0, Message = result > 0 ? "삭제되었습니다." : "삭제에 실패했습니다." }); + } + catch (Exception ex) + { + return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message }); + } + } + + #endregion + } +} diff --git a/Project/Web/MachineBridge/MachineBridge.Todo.cs b/Project/Web/MachineBridge/MachineBridge.Todo.cs new file mode 100644 index 0000000..fecdc29 --- /dev/null +++ b/Project/Web/MachineBridge/MachineBridge.Todo.cs @@ -0,0 +1,280 @@ +using System; +using System.Data; +using System.Data.SqlClient; +using Newtonsoft.Json; +using FCOMMON; +using Project.Web.Model; + +namespace Project.Web +{ + public partial class MachineBridge + { + #region Todo API + + /// + /// 급한 할일 목록 조회 + /// + public string GetUrgentTodos() + { + try + { + if (string.IsNullOrEmpty(info.Login.no)) + { + return JsonConvert.SerializeObject(new { Success = false, Message = "로그인되지 않은 상태입니다." }); + } + + var sql = @"SELECT * FROM EETGW_Todo + WHERE gcode = @gcode AND uid = @uid + and isnull(status,'0') not in ('2','3','5') + ORDER BY flag DESC, seqno DESC, expire ASC, wdate ASC"; + + var todos = DBM.Query(sql, new { gcode = info.Login.gcode, uid = info.Login.no }); + + return JsonConvert.SerializeObject(new { Success = true, Data = todos }, new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore, + DateFormatString = "yyyy-MM-dd HH:mm:ss" + }); + } + catch (Exception ex) + { + return JsonConvert.SerializeObject(new { Success = false, Message = "급한 Todo 목록을 가져오는 중 오류가 발생했습니다: " + ex.Message }); + } + } + + /// + /// 할일 상세 조회 + /// + public string GetTodo(int id) + { + try + { + if (string.IsNullOrEmpty(info.Login.no)) + { + return JsonConvert.SerializeObject(new { Success = false, Message = "로그인되지 않은 상태입니다." }); + } + + if (id <= 0) + { + return JsonConvert.SerializeObject(new { Success = false, Message = "유효하지 않은 Todo ID입니다." }); + } + + var sql = "SELECT * FROM EETGW_Todo WHERE idx = @idx AND gcode = @gcode AND uid = @uid"; + var todo = DBM.QuerySingleOrDefault(sql, new { idx = id, gcode = info.Login.gcode, uid = info.Login.no }); + + if (todo == null) + { + return JsonConvert.SerializeObject(new { Success = false, Message = "할일을 찾을 수 없습니다." }); + } + + return JsonConvert.SerializeObject(new { Success = true, Data = todo }, new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore, + DateFormatString = "yyyy-MM-dd HH:mm:ss" + }); + } + catch (Exception ex) + { + return JsonConvert.SerializeObject(new { Success = false, Message = "할일 조회 중 오류가 발생했습니다: " + ex.Message }); + } + } + + /// + /// 할일 추가 + /// + public string CreateTodo(string title, string remark, string expire, int seqno, bool flag, string request, string status) + { + try + { + if (string.IsNullOrEmpty(info.Login.no)) + { + return JsonConvert.SerializeObject(new { Success = false, Message = "로그인되지 않은 상태입니다." }); + } + + if (string.IsNullOrEmpty(remark)) + { + return JsonConvert.SerializeObject(new { Success = false, Message = "할일 내용은 필수입니다." }); + } + + DateTime? expireDate = null; + if (!string.IsNullOrEmpty(expire)) + { + if (DateTime.TryParse(expire, out DateTime parsed)) + expireDate = parsed; + } + + DateTime? okdateValue = null; + if (status == "5") + { + okdateValue = DateTime.Now; + } + + var sql = @" + INSERT INTO EETGW_Todo (gcode, uid, title, remark, flag, expire, seqno, request, status, okdate, wuid, wdate) + VALUES (@gcode, @uid, @title, @remark, @flag, @expire, @seqno, @request, @status, @okdate, @wuid, @wdate); + SELECT SCOPE_IDENTITY();"; + + var newId = DBM.QuerySingle(sql, new + { + gcode = info.Login.gcode, + uid = info.Login.no, + title = title, + remark = remark, + flag = flag, + expire = expireDate, + seqno = seqno, + request = request, + status = string.IsNullOrEmpty(status) ? "0" : status, + okdate = okdateValue, + wuid = info.Login.no, + wdate = DateTime.Now + }); + + return JsonConvert.SerializeObject(new { Success = true, Message = "할일이 추가되었습니다.", Data = new { idx = newId } }); + } + catch (Exception ex) + { + return JsonConvert.SerializeObject(new { Success = false, Message = "할일 추가 중 오류가 발생했습니다: " + ex.Message }); + } + } + + #endregion + + #region Todo Extended API + + /// + /// 할일 전체 목록 조회 + /// + public string Todo_GetTodos() + { + try + { + if (string.IsNullOrEmpty(info.Login.no)) + { + return JsonConvert.SerializeObject(new { Success = false, Message = "로그인되지 않은 상태입니다." }); + } + + var sql = @"SELECT * FROM EETGW_Todo + WHERE gcode = @gcode AND uid = @uid + ORDER BY flag DESC, seqno DESC, expire ASC, wdate ASC"; + + var cs = Properties.Settings.Default.gwcs; + var cn = new SqlConnection(cs); + var cmd = new SqlCommand(sql, cn); + cmd.Parameters.AddWithValue("@gcode", info.Login.gcode); + cmd.Parameters.AddWithValue("@uid", info.Login.no); + + var da = new SqlDataAdapter(cmd); + var dt = new DataTable(); + da.Fill(dt); + da.Dispose(); + cmd.Dispose(); + cn.Dispose(); + + return JsonConvert.SerializeObject(new { Success = true, Data = dt }, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, DateFormatString = "yyyy-MM-dd HH:mm:ss" }); + } + catch (Exception ex) + { + return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message }); + } + } + + /// + /// 할일 수정 + /// + public string Todo_UpdateTodo(int idx, string title, string remark, string expire, int seqno, bool flag, string request, string status) + { + try + { + if (string.IsNullOrEmpty(info.Login.no)) + { + return JsonConvert.SerializeObject(new { Success = false, Message = "로그인되지 않은 상태입니다." }); + } + + DateTime? expireDate = null; + if (!string.IsNullOrEmpty(expire)) + { + if (DateTime.TryParse(expire, out DateTime parsed)) + expireDate = parsed; + } + + DateTime? okdateValue = null; + if (status == "5") + { + okdateValue = DateTime.Now; + } + + var sql = @"UPDATE EETGW_Todo SET + title = @title, remark = @remark, expire = @expire, seqno = @seqno, + flag = @flag, request = @request, status = @status, okdate = @okdate, + wuid = @wuid, wdate = GETDATE() + WHERE idx = @idx AND gcode = @gcode AND uid = @uid"; + + var cs = Properties.Settings.Default.gwcs; + var cn = new SqlConnection(cs); + var cmd = new SqlCommand(sql, cn); + cmd.Parameters.AddWithValue("@idx", idx); + cmd.Parameters.AddWithValue("@gcode", info.Login.gcode); + cmd.Parameters.AddWithValue("@uid", info.Login.no); + cmd.Parameters.AddWithValue("@title", title ?? ""); + cmd.Parameters.AddWithValue("@remark", remark ?? ""); + cmd.Parameters.AddWithValue("@expire", expireDate.HasValue ? (object)expireDate.Value : DBNull.Value); + cmd.Parameters.AddWithValue("@seqno", seqno); + cmd.Parameters.AddWithValue("@flag", flag); + cmd.Parameters.AddWithValue("@request", request ?? ""); + cmd.Parameters.AddWithValue("@status", string.IsNullOrEmpty(status) ? "0" : status); + cmd.Parameters.AddWithValue("@okdate", okdateValue.HasValue ? (object)okdateValue.Value : DBNull.Value); + cmd.Parameters.AddWithValue("@wuid", info.Login.no); + + cn.Open(); + var result = cmd.ExecuteNonQuery(); + cn.Close(); + cmd.Dispose(); + cn.Dispose(); + + return JsonConvert.SerializeObject(new { Success = result > 0, Message = result > 0 ? "수정되었습니다." : "수정에 실패했습니다." }); + } + catch (Exception ex) + { + return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message }); + } + } + + /// + /// 할일 삭제 + /// + public string Todo_DeleteTodo(int id) + { + try + { + if (string.IsNullOrEmpty(info.Login.no)) + { + return JsonConvert.SerializeObject(new { Success = false, Message = "로그인되지 않은 상태입니다." }); + } + + var sql = "DELETE FROM EETGW_Todo WHERE idx = @idx AND gcode = @gcode AND uid = @uid"; + + var cs = Properties.Settings.Default.gwcs; + var cn = new SqlConnection(cs); + var cmd = new SqlCommand(sql, cn); + cmd.Parameters.AddWithValue("@idx", id); + cmd.Parameters.AddWithValue("@gcode", info.Login.gcode); + cmd.Parameters.AddWithValue("@uid", info.Login.no); + + cn.Open(); + var result = cmd.ExecuteNonQuery(); + cn.Close(); + cmd.Dispose(); + cn.Dispose(); + + return JsonConvert.SerializeObject(new { Success = result > 0, Message = result > 0 ? "삭제되었습니다." : "삭제에 실패했습니다." }); + } + catch (Exception ex) + { + return JsonConvert.SerializeObject(new { Success = false, Message = ex.Message }); + } + } + + #endregion + } +} diff --git a/Project/Web/MachineBridge/MachineBridge.cs b/Project/Web/MachineBridge/MachineBridge.cs new file mode 100644 index 0000000..92152da --- /dev/null +++ b/Project/Web/MachineBridge/MachineBridge.cs @@ -0,0 +1,42 @@ +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; +using Newtonsoft.Json; +using System.IO; +using System.Linq; +using System.Collections.Generic; +using System.Net; +using System.Net.NetworkInformation; +using System.Data; +using System.Data.SqlClient; +using FCOMMON; +using Project.Web.Model; + +namespace Project.Web +{ + // Important: Allows JavaScript to see this class + [ClassInterface(ClassInterfaceType.AutoDual)] + [ComVisible(true)] + public partial class MachineBridge + { + // Reference to the main form to update logic + private Dialog.fDashboardNew _host; + + public MachineBridge(Dialog.fDashboardNew host) + { + _host = host; + } + } + + /// + /// 로그인 결과 클래스 + /// + public class LoginResult + { + public bool Success { get; set; } + public string Message { get; set; } + public string RedirectUrl { get; set; } + public string UserName { get; set; } + public string VersionWarning { get; set; } + } +} diff --git a/Project/Web/WebSocketServer.cs b/Project/Web/WebSocketServer.cs new file mode 100644 index 0000000..7d700ca --- /dev/null +++ b/Project/Web/WebSocketServer.cs @@ -0,0 +1,221 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Project.Web +{ + public class WebSocketServer + { + private HttpListener _httpListener; + private List _clients = new List(); + private Dialog.fDashboardNew _mainForm; + + public WebSocketServer(string url, Dialog.fDashboardNew form) + { + _mainForm = form; + _httpListener = new HttpListener(); + _httpListener.Prefixes.Add(url); + _httpListener.Start(); + Console.WriteLine($"[WS] Listening on {url}"); + Task.Run(AcceptConnections); + } + + private async Task AcceptConnections() + { + while (_httpListener.IsListening) + { + try + { + var context = await _httpListener.GetContextAsync(); + if (context.Request.IsWebSocketRequest) + { + ProcessRequest(context); + } + else + { + context.Response.StatusCode = 400; + context.Response.Close(); + } + } + catch (Exception ex) + { + Console.WriteLine($"[WS] Error: {ex.Message}"); + } + } + } + + private System.Collections.Concurrent.ConcurrentDictionary _socketLocks = new System.Collections.Concurrent.ConcurrentDictionary(); + + private async void ProcessRequest(HttpListenerContext context) + { + WebSocketContext wsContext = null; + try + { + wsContext = await context.AcceptWebSocketAsync(subProtocol: null); + WebSocket socket = wsContext.WebSocket; + _socketLocks.TryAdd(socket, new SemaphoreSlim(1, 1)); + + lock (_clients) { _clients.Add(socket); } + Console.WriteLine("[WS] Client Connected"); + + await ReceiveLoop(socket); + } + catch (Exception ex) + { + Console.WriteLine($"[WS] Accept Error: {ex.Message}"); + } + finally + { + if (wsContext != null) + { + WebSocket socket = wsContext.WebSocket; + lock (_clients) { _clients.Remove(socket); } + + if (_socketLocks.TryRemove(socket, out var semaphore)) + { + semaphore.Dispose(); + } + socket.Dispose(); + } + } + } + + private async Task ReceiveLoop(WebSocket socket) + { + var buffer = new byte[1024 * 4]; + while (socket.State == WebSocketState.Open) + { + try + { + var result = await socket.ReceiveAsync(new ArraySegment(buffer), CancellationToken.None); + if (result.MessageType == WebSocketMessageType.Close) + { + await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None); + } + else if (result.MessageType == WebSocketMessageType.Text) + { + string msg = Encoding.UTF8.GetString(buffer, 0, result.Count); + HandleMessage(msg, socket); + } + } + catch + { + break; + } + } + } + + private async void HandleMessage(string msg, WebSocket socket) + { + // Simple JSON parsing (manual or Newtonsoft) + // Expected format: { "type": "...", "data": ... } + try + { + dynamic json = Newtonsoft.Json.JsonConvert.DeserializeObject(msg); + string type = json.type; + + Console.WriteLine($"HandleMessage:{type}"); + if (type == "GET_CONFIG") + { + //// Simulate Delay for Loading Screen Test + //await Task.Delay(1000); + + //// Send Config back + //var bridge = new MachineBridge(_mainForm); // Re-use logic + //string configJson = bridge.GetConfig(); + //var response = new { type = "CONFIG_DATA", data = Newtonsoft.Json.JsonConvert.DeserializeObject(configJson) }; + //await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response)); + } + else if (type == "GET_IO_LIST") + { + // var bridge = new MachineBridge(_mainForm); + // string ioJson = bridge.GetIOList(); + // var response = new { type = "IO_LIST_DATA", data = Newtonsoft.Json.JsonConvert.DeserializeObject(ioJson) }; + // await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response)); + //} + //else if (type == "GET_RECIPE_LIST") + //{ + // var bridge = new MachineBridge(_mainForm); + // string recipeJson = bridge.GetRecipeList(); + // var response = new { type = "RECIPE_LIST_DATA", data = Newtonsoft.Json.JsonConvert.DeserializeObject(recipeJson) }; + // await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response)); + } + else if (type == "SAVE_CONFIG") + { + //string configJson = Newtonsoft.Json.JsonConvert.SerializeObject(json.data); + //var bridge = new MachineBridge(_mainForm); + //bridge.SaveConfig(configJson); + } + + } + catch (Exception ex) + { + Console.WriteLine($"[WS] Msg Error: {ex.Message}"); + } + } + + private async Task Send(WebSocket socket, string message) + { + if (_socketLocks.TryGetValue(socket, out var semaphore)) + { + await semaphore.WaitAsync(); + try + { + if (socket.State == WebSocketState.Open) + { + byte[] buffer = Encoding.UTF8.GetBytes(message); + await socket.SendAsync(new ArraySegment(buffer), WebSocketMessageType.Text, true, CancellationToken.None); + } + } + finally + { + semaphore.Release(); + } + } + } + + public async void Broadcast(string message) + { + byte[] buffer = Encoding.UTF8.GetBytes(message); + WebSocket[] clientsCopy; + + lock (_clients) + { + clientsCopy = _clients.ToArray(); + } + + foreach (var client in clientsCopy) + { + if (client.State == WebSocketState.Open && _socketLocks.TryGetValue(client, out var semaphore)) + { + // Fire and forget, but safely + _ = Task.Run(async () => + { + // Try to get lock immediately. If busy (sending previous frame), skip this frame to prevent lag. + if (await semaphore.WaitAsync(0)) + { + try + { + if (client.State == WebSocketState.Open) + { + await client.SendAsync(new ArraySegment(buffer), WebSocketMessageType.Text, true, CancellationToken.None); + } + } + catch { /* Ignore send errors */ } + finally + { + semaphore.Release(); + } + } + }); + } + } + } + } +} diff --git a/Project/Web/wwwroot/Jobreport/index.html b/Project/Web/wwwroot/Jobreport/index.html index 9eea7ce..7be8f88 100644 --- a/Project/Web/wwwroot/Jobreport/index.html +++ b/Project/Web/wwwroot/Jobreport/index.html @@ -462,151 +462,12 @@ + + + +