diff --git a/.gitignore b/.gitignore index 848569d9d..320f9e49b 100644 --- a/.gitignore +++ b/.gitignore @@ -195,4 +195,7 @@ FakesAssemblies/ # Visual Studio 6 workspace options file *.opt -*.bak \ No newline at end of file +*.bak + +# Unignore the configuration database files +!Base/data/*.sdf \ No newline at end of file diff --git a/Base/data/ScadaBase_4.1_en.sdf b/Base/data/ScadaBase_4.1_en.sdf new file mode 100644 index 000000000..578ddec61 Binary files /dev/null and b/Base/data/ScadaBase_4.1_en.sdf differ diff --git a/Base/data/ScadaBase_4.1_ru.sdf b/Base/data/ScadaBase_4.1_ru.sdf new file mode 100644 index 000000000..95aaadaee Binary files /dev/null and b/Base/data/ScadaBase_4.1_ru.sdf differ diff --git a/Base/data/ScadaBase_4.2_en.sdf b/Base/data/ScadaBase_4.2_en.sdf new file mode 100644 index 000000000..33725e071 Binary files /dev/null and b/Base/data/ScadaBase_4.2_en.sdf differ diff --git a/Base/data/ScadaBase_4.2_ru.sdf b/Base/data/ScadaBase_4.2_ru.sdf new file mode 100644 index 000000000..409cb1017 Binary files /dev/null and b/Base/data/ScadaBase_4.2_ru.sdf differ diff --git a/Base/model/scada_3.0.erwin b/Base/model/scada_3.0.erwin new file mode 100644 index 000000000..df5c4ba39 Binary files /dev/null and b/Base/model/scada_3.0.erwin differ diff --git a/Base/model/scada_4.0.erwin b/Base/model/scada_4.0.erwin new file mode 100644 index 000000000..5323c7355 Binary files /dev/null and b/Base/model/scada_4.0.erwin differ diff --git a/Base/model/scada_4.1.erwin b/Base/model/scada_4.1.erwin new file mode 100644 index 000000000..01cb75d08 Binary files /dev/null and b/Base/model/scada_4.1.erwin differ diff --git a/Base/model/scada_4.1_logical.png b/Base/model/scada_4.1_logical.png new file mode 100644 index 000000000..8972c4beb Binary files /dev/null and b/Base/model/scada_4.1_logical.png differ diff --git a/Base/model/scada_4.1_physical.png b/Base/model/scada_4.1_physical.png new file mode 100644 index 000000000..111549952 Binary files /dev/null and b/Base/model/scada_4.1_physical.png differ diff --git a/Base/model/scada_4.2_physical.png b/Base/model/scada_4.2_physical.png new file mode 100644 index 000000000..b34ad9ec0 Binary files /dev/null and b/Base/model/scada_4.2_physical.png differ diff --git a/Base/sql/convert_3.0_to_4.0.sql b/Base/sql/convert_3.0_to_4.0.sql new file mode 100644 index 000000000..270322105 --- /dev/null +++ b/Base/sql/convert_3.0_to_4.0.sql @@ -0,0 +1,8 @@ +-- Convert the configuration database from v3.0 to v4.0 +CREATE TABLE Formula +( + Name nvarchar(50) NOT NULL , + Source nvarchar(1000) NOT NULL , + Descr nvarchar(100) NULL , + CONSTRAINT PK_Formula PRIMARY KEY (Name) +) \ No newline at end of file diff --git a/Base/sql/convert_4.1_to_4.2.sql b/Base/sql/convert_4.1_to_4.2.sql new file mode 100644 index 000000000..3a14ad40c --- /dev/null +++ b/Base/sql/convert_4.1_to_4.2.sql @@ -0,0 +1,3 @@ +-- Script Date: 25.10.2016 12:05 - ErikEJ.SqlCeScripting version 3.5.2.58 +ALTER TABLE [KP] ALTER COLUMN [CallNum] nvarchar(50) NULL ; +GO \ No newline at end of file diff --git a/Base/sql/scada_4.1_erwin.sql b/Base/sql/scada_4.1_erwin.sql new file mode 100644 index 000000000..4fc1ddda2 --- /dev/null +++ b/Base/sql/scada_4.1_erwin.sql @@ -0,0 +1,385 @@ + +CREATE TABLE CmdType +( + CmdTypeID int NOT NULL , + Name nvarchar(50) NOT NULL , + Descr nvarchar(100) NULL , + CONSTRAINT pk_CmdType PRIMARY KEY (CmdTypeID ASC) +) +go + + + +CREATE TABLE CmdVal +( + CmdValID int NOT NULL , + Name nvarchar(50) NOT NULL , + Val nvarchar(100) NOT NULL , + Descr nvarchar(100) NULL , + CONSTRAINT pk_CmdVal PRIMARY KEY (CmdValID ASC) +) +go + + + +CREATE TABLE CnlType +( + CnlTypeID int NOT NULL , + Name nvarchar(50) NOT NULL , + ShtName nvarchar(20) NOT NULL , + Descr nvarchar(100) NULL , + CONSTRAINT pk_CnlType PRIMARY KEY (CnlTypeID ASC) +) +go + + + +CREATE TABLE CommLine +( + CommLineNum int NOT NULL , + Name nvarchar(50) NOT NULL , + Descr nvarchar(100) NULL , + CONSTRAINT pk_CommLine PRIMARY KEY (CommLineNum ASC) +) +go + + + +CREATE TABLE CtrlCnl +( + CtrlCnlNum int NOT NULL , + Active bit NOT NULL , + Name nvarchar(50) NOT NULL , + CmdTypeID int NOT NULL , + ObjNum int NULL , + KPNum int NULL , + CmdNum int NULL , + CmdValID int NULL , + FormulaUsed bit NOT NULL , + Formula nvarchar(100) NULL , + EvEnabled bit NOT NULL , + ModifiedDT datetime NOT NULL , + CONSTRAINT pk_CtrlCnl PRIMARY KEY (CtrlCnlNum ASC) +) +go + + + +CREATE INDEX idx_CtrlCnl_KPNum ON CtrlCnl +( + KPNum ASC +) +go + + + +CREATE INDEX idx_CtrlCnl_ObjNum ON CtrlCnl +( + ObjNum ASC +) +go + + + +CREATE TABLE EvType +( + CnlStatus int NOT NULL , + Name nvarchar(50) NOT NULL , + Color nvarchar(20) NULL , + Descr nvarchar(100) NULL , + CONSTRAINT pk_EvType PRIMARY KEY (CnlStatus ASC) +) +go + + + +CREATE TABLE Format +( + FormatID int NOT NULL , + Name nvarchar(50) NOT NULL , + ShowNumber bit NOT NULL , + DecDigits int NULL , + CONSTRAINT pk_Format PRIMARY KEY (FormatID ASC) +) +go + + + +CREATE TABLE Formula +( + Name nvarchar(50) NOT NULL , + Source nvarchar(1000) NOT NULL , + Descr nvarchar(100) NULL , + CONSTRAINT pk_Formula PRIMARY KEY (Name ASC) +) +go + + + +CREATE TABLE InCnl +( + CnlNum int NOT NULL , + Active bit NOT NULL , + Name nvarchar(50) NOT NULL , + CnlTypeID int NOT NULL , + ObjNum int NULL , + KPNum int NULL , + Signal int NULL , + FormulaUsed bit NOT NULL , + Formula nvarchar(100) NULL , + Averaging bit NOT NULL , + ParamID int NULL , + FormatID int NULL , + UnitID int NULL , + CtrlCnlNum int NULL , + EvEnabled bit NOT NULL , + EvSound bit NOT NULL , + EvOnChange bit NOT NULL , + EvOnUndef bit NOT NULL , + LimLowCrash float NULL , + LimLow float NULL , + LimHigh float NULL , + LimHighCrash float NULL , + ModifiedDT datetime NOT NULL , + CONSTRAINT pk_InCnl PRIMARY KEY (CnlNum ASC) +) +go + + + +CREATE INDEX idx_InCnl_KPNum ON InCnl +( + KPNum ASC +) +go + + + +CREATE INDEX idx_InCnl_ObjNum ON InCnl +( + ObjNum ASC +) +go + + + +CREATE TABLE Interface +( + ItfID int NOT NULL , + Name nvarchar(50) NOT NULL , + Descr nvarchar(100) NULL , + CONSTRAINT pk_Interface PRIMARY KEY (ItfID ASC) +) +go + + + +CREATE TABLE KP +( + KPNum int NOT NULL , + Name nvarchar(50) NOT NULL , + KPTypeID int NOT NULL , + Address int NULL , + CallNum nvarchar(20) NULL , + CommLineNum int NULL , + Descr nvarchar(100) NULL , + CONSTRAINT pk_KP PRIMARY KEY (KPNum ASC) +) +go + + + +CREATE TABLE KPType +( + KPTypeID int NOT NULL , + Name nvarchar(50) NOT NULL , + DllFileName nvarchar(20) NULL , + Descr nvarchar(100) NULL , + CONSTRAINT pk_KPType PRIMARY KEY (KPTypeID ASC) +) +go + + + +CREATE TABLE Obj +( + ObjNum int NOT NULL , + Name nvarchar(50) NOT NULL , + Descr nvarchar(100) NULL , + CONSTRAINT pk_Obj PRIMARY KEY (ObjNum ASC) +) +go + + + +CREATE TABLE Param +( + ParamID int NOT NULL , + Name nvarchar(50) NOT NULL , + Sign nvarchar(20) NULL , + IconFileName nvarchar(20) NULL , + CONSTRAINT pk_Param PRIMARY KEY (ParamID ASC) +) +go + + + +CREATE TABLE Right +( + ItfID int NOT NULL , + RoleID int NOT NULL , + ViewRight bit NOT NULL , + CtrlRight bit NOT NULL , + CONSTRAINT pk_Right PRIMARY KEY (ItfID ASC,RoleID ASC) +) +go + + + +CREATE TABLE Role +( + RoleID int NOT NULL , + Name nvarchar(50) NOT NULL , + Descr nvarchar(100) NULL , + CONSTRAINT pk_Role PRIMARY KEY (RoleID ASC) +) +go + + + +CREATE TABLE Unit +( + UnitID int NOT NULL , + Name nvarchar(50) NOT NULL , + Sign nvarchar(100) NOT NULL , + Descr nvarchar(100) NULL , + CONSTRAINT pk_Unit PRIMARY KEY (UnitID ASC) +) +go + + + +CREATE TABLE User +( + UserID int NOT NULL , + Name nvarchar(50) NOT NULL , + Password nvarchar(20) NULL , + RoleID int NOT NULL , + Descr nvarchar(100) NULL , + CONSTRAINT pk_User PRIMARY KEY (UserID ASC) +) +go + + + + +ALTER TABLE CtrlCnl + ADD CONSTRAINT fk_CtrlCnl_ObjNum FOREIGN KEY (ObjNum) REFERENCES Obj(ObjNum) +go + + + + +ALTER TABLE CtrlCnl + ADD CONSTRAINT fk_CtrlCnl_KPNum FOREIGN KEY (KPNum) REFERENCES KP(KPNum) +go + + + + +ALTER TABLE CtrlCnl + ADD CONSTRAINT fk_CtrlCnl_CmdTypeID FOREIGN KEY (CmdTypeID) REFERENCES CmdType(CmdTypeID) +go + + + + +ALTER TABLE CtrlCnl + ADD CONSTRAINT fk_CtrlCnl_CmdValID FOREIGN KEY (CmdValID) REFERENCES CmdVal(CmdValID) +go + + + + +ALTER TABLE InCnl + ADD CONSTRAINT fk_InCnl_KPNum FOREIGN KEY (KPNum) REFERENCES KP(KPNum) +go + + + + +ALTER TABLE InCnl + ADD CONSTRAINT fk_InCnl_ObjNum FOREIGN KEY (ObjNum) REFERENCES Obj(ObjNum) +go + + + + +ALTER TABLE InCnl + ADD CONSTRAINT fk_InCnl_CnlTypeID FOREIGN KEY (CnlTypeID) REFERENCES CnlType(CnlTypeID) +go + + + + +ALTER TABLE InCnl + ADD CONSTRAINT fk_InCnl_UnitID FOREIGN KEY (UnitID) REFERENCES Unit(UnitID) +go + + + + +ALTER TABLE InCnl + ADD CONSTRAINT fk_InCnl_FormatID FOREIGN KEY (FormatID) REFERENCES Format(FormatID) +go + + + + +ALTER TABLE InCnl + ADD CONSTRAINT fk_InCnl_ParamID FOREIGN KEY (ParamID) REFERENCES Param(ParamID) +go + + + + +ALTER TABLE InCnl + ADD CONSTRAINT fk_InCnl_CtrlCnlNum FOREIGN KEY (CtrlCnlNum) REFERENCES CtrlCnl(CtrlCnlNum) +go + + + + +ALTER TABLE KP + ADD CONSTRAINT fk_KP_KPTypeID FOREIGN KEY (KPTypeID) REFERENCES KPType(KPTypeID) +go + + + + +ALTER TABLE KP + ADD CONSTRAINT fk_KP_CommLineNum FOREIGN KEY (CommLineNum) REFERENCES CommLine(CommLineNum) +go + + + + +ALTER TABLE Right + ADD CONSTRAINT fk_Right_RoleID FOREIGN KEY (RoleID) REFERENCES Role(RoleID) +go + + + + +ALTER TABLE Right + ADD CONSTRAINT fk_Right_ItfID FOREIGN KEY (ItfID) REFERENCES Interface(ItfID) +go + + + + +ALTER TABLE User + ADD CONSTRAINT fk_User_RoleID FOREIGN KEY (RoleID) REFERENCES Role(RoleID) +go + + diff --git a/Config/DefaultEn/Images/server_room.png b/Config/DefaultEn/Images/server_room.png new file mode 100644 index 000000000..459019bec Binary files /dev/null and b/Config/DefaultEn/Images/server_room.png differ diff --git a/Config/DefaultEn/Images/server_room.psd b/Config/DefaultEn/Images/server_room.psd new file mode 100644 index 000000000..c38510064 Binary files /dev/null and b/Config/DefaultEn/Images/server_room.psd differ diff --git a/Config/DefaultEn/SCADA/Interface/DemoViews/Notifications.tbl b/Config/DefaultEn/SCADA/Interface/DemoViews/Notifications.tbl new file mode 100644 index 000000000..2e0382af6 --- /dev/null +++ b/Config/DefaultEn/SCADA/Interface/DemoViews/Notifications.tbl @@ -0,0 +1,9 @@ + + + SMS + Connection + Event count + + Email + Sent emails + \ No newline at end of file diff --git a/Config/DefaultEn/SCADA/Interface/DemoViews/OpcDemo.tbl b/Config/DefaultEn/SCADA/Interface/DemoViews/OpcDemo.tbl new file mode 100644 index 000000000..25bdb04c5 --- /dev/null +++ b/Config/DefaultEn/SCADA/Interface/DemoViews/OpcDemo.tbl @@ -0,0 +1,6 @@ + + + Tag 1 + Tag 2 + Tag 3 + \ No newline at end of file diff --git a/Config/DefaultEn/SCADA/Interface/DemoViews/SnmpDemo.tbl b/Config/DefaultEn/SCADA/Interface/DemoViews/SnmpDemo.tbl new file mode 100644 index 000000000..98e4c2282 --- /dev/null +++ b/Config/DefaultEn/SCADA/Interface/DemoViews/SnmpDemo.tbl @@ -0,0 +1,8 @@ + + + sysDescr + sysUpTime + sysName + sysServices + snmpInPkts + \ No newline at end of file diff --git a/Config/DefaultEn/SCADA/Interface/Servers/ServerRoom.sch b/Config/DefaultEn/SCADA/Interface/Servers/ServerRoom.sch new file mode 100644 index 000000000..257b26a07 --- /dev/null +++ b/Config/DefaultEn/SCADA/Interface/Servers/ServerRoom.sch @@ -0,0 +1,194 @@ + + + + + 1000 + 550 + + White + server_room.png + Black + + Verdana + 16 + False + False + False + + + + + 1 + + 75 + 140 + + + 90 + 30 + + 0 + False + #f9f9f9 + DimGray + Status + t0 + False + Center + Center + True + 101 + 0 + DrawDiagram + ShowWithUnit + + + 2 + + 270 + 140 + + + 90 + 30 + + 0 + False + #f9f9f9 + DimGray + Status + t1 + False + Center + Center + True + 102 + 0 + DrawDiagram + ShowWithUnit + + + 3 + + 458 + 140 + + + 80 + 30 + + 0 + False + #f9f9f9 + DimGray + Status + t2 + False + Center + Center + True + 103 + 0 + DrawDiagram + ShowWithUnit + + + 4 + + 635 + 140 + + + 90 + 30 + + 0 + False + #f9f9f9 + DimGray + Status + t3 + False + Center + Center + True + 104 + 0 + DrawDiagram + ShowWithUnit + + + 5 + + 835 + 140 + + + 90 + 30 + + 0 + False + #f9f9f9 + DimGray + Status + t4 + False + Center + Center + True + 105 + 0 + DrawDiagram + ShowWithUnit + + + 6 + + 715 + 490 + + + 100 + 100 + + 0 + True + Average: + False + Left + Top + + + 7 + + 795 + 490 + + + 100 + 100 + + 0 + True + Status + t_avg + False + Left + Top + False + 115 + 0 + DrawDiagram + ShowWithUnit + + + + + server_room.png + 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 + + + + + \ No newline at end of file diff --git a/Config/DefaultEn/SCADA/Interface/Servers/ServerRoom.tbl b/Config/DefaultEn/SCADA/Interface/Servers/ServerRoom.tbl new file mode 100644 index 000000000..ce4c7272c --- /dev/null +++ b/Config/DefaultEn/SCADA/Interface/Servers/ServerRoom.tbl @@ -0,0 +1,27 @@ + + + t0 + t1 + t2 + t3 + t4 + t5 + t6 + t_avg + + t0_max + t1_max + t2_max + t3_max + t4_max + t5_max + t6_max + + t0_min + t1_min + t2_min + t3_min + t4_min + t5_min + t6_min + \ No newline at end of file diff --git a/Config/DefaultEn/SCADA/ScadaComm/Config/AddressBook.xml b/Config/DefaultEn/SCADA/ScadaComm/Config/AddressBook.xml new file mode 100644 index 000000000..d960108f2 --- /dev/null +++ b/Config/DefaultEn/SCADA/ScadaComm/Config/AddressBook.xml @@ -0,0 +1,17 @@ + + + + + alexander@gmail.com + + + james@gmail.com + + + maria@gmail.com + + + michael@gmail.com + + + \ No newline at end of file diff --git a/Config/DefaultEn/SCADA/ScadaComm/Config/KpEmail_041.xml b/Config/DefaultEn/SCADA/ScadaComm/Config/KpEmail_041.xml new file mode 100644 index 000000000..cc4432f92 --- /dev/null +++ b/Config/DefaultEn/SCADA/ScadaComm/Config/KpEmail_041.xml @@ -0,0 +1,9 @@ + + + smtp.gmail.com + 587 + myemail@gmail.com + Rapid SCADA + 12345 + true + \ No newline at end of file diff --git a/ScadaComm/OpenKPs/KpModbus/Templates/KpModbus_DemoTemplate.xml b/Config/DefaultEn/SCADA/ScadaComm/Config/KpModbus_Adam6015.xml similarity index 100% rename from ScadaComm/OpenKPs/KpModbus/Templates/KpModbus_DemoTemplate.xml rename to Config/DefaultEn/SCADA/ScadaComm/Config/KpModbus_Adam6015.xml diff --git a/Config/DefaultEn/SCADA/ScadaComm/Config/KpOpc_021.xml b/Config/DefaultEn/SCADA/ScadaComm/Config/KpOpc_021.xml new file mode 100644 index 000000000..e3abb95f8 --- /dev/null +++ b/Config/DefaultEn/SCADA/ScadaComm/Config/KpOpc_021.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Config/DefaultEn/SCADA/ScadaComm/Config/KpSnmp_051.xml b/Config/DefaultEn/SCADA/ScadaComm/Config/KpSnmp_051.xml new file mode 100644 index 000000000..a471e2430 --- /dev/null +++ b/Config/DefaultEn/SCADA/ScadaComm/Config/KpSnmp_051.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Config/DefaultEn/SCADA/ScadaComm/Config/ScadaCommSvcConfig.xml b/Config/DefaultEn/SCADA/ScadaComm/Config/ScadaCommSvcConfig.xml new file mode 100644 index 000000000..8ce0ef73e --- /dev/null +++ b/Config/DefaultEn/SCADA/ScadaComm/Config/ScadaCommSvcConfig.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Config/DefaultRu/SCADA/Interface/DemoViews/Notifications.tbl b/Config/DefaultRu/SCADA/Interface/DemoViews/Notifications.tbl new file mode 100644 index 000000000..eb138eedd --- /dev/null +++ b/Config/DefaultRu/SCADA/Interface/DemoViews/Notifications.tbl @@ -0,0 +1,9 @@ + + + SMS + Связь + Кол-во событий + + Эл. почта + Отправлено писем + \ No newline at end of file diff --git a/Config/DefaultRu/SCADA/ScadaComm/Config/ScadaCommSvcConfig.xml b/Config/DefaultRu/SCADA/ScadaComm/Config/ScadaCommSvcConfig.xml new file mode 100644 index 000000000..d4f0c1fe3 --- /dev/null +++ b/Config/DefaultRu/SCADA/ScadaComm/Config/ScadaCommSvcConfig.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Report/RepBuilder/AssemblyInfo.cs b/Report/RepBuilder/AssemblyInfo.cs index 009292328..7bf7d2bf5 100644 --- a/Report/RepBuilder/AssemblyInfo.cs +++ b/Report/RepBuilder/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Rapid SCADA")] -[assembly: AssemblyCopyright("Copyright © 2005-2014")] +[assembly: AssemblyCopyright("Copyright © 2005-2016")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/Report/RepBuilder/ExcelRepBuilder.cs b/Report/RepBuilder/ExcelRepBuilder.cs index 4604ca821..6ffd2a688 100644 --- a/Report/RepBuilder/ExcelRepBuilder.cs +++ b/Report/RepBuilder/ExcelRepBuilder.cs @@ -38,15 +38,73 @@ namespace Utils.Report /// public abstract class ExcelRepBuilder : RepBuilder { + /// + /// Пространства имён, которые используются в SpreadsheetML + /// + protected static class XmlNamespaces + { + /// + /// Пространстро имён xmlns + /// + public const string noprefix = "urn:schemas-microsoft-com:office:spreadsheet"; + /// + /// Пространстро имён xmlns:o + /// + public const string o = "urn:schemas-microsoft-com:office:office"; + /// + /// Пространстро имён xmlns:x + /// + public const string x = "urn:schemas-microsoft-com:office:excel"; + /// + /// Пространстро имён xmlns:ss + /// + public const string ss = "urn:schemas-microsoft-com:office:spreadsheet"; + /// + /// Пространстро имён xmlns:html + /// + public const string html = "http://www.w3.org/TR/REC-html40"; + } + /// /// Книга Excel /// protected class Workbook { - protected XmlNode node; // ссылка на XML-узел, соответствующий книге Excel - protected XmlNode stylesNode; // ссылка на XML-узел, содержащий стили книги Excel - protected SortedList styles; // список стилей книги Excel с возможностью доступа по ID стиля - protected List worksheets; // список листов книги Excel + /// + /// Ссылка на XML-узел, соответствующий книге Excel + /// + protected XmlNode node; + /// + /// Ссылка на XML-узел, содержащий стили книги Excel + /// + protected XmlNode stylesNode; + /// + /// Список стилей книги Excel с возможностью доступа по ID стиля + /// + protected SortedList styles; + /// + /// Список листов книги Excel + /// + protected List worksheets; + + + /// + /// Конструктор + /// + protected Workbook() + { + } + + /// + /// Конструктор + /// + /// Ссылка на XML-узел, соответствующий книге Excel + public Workbook(XmlNode xmlNode) + { + node = xmlNode; + styles = new SortedList(); + worksheets = new List(); + } /// @@ -98,25 +156,6 @@ public List Worksheets } - /// - /// Конструктор - /// - protected Workbook() - { - } - - /// - /// Конструктор - /// - /// Ссылка на XML-узел, соответствующий книге Excel - public Workbook(XmlNode xmlNode) - { - node = xmlNode; - styles = new SortedList(); - worksheets = new List(); - } - - /// /// Добавить стиль в конец списка стилей книги Excel и модифицировать дерево XML-документа /// @@ -266,8 +305,32 @@ public void SetColor(XmlNode targetNode, string color) /// protected class Style { - protected XmlNode node; // ссылка на XML-узел, соответствующий стилю книги Excel - protected string id; // идентификатор стиля + /// + /// Ссылка на XML-узел, соответствующий стилю книги Excel + /// + protected XmlNode node; + /// + /// Идентификатор стиля + /// + protected string id; + + + /// + /// Конструктор + /// + protected Style() + { + } + + /// + /// Конструктор + /// + /// Ссылка на XML-узел, соответствующий стилю книги Excel + public Style(XmlNode xmlNode) + { + node = xmlNode; + id = xmlNode.Attributes["ss:ID"].Value; + } /// @@ -298,24 +361,6 @@ public string ID } - /// - /// Конструктор - /// - protected Style() - { - } - - /// - /// Конструктор - /// - /// Ссылка на XML-узел, соответствующий стилю книги Excel - public Style(XmlNode xmlNode) - { - node = xmlNode; - id = xmlNode.Attributes["ss:ID"].Value; - } - - /// /// Клонировать стиль /// @@ -331,10 +376,41 @@ public Style Clone() /// protected class Worksheet { - protected XmlNode node; // ссылка на XML-узел, соответствующий листу книги Excel - protected string name; // имя листа - protected Table table; // таблица с содержимым листа - protected Workbook parentWorkbook; // родительский книга данного листа + /// + /// Ссылка на XML-узел, соответствующий листу книги Excel + /// + protected XmlNode node; + /// + /// Имя листа + /// + protected string name; + /// + /// Таблица с содержимым листа + /// + protected Table table; + /// + /// Родительская книга данного листа + /// + protected Workbook parentWorkbook; + + + /// + /// Конструктор + /// + protected Worksheet() + { + } + + /// + /// Конструктор + /// + /// Ссылка на XML-узел, соответствующий листу книги Excel + public Worksheet(XmlNode xmlNode) + { + node = xmlNode; + name = xmlNode.Attributes["ss:Name"].Value; + table = null; + } /// @@ -397,23 +473,37 @@ public Workbook ParentWorkbook /// - /// Конструктор + /// Установить горизонтальный разделитель области прокрутки /// - protected Worksheet() + public void SplitHorizontal(int rowIndex) { - } + XmlDocument xmlDoc = node.OwnerDocument; + XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmlDoc.NameTable); + nsmgr.AddNamespace("report", XmlNamespaces.x); - /// - /// Конструктор - /// - /// Ссылка на XML-узел, соответствующий листу книги Excel - public Worksheet(XmlNode xmlNode) - { - node = xmlNode; - name = xmlNode.Attributes["ss:Name"].Value; - table = null; - } + XmlNode optionsNode = node.SelectSingleNode("report:WorksheetOptions", nsmgr); + + if (optionsNode != null) + { + string rowIndexStr = rowIndex.ToString(); + + XmlNode splitNode = optionsNode.SelectSingleNode("report:SplitHorizontal", nsmgr); + if (splitNode == null) + { + splitNode = xmlDoc.CreateElement("SplitHorizontal"); + optionsNode.AppendChild(splitNode); + } + splitNode.InnerText = rowIndexStr; + XmlNode paneNode = optionsNode.SelectSingleNode("report:TopRowBottomPane", nsmgr); + if (paneNode == null) + { + paneNode = xmlDoc.CreateElement("TopRowBottomPane"); + optionsNode.AppendChild(paneNode); + } + paneNode.InnerText = rowIndexStr; + } + } /// /// Клонировать лист @@ -441,10 +531,42 @@ public Worksheet Clone() /// protected class Table { - protected XmlNode node; // ссылка на XML-узел, соответствующий таблице листа Excel - protected List columns; // список столбцов таблицы - protected List rows; // список строк таблицы - protected Worksheet parentWorksheet; // родительский лист данной таблицы + /// + /// Ссылка на XML-узел, соответствующий таблице листа Excel + /// + protected XmlNode node; + /// + /// Список столбцов таблицы + /// + protected List columns; + /// + /// Список строк таблицы + /// + protected List rows; + /// + /// Родительский лист данной таблицы + /// + protected Worksheet parentWorksheet; + + + /// + /// Конструктор + /// + protected Table() + { + } + + /// + /// Конструктор + /// + /// Ссылка на XML-узел, соответствующий таблице листа Excel + public Table(XmlNode xmlNode) + { + node = xmlNode; + columns = new List(); + rows = new List(); + parentWorksheet = null; + } /// @@ -496,26 +618,6 @@ public Worksheet ParentWorksheet } - /// - /// Конструктор - /// - protected Table() - { - } - - /// - /// Конструктор - /// - /// Ссылка на XML-узел, соответствующий таблице листа Excel - public Table(XmlNode xmlNode) - { - node = xmlNode; - columns = new List(); - rows = new List(); - parentWorksheet = null; - } - - /// /// Удалить атрибуты XML-узла таблицы, необходимые для корректного отображения книги Excel /// @@ -528,111 +630,119 @@ public void RemoveTableNodeAttrs() /// /// Найти столбец в таблице по индексу /// - /// Индекс искомого столбца - /// Получен столбец с указанным индексом (true) или с ближайшим большим (false) - /// Столбец, удовлетворяющий критерию поиска, или null, если индексы всех столбцов меньше заданного - public Column FindColumn(int columnIndex, out bool exact) + /// Индекс искомого столбца, начиная с 1 + /// Столбец, удовлетворяющий критерию поиска, или null, если столбец не найден + public Column FindColumn(int columnIndex) { int index = 0; foreach (Column column in columns) { - if (column.Index > 0) - index = column.Index; - else - index++; + index = column.Index > 0 ? column.Index : index + 1; + int endIndex = index + column.Span; - if (index == columnIndex) - { - exact = true; + if (index <= columnIndex && columnIndex <= endIndex) return column; - } - else if (index > columnIndex) - { - exact = false; - return column; - } + + index = endIndex; } - exact = false; return null; } /// /// Добавить столбец в конец списка столбцов таблицы и модифицировать дерево XML-документа /// - /// Добавляемый столбец public void AppendColumn(Column column) { + if (columns.Count > 0) + node.InsertAfter(column.Node, columns[columns.Count - 1].Node); + else + node.PrependChild(column.Node); + + column.ParentTable = this; columns.Add(column); - node.AppendChild(column.Node); } /// /// Вставить столбец в список столбцов таблицы и модифицировать дерево XML-документа /// - /// Индекс вставляемого столбца в списке - /// Вставляемый столбец public void InsertColumn(int listIndex, Column column) { - columns.Insert(listIndex, column); - - if (columns.Count == 1) - node.AppendChild(column.Node); - else if (listIndex == 0) + if (columns.Count == 0 || listIndex == 0) node.PrependChild(column.Node); else node.InsertAfter(column.Node, columns[listIndex - 1].Node); + + column.ParentTable = this; + columns.Insert(listIndex, column); } /// /// Удалить столбец из списка столбцов таблицы и модифицировать дерево XML-документа /// - /// Индекс удаляемого столбца в списке public void RemoveColumn(int listIndex) { Column column = columns[listIndex]; + column.ParentTable = null; node.RemoveChild(column.Node); columns.RemoveAt(listIndex); } + /// + /// Удалить все столбцы из списка столбцов таблицы и модифицировать дерево XML-документа + /// + public void RemoveAllColumns() + { + while (columns.Count > 0) + RemoveColumn(0); + } + /// /// Добавить строку в конец списка строк таблицы и модифицировать дерево XML-документа /// - /// Добавляемая строка public void AppendRow(Row row) { - rows.Add(row); node.AppendChild(row.Node); + row.ParentTable = this; + rows.Add(row); } /// /// Вставить строку в список строк таблицы и модифицировать дерево XML-документа /// - /// Индекс вставляемой строки в списке - /// Вставляемая строка public void InsertRow(int listIndex, Row row) { - rows.Insert(listIndex, row); - - if (rows.Count == 1) + if (rows.Count == 0) node.AppendChild(row.Node); else if (listIndex == 0) - node.PrependChild(row.Node); + node.InsertBefore(row.Node, rows[0].Node); else node.InsertAfter(row.Node, rows[listIndex - 1].Node); + + row.ParentTable = this; + rows.Insert(listIndex, row); } /// /// Удалить строку из списка строк таблицы и модифицировать дерево XML-документа /// - /// Индекс удаляемой строки в списке public void RemoveRow(int listIndex) { Row row = rows[listIndex]; + row.ParentTable = null; node.RemoveChild(row.Node); rows.RemoveAt(listIndex); } + /// + /// Удалить все строки из списка строк таблицы и модифицировать дерево XML-документа + /// + public void RemoveAllRows() + { + while (rows.Count > 0) + RemoveRow(0); + } + /// /// Клонировать таблицу /// @@ -666,9 +776,39 @@ public Table Clone() /// protected class Column { - protected XmlNode node; // ссылка на XML-узел, соответствующий столбцу таблицы Excel - protected int index; // индекс столбца, 0 - неопределён - protected Table parentTable; // родительская таблица данного столбца + /// + /// Ссылка на XML-узел, соответствующий столбцу таблицы Excel + /// + protected XmlNode node; + /// + /// Родительская таблица данного столбца + /// + protected Table parentTable; + /// + /// Индекс столбца, 0 - неопределён + /// + protected int index; + + + /// + /// Конструктор + /// + protected Column() + { + } + + /// + /// Конструктор + /// + /// Ссылка на XML-узел, соответствующий столбцу таблицы Excel + public Column(XmlNode xmlNode) + { + node = xmlNode; + parentTable = null; + + XmlAttribute attr = node.Attributes["ss:Index"]; + index = attr == null ? 0 : int.Parse(attr.Value); + } /// @@ -683,70 +823,68 @@ public XmlNode Node } /// - /// Получить или установить индекс столбца (0 - неопределён), при установке модифицируется дерево XML-документа + /// Получить или установить родительскую таблицу данного столбца /// - public int Index + public Table ParentTable { get { - return index; + return parentTable; } set { - if (index <= 0) - { - if (value <= 0) - index = value; - else - { - XmlAttribute attr = node.OwnerDocument.CreateAttribute("ss:Index"); - attr.Value = index.ToString(); - node.Attributes.SetNamedItem(attr); - } - } - else - { - if (value <= 0) - node.Attributes.RemoveNamedItem("ss:Index"); - else - node.Attributes["ss:Index"].Value = index.ToString(); - } + parentTable = value; } } /// - /// Получить или установить родительскую таблицу данного столбца + /// Получить или установить индекс столбца (0 - неопределён), при установке модифицируется дерево XML-документа /// - public Table ParentTable + public int Index { get { - return parentTable; + return index; } set { - parentTable = value; + index = value; + SetAttribute(node, "Index", XmlNamespaces.ss, index <= 0 ? null : index.ToString(), true); } } - /// - /// Конструктор + /// Получить или установить ширину /// - protected Column() + public double Width { + get + { + string widthStr = GetAttribute(node, "ss:Width"); + double width; + return double.TryParse(widthStr, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out width) ? + width : 0; + } + set + { + SetColumnWidth(node, value); + } } /// - /// Конструктор + /// Получить или установить количество объединямых колонок справа /// - /// Ссылка на XML-узел, соответствующий столбцу таблицы Excel - public Column(XmlNode xmlNode) + public int Span { - node = xmlNode; - XmlAttribute attr = node.Attributes["ss:Index"]; - index = attr == null ? 0 : int.Parse(attr.Value); - parentTable = null; + get + { + string valStr = GetAttribute(node, "ss:Span"); + return valStr == "" ? 0 : int.Parse(valStr); + } + set + { + SetAttribute(node, "Span", XmlNamespaces.ss, value < 1 ? "" : value.ToString(), true); + } } @@ -760,6 +898,16 @@ public Column Clone() columnClone.parentTable = parentTable; return columnClone; } + + /// + /// Установить ширину столбца + /// + public static void SetColumnWidth(XmlNode columnNode, double width) + { + SetAttribute(columnNode, "AutoFitWidth", XmlNamespaces.ss, "0"); + SetAttribute(columnNode, "Width", XmlNamespaces.ss, + width > 0 ? width.ToString(NumberFormatInfo.InvariantInfo) : "", true); + } } /// @@ -767,9 +915,37 @@ public Column Clone() /// protected class Row { - protected XmlNode node; // ссылка на XML-узел, соответствующий строке таблицы Excel - protected List cells; // список ячеек строки - protected Table parentTable; // родительская таблица данной строки + /// + /// Ссылка на XML-узел, соответствующий строке таблицы Excel + /// + protected XmlNode node; + /// + /// Список ячеек строки + /// + protected List cells; + /// + /// Родительская таблица данной строки + /// + protected Table parentTable; + + + /// + /// Конструктор + /// + protected Row() + { + } + + /// + /// Конструктор + /// + /// Ссылка на XML-узел, соответствующий строке таблицы Excel + public Row(XmlNode xmlNode) + { + node = xmlNode; + cells = new List(); + parentTable = null; + } /// @@ -809,23 +985,22 @@ public Table ParentTable } } - - /// - /// Конструктор - /// - protected Row() - { - } - /// - /// Конструктор + /// Получить или установить высоту /// - /// Ссылка на XML-узел, соответствующий строке таблицы Excel - public Row(XmlNode xmlNode) + public double Height { - node = xmlNode; - cells = new List(); - parentTable = null; + get + { + string heightStr = GetAttribute(node, "ss:Height"); + double height; + return double.TryParse(heightStr, NumberStyles.Any, NumberFormatInfo.InvariantInfo, out height) ? + height : 0; + } + set + { + SetRowHeight(node, value); + } } @@ -852,58 +1027,42 @@ public Row Clone() /// /// Найти ячейку в строке по индексу /// - /// Индекс искомой ячейки - /// Получена ячейка с указанным индексом (true) или с ближайшим большим (false) - /// Ячейка, удовлетворяющая критерию поиска, или null, если индексы всех ячеек меньше заданного - public Cell FindCell(int cellIndex, out bool exact) + /// Индекс искомой ячейки, начиная с 1 + /// Ячейка, удовлетворяющая критерию поиска, или null, если ячейка не найдена + public Cell FindCell(int cellIndex) { int index = 0; foreach (Cell cell in cells) { - XmlAttribute attr = cell.Node.Attributes["ss:MergeAcross"]; - int merge = attr == null ? 0 : int.Parse(attr.Value); - - if (cell.Index > 0) - index = cell.Index; - else - index++; + index = cell.Index > 0 ? cell.Index : index + 1; + int endIndex = index + cell.MergeAcross; - if (index == cellIndex || index < cellIndex && cellIndex <= index + merge) - { - exact = true; + if (index <= cellIndex && cellIndex <= endIndex) return cell; - } - else if (index > cellIndex) - { - exact = false; - return cell; - } - index += merge; + index = endIndex; } - exact = false; return null; } /// /// Добавить ячейку в конец списка ячеек строки и модифицировать дерево XML-документа /// - /// Добавляемая ячейка public void AppendCell(Cell cell) { cells.Add(cell); + cell.ParentRow = this; node.AppendChild(cell.Node); } /// /// Вставить ячейку в список ячеек строки и модифицировать дерево XML-документа /// - /// Индекс вставляемой ячейки в списке - /// Вставляемая ячейка public void InsertCell(int listIndex, Cell cell) { cells.Insert(listIndex, cell); + cell.ParentRow = this; if (cells.Count == 1) node.AppendChild(cell.Node); @@ -916,10 +1075,10 @@ public void InsertCell(int listIndex, Cell cell) /// /// Удалить ячейку из списка ячеек строки и модифицировать дерево XML-документа /// - /// Индекс удаляемой ячейки в списке public void RemoveCell(int listIndex) { Cell cell = cells[listIndex]; + cell.ParentRow = null; node.RemoveChild(cell.Node); cells.RemoveAt(listIndex); } @@ -928,28 +1087,11 @@ public void RemoveCell(int listIndex) /// /// Установить высоту строки /// - /// Высота строки - public void SetRowHeight(double height) - { - SetRowHeight(node, height); - } - - /// - /// Установить высоту строки - /// - /// Ссылка на XML-узел, соответствующий строке таблицы Excel - /// Высота строки public static void SetRowHeight(XmlNode rowNode, double height) { - rowNode.Attributes.RemoveNamedItem("ss:AutoFitHeight"); - rowNode.Attributes.RemoveNamedItem("ss:Height"); - - NumberFormatInfo nfi = new NumberFormatInfo(); - nfi.NumberDecimalSeparator = "."; - - XmlAttribute attr = rowNode.OwnerDocument.CreateAttribute("ss", "Height", rowNode.NamespaceURI); - attr.Value = height.ToString(nfi); - rowNode.Attributes.Append(attr); + SetAttribute(rowNode, "AutoFitHeight", XmlNamespaces.ss, "0"); + SetAttribute(rowNode, "Height", XmlNamespaces.ss, + height > 0 ? height.ToString(NumberFormatInfo.InvariantInfo) : "", true); } } @@ -958,10 +1100,59 @@ public static void SetRowHeight(XmlNode rowNode, double height) /// protected class Cell { - protected XmlNode node; // ссылка на XML-узел, соответствующий ячейке строки таблицы Excel - protected XmlNode dataNode; // ссылка на XML-узел, соответствующий данным ячейки - protected int index; // индекс ячейки, 0 - неопределён - protected Row parentRow; // родительская строка данной ячейки + /// + /// Типы данных ячеек + /// + public static class DataTypes + { + /// + /// Строковый тип + /// + public const string String = "String"; + /// + /// Числовой тип + /// + public const string Number = "Number"; + } + + /// + /// Ссылка на XML-узел, соответствующий ячейке строки таблицы Excel + /// + protected XmlNode node; + /// + /// Ссылка на XML-узел, соответствующий данным ячейки + /// + protected XmlNode dataNode; + /// + /// Родительская строка данной ячейки + /// + protected Row parentRow; + /// + /// Индекс ячейки, 0 - неопределён + /// + protected int index; + + + /// + /// Конструктор + /// + protected Cell() + { + } + + /// + /// Конструктор + /// + /// Ссылка на XML-узел, соответствующий ячейке строки таблицы Excel + public Cell(XmlNode xmlNode) + { + node = xmlNode; + dataNode = null; + parentRow = null; + + XmlAttribute attr = node.Attributes["ss:Index"]; + index = attr == null ? 0 : int.Parse(attr.Value); + } /// @@ -990,6 +1181,21 @@ public XmlNode DataNode } } + /// + /// Получить или установить родительскую строку данной ячейки + /// + public Row ParentRow + { + get + { + return parentRow; + } + set + { + parentRow = value; + } + } + /// /// Получить или установить индекс ячейки (0 - неопределён), при установке модифицируется дерево XML-документа /// @@ -1001,63 +1207,128 @@ public int Index } set { - if (index <= 0) - { - if (value <= 0) - index = value; - else - { - XmlAttribute attr = node.OwnerDocument.CreateAttribute("ss:Index"); - attr.Value = index.ToString(); - node.Attributes.SetNamedItem(attr); - } - } - else - { - if (value <= 0) - node.Attributes.RemoveNamedItem("ss:Index"); - else - node.Attributes["ss:Index"].Value = index.ToString(); - } + index = value; + SetAttribute(node, "Index", XmlNamespaces.ss, index <= 0 ? null : index.ToString(), true); } } /// - /// Получить или установить родительскую строку данной ячейки + /// Получить или установить тип данных (формат) ячейки /// - public Row ParentRow + public string DataType { get { - return parentRow; + return GetAttribute(dataNode, "ss:Type"); } set { - parentRow = value; + SetAttribute(dataNode, "Type", XmlNamespaces.ss, + string.IsNullOrEmpty(value) ? DataTypes.String : value); } } + /// + /// Получить или установить текст + /// + public string Text + { + get + { + return dataNode == null ? "" : dataNode.InnerText; + } + set + { + if (dataNode != null) + dataNode.InnerText = value; + } + } /// - /// Конструктор + /// Получить или установить формулу /// - protected Cell() + public string Formula { + get + { + return GetAttribute(node, "ss:Formula"); + } + set + { + SetAttribute(node, "Formula", XmlNamespaces.ss, value, true); + } } /// - /// Конструктор + /// Получить или установить ид. стиля /// - /// Ссылка на XML-узел, соответствующий ячейке строки таблицы Excel - public Cell(XmlNode xmlNode) + public string StyleID { - node = xmlNode; - dataNode = null; - XmlAttribute attr = node.Attributes["ss:Index"]; - index = attr == null ? 0 : int.Parse(attr.Value); - parentRow = null; + get + { + return GetAttribute(node, "ss:StyleID"); + } + set + { + SetAttribute(node, "StyleID", XmlNamespaces.ss, value, true); + } } + /// + /// Получить или установить количество объединямых ячеек справа + /// + public int MergeAcross + { + get + { + string valStr = GetAttribute(node, "ss:MergeAcross"); + return valStr == "" ? 0 : int.Parse(valStr); + } + set + { + SetAttribute(node, "MergeAcross", XmlNamespaces.ss, value < 1 ? "" : value.ToString(), true); + } + } + + /// + /// Получить или установить количество объединямых ячеек вниз + /// + public int MergeDown + { + get + { + string valStr = GetAttribute(node, "ss:MergeDown"); + return valStr == "" ? 0 : int.Parse(valStr); + } + set + { + SetAttribute(node, "MergeDown", XmlNamespaces.ss, value < 1 ? "" : value.ToString(), true); + } + } + + + /// + /// Рассчитать индекс в строке + /// + public int CalcIndex() + { + if (index > 0) + { + return index; + } + else + { + int index = 0; + foreach (Cell cell in parentRow.Cells) + { + index = cell.Index > 0 ? cell.Index : index + 1; + if (cell == this) + return index; + index += cell.MergeAcross; + } + return 0; + } + } /// /// Клонировать ячейку @@ -1082,21 +1353,12 @@ public Cell Clone() return cellClone; } - /// - /// Установить тип данных (формат) ячейки - /// - public void SetDataType(string dataTypeName) - { - if (dataNode != null) - dataNode.Attributes["ss:Type"].Value = dataTypeName; - } - /// /// Установить числовой тип данных ячейки /// public void SetNumberType() { - SetDataType("Number"); + DataType = DataTypes.Number; } } @@ -1106,9 +1368,9 @@ public void SetNumberType() /// protected const string Break = " "; /// - /// Имя с префиксом XML-элемента в шаблоне, значение которого может содержать директивы изменения + /// Имя XML-элемента в шаблоне, который может содержать директивы преобразований /// - protected const string ElemName = "Data"; + protected const string DirectiveElem = "Data"; /// @@ -1122,23 +1384,23 @@ public void SetNumberType() /// /// Обрабатываемый лист Excel /// - protected Worksheet worksheet; + protected Worksheet procWorksheet; /// /// Обрабатываемая таблица листа Excel /// - protected Table table; + protected Table procTable; /// /// Обрабатываемая строка таблицы Excel /// - protected Row row; + protected Row procRow; /// /// Обрабатываемая ячейка строки таблицы Excel /// - protected Cell cell; + protected Cell procCell; /// /// XML-узлы могут иметь текст, содержащий переносы строк /// - protected bool textBreaked; + protected bool textBroken; /// @@ -1164,50 +1426,93 @@ public override string RepFormat /// - /// Найти атрибут (директиву) в строке и получить его значение + /// Получить значение атрибута XML-узла + /// + protected static string GetAttribute(XmlNode xmlNode, string name) + { + if (xmlNode == null) + { + return ""; + } + else + { + XmlAttribute xmlAttr = xmlNode.Attributes[name]; + return xmlAttr == null ? "" : xmlAttr.Value; + } + } + + /// + /// Установить атрибут XML-узла, создав его при необходимости + /// + protected static void SetAttribute(XmlNode xmlNode, string localName, string namespaceURI, string value, + bool removeEmpty = false) + { + if (xmlNode != null) + { + if (string.IsNullOrEmpty(value) && removeEmpty) + { + xmlNode.Attributes.RemoveNamedItem(localName, namespaceURI); + } + else + { + XmlAttribute xmlAttr = xmlNode.Attributes.GetNamedItem(localName, namespaceURI) as XmlAttribute; + if (xmlAttr == null) + { + xmlAttr = xmlNode.OwnerDocument.CreateAttribute("", localName, namespaceURI); + xmlAttr.Value = value; + xmlNode.Attributes.SetNamedItem(xmlAttr); + } + else + { + xmlAttr.Value = value; + } + } + } + } + + /// + /// Найти директиву в строке, получить её значение и остаток строки /// - /// Строка для поиска - /// Имя искомого атрибута - /// Значение атрибута - /// Найден ли атрибут - protected bool FindAttr(string str, string attrName, out string attrVal) + protected bool FindDirective(string s, string attrName, out string attrVal, out string rest) { // "attrName=attrVal", вместо '=' может быть любой символ - attrVal = ""; - if (str.StartsWith(attrName)) + int valStartInd = attrName.Length + 1; + if (valStartInd <= s.Length && s.StartsWith(attrName, StringComparison.Ordinal)) { - int start = attrName.Length + 1; - if (start < str.Length) + int valEndInd = s.IndexOf(" ", valStartInd); + if (valEndInd < 0) { - int end = str.IndexOf(" ", start); - if (end < 0) end = str.Length; - attrVal = str.Substring(start, end - start); + attrVal = s.Substring(valStartInd); + rest = ""; + } + else + { + attrVal = s.Substring(valStartInd, valEndInd - valStartInd); + rest = s.Substring(valEndInd + 1); } return true; } else + { + attrVal = ""; + rest = s; return false; + } } /// /// Установить XML-узлу текст, содержащий переносы строк, разбив при необходимости элемент на несколько /// - /// XML-узел - /// Устанавливаемый текст - /// Обозначение переноса строки в устанавливаемом тексте protected void SetNodeTextWithBreak(XmlNode xmlNode, string text, string textBreak) { if (text == null) text = ""; xmlNode.InnerText = text.Replace(textBreak, Break); - textBreaked = true; + textBroken = true; } /// /// Установить XML-узлу текст, содержащий переносы строк, разбив при необходимости элемент на несколько /// - /// XML-узел - /// Устанавливаемый текст - /// Обозначение переноса строки в устанавливаемом тексте protected void SetNodeTextWithBreak(XmlNode xmlNode, object text, string textBreak) { string textStr = text == null ? "" : text.ToString(); @@ -1217,8 +1522,6 @@ protected void SetNodeTextWithBreak(XmlNode xmlNode, object text, string textBre /// /// Установить XML-узлу текст, содержащий переносы строк "\n", разбив при необходимости элемент на несколько /// - /// XML-узел - /// Устанавливаемый текст protected void SetNodeTextWithBreak(XmlNode xmlNode, string text) { SetNodeTextWithBreak(xmlNode, text, "\n"); @@ -1227,8 +1530,6 @@ protected void SetNodeTextWithBreak(XmlNode xmlNode, string text) /// /// Установить XML-узлу текст, содержащий переносы строк "\n", разбив при необходимости элемент на несколько /// - /// XML-узел - /// Устанавливаемый текст protected void SetNodeTextWithBreak(XmlNode xmlNode, object text) { SetNodeTextWithBreak(xmlNode, text, "\n"); @@ -1236,43 +1537,33 @@ protected void SetNodeTextWithBreak(XmlNode xmlNode, object text) /// - /// Начальная обработка дерева XML-документа + /// Предварительно обработать дерево XML-документа /// protected virtual void StartXmlDocProc() { } /// - /// Рекурсивный обход, распознавание и обработка дерева XML-документа согласно директивам для получения отчёта + /// Рекурсивно обработать дерево XML-документа согласно директивам /// - /// Обрабатываемый XML-узел protected virtual void XmlDocProc(XmlNode xmlNode) { - if (xmlNode.Name == ElemName) + if (xmlNode.Name == DirectiveElem) { - cell.DataNode = xmlNode; - - // поиск директив преобразований элементов - string nodeVal = xmlNode.InnerText; - string attrVal; - if (FindAttr(nodeVal, "repRow", out attrVal)) - { - if (nodeVal.Length < 8 /*"repRow=".Length + 1*/ + attrVal.Length) - xmlNode.InnerText = ""; - else - xmlNode.InnerText = nodeVal.Substring(8 + attrVal.Length); - ProcRow(cell, attrVal); - } - else if (FindAttr(nodeVal, "repVal", out attrVal)) - ProcVal(cell, attrVal); + procCell.DataNode = xmlNode; + FindDirectives(procCell); // поиск и обработка директив } else { // формирование книги Excel на основе XML-документа if (xmlNode.Name == "Workbook") + { workbook = new Workbook(xmlNode); + } else if (xmlNode.Name == "Styles") + { workbook.StylesNode = xmlNode; + } else if (xmlNode.Name == "Style") { Style style = new Style(xmlNode); @@ -1280,33 +1571,33 @@ protected virtual void XmlDocProc(XmlNode xmlNode) } else if (xmlNode.Name == "Worksheet") { - worksheet = new Worksheet(xmlNode); - worksheet.ParentWorkbook = workbook; - workbook.Worksheets.Add(worksheet); + procWorksheet = new Worksheet(xmlNode); + procWorksheet.ParentWorkbook = workbook; + workbook.Worksheets.Add(procWorksheet); } else if (xmlNode.Name == "Table") { - table = new Table(xmlNode); - table.ParentWorksheet = worksheet; - worksheet.Table = table; + procTable = new Table(xmlNode); + procTable.ParentWorksheet = procWorksheet; + procWorksheet.Table = procTable; } else if (xmlNode.Name == "Column") { Column column = new Column(xmlNode); - column.ParentTable = table; - table.Columns.Add(column); + column.ParentTable = procTable; + procTable.Columns.Add(column); } else if (xmlNode.Name == "Row") { - row = new Row(xmlNode); - row.ParentTable = table; - table.Rows.Add(row); + procRow = new Row(xmlNode); + procRow.ParentTable = procTable; + procTable.Rows.Add(procRow); } else if (xmlNode.Name == "Cell") { - cell = new Cell(xmlNode); - cell.ParentRow = row; - row.Cells.Add(cell); + procCell = new Cell(xmlNode); + procCell.ParentRow = procRow; + procRow.Cells.Add(procCell); } // рекурсивный перебор потомков текущего элемента @@ -1317,16 +1608,15 @@ protected virtual void XmlDocProc(XmlNode xmlNode) } /// - /// Окончательная обработка дерева XML-документа + /// Окончательно обработать дерево XML-документа /// protected virtual void FinalXmlDocProc() { } /// - /// Обработка структур, представляющих книгу Excel + /// Обработать структуры, представляющие таблицу листа Excel /// - /// Таблица листа Excel protected virtual void ExcelProc(Table table) { foreach (Row row in table.Rows) @@ -1334,47 +1624,47 @@ protected virtual void ExcelProc(Table table) } /// - /// Обработка структур, представляющих книгу Excel + /// Обработать структуры, представляющие строку таблицы листа Excel /// - /// Строка таблицы Excel protected virtual void ExcelProc(Row row) { + // поиск и обработка директив для ячеек строки foreach (Cell cell in row.Cells) + FindDirectives(cell); + } + + /// + /// Найти и обработать директивы, которые могут содержаться в заданной ячейке + /// + protected virtual void FindDirectives(Cell cell) + { + XmlNode dataNode = cell.DataNode; + if (dataNode != null) { - // поиск директив преобразований элементов - XmlNode dataNode = cell.DataNode; - if (dataNode != null) + string attrVal; + string rest; + if (FindDirective(dataNode.InnerText, "repRow", out attrVal, out rest)) { - string nodeVal = cell.DataNode.InnerText; - string attrVal; - if (FindAttr(nodeVal, "repRow", out attrVal)) - { - if (nodeVal.Length < 8 /*"repRow=".Length + 1*/ + attrVal.Length) - dataNode.InnerText = ""; - else - dataNode.InnerText = nodeVal.Substring(8 + attrVal.Length); - ProcRow(cell, attrVal); - } - else if (FindAttr(nodeVal, "repVal", out attrVal)) - ProcVal(cell, attrVal); + dataNode.InnerText = rest; + ProcRow(cell, attrVal); + } + else if (FindDirective(dataNode.InnerText, "repVal", out attrVal, out rest)) + { + ProcVal(cell, attrVal); } } } /// - /// Обработка директивы, изменяющей значение элемента + /// Обработать директиву, связанную со значением ячейки /// - /// Ячейка строки таблицы Excel, содержащая директиву - /// Имя элемента, заданное директивой protected virtual void ProcVal(Cell cell, string valName) { } /// - /// Обработка директивы, создающей строки таблицы + /// Обработать директиву, связанную со строкой таблицы /// - /// Ячейка строки таблицы Excel, содержащая директиву - /// Имя строки, заданное директивой protected virtual void ProcRow(Cell cell, string rowName) { } @@ -1383,8 +1673,7 @@ protected virtual void ProcRow(Cell cell, string rowName) /// /// Генерировать отчёт в поток в формате SpreadsheetML /// - /// Выходной поток - /// Директория шаблона со '\' на конце + /// Директория шаблона должна оканчиваться на слеш public override void Make(Stream outStream, string templateDir) { // имя файла шаблона отчёта @@ -1396,11 +1685,11 @@ public override void Make(Stream outStream, string templateDir) // инициализация полей workbook = null; - worksheet = null; - table = null; - row = null; - cell = null; - textBreaked = false; + procWorksheet = null; + procTable = null; + procRow = null; + procCell = null; + textBroken = false; // создание отчёта - модификация xmlDoc StartXmlDocProc(); @@ -1408,17 +1697,20 @@ public override void Make(Stream outStream, string templateDir) FinalXmlDocProc(); // запись в выходной поток - if (textBreaked) + if (textBroken) { StringWriter stringWriter = new StringWriter(); xmlDoc.Save(stringWriter); - string xmlText = stringWriter.GetStringBuilder().Replace("encoding=\"utf-16\"", "encoding=\"utf-8\""). - Replace("
", " ").ToString(); + string xmlText = stringWriter.GetStringBuilder() + .Replace("encoding=\"utf-16\"", "encoding=\"utf-8\"") + .Replace("
", " ").ToString(); byte[] bytes = Encoding.UTF8.GetBytes(xmlText); outStream.Write(bytes, 0, bytes.Length); } else + { xmlDoc.Save(outStream); + } } } } diff --git a/Report/RepBuilder/RepBuilder.cs b/Report/RepBuilder/RepBuilder.cs index ebc1efabd..c78281f46 100644 --- a/Report/RepBuilder/RepBuilder.cs +++ b/Report/RepBuilder/RepBuilder.cs @@ -1,5 +1,5 @@ /* - * Copyright 2014 Mikhail Shiryaev + * Copyright 2016 Mikhail Shiryaev * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,10 +20,9 @@ * * Author : Mikhail Shiryaev * Created : 2006 - * Modified : 2006 + * Modified : 2016 */ -using System; using System.IO; using System.Windows.Forms; @@ -38,26 +37,23 @@ public abstract class RepBuilder /// /// Получить имя отчёта /// - public abstract string RepName - { - get; - } + public abstract string RepName { get; } /// /// Получить описание отчёта /// - public abstract string RepDescr + public virtual string RepDescr { - get; + get + { + return ""; + } } /// /// Получить формат отчёта /// - public abstract string RepFormat - { - get; - } + public abstract string RepFormat { get; } /// /// Получить имя файла шаблона diff --git a/ScadaAdmin/ScadaAdmin/AppCode/AppPhrases.cs b/ScadaAdmin/ScadaAdmin/AppCode/AppPhrases.cs index a77f207a7..6d1b4f31c 100644 --- a/ScadaAdmin/ScadaAdmin/AppCode/AppPhrases.cs +++ b/ScadaAdmin/ScadaAdmin/AppCode/AppPhrases.cs @@ -1,5 +1,5 @@ /* - * Copyright 2015 Mikhail Shiryaev + * Copyright 2016 Mikhail Shiryaev * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ * * Author : Mikhail Shiryaev * Created : 2014 - * Modified : 2015 + * Modified : 2016 */ using Scada; @@ -114,6 +114,8 @@ static AppPhrases() public static string CmdValsNotFound { get; private set; } public static string CreateCnlsImpossible { get; private set; } public static string CreateCnlsStart { get; private set; } + public static string InCnlNameTrancated { get; private set; } + public static string CtrlCnlNameTrancated { get; private set; } public static string NumFormatNotFound { get; private set; } public static string TextFormatNotFound { get; private set; } public static string AddedInCnlsCount { get; private set; } @@ -280,6 +282,8 @@ private static void SetToDefault() CmdValsNotFound = "Не найдены значения команды \"{0}\"."; CreateCnlsImpossible = "Создание каналов невозможно."; CreateCnlsStart = "Создание каналов."; + InCnlNameTrancated = "Наименование входного канала {0} было обрезано."; + CtrlCnlNameTrancated = "Наименование канала управления {0} было обрезано."; NumFormatNotFound = "Не найден формат входного канала {0}. Описание формата: числовой, количество знаков дробной части равно {1}."; TextFormatNotFound = "Не найден формат входного канала {0}. Описание формата: текстовый."; AddedInCnlsCount = "Добавлено входных каналов: {0}."; @@ -452,6 +456,8 @@ public static void Init() CmdValsNotFound = dict.GetPhrase("CmdValsNotFound", CmdValsNotFound); CreateCnlsImpossible = dict.GetPhrase("CreateCnlsImpossible", CreateCnlsImpossible); CreateCnlsStart = dict.GetPhrase("CreateCnlsStart", CreateCnlsStart); + InCnlNameTrancated = dict.GetPhrase("InCnlNameTrancated", InCnlNameTrancated); + CtrlCnlNameTrancated = dict.GetPhrase("CtrlCnlNameTrancated", CtrlCnlNameTrancated); NumFormatNotFound = dict.GetPhrase("NumFormatNotFound", NumFormatNotFound); TextFormatNotFound = dict.GetPhrase("TextFormatNotFound", TextFormatNotFound); AddedInCnlsCount = dict.GetPhrase("AddedInCnlsCount", AddedInCnlsCount); diff --git a/ScadaAdmin/ScadaAdmin/AppCode/CreateCnls.cs b/ScadaAdmin/ScadaAdmin/AppCode/CreateCnls.cs index 7b2669f1e..f8c0955e6 100644 --- a/ScadaAdmin/ScadaAdmin/AppCode/CreateCnls.cs +++ b/ScadaAdmin/ScadaAdmin/AppCode/CreateCnls.cs @@ -1,5 +1,5 @@ /* - * Copyright 2015 Mikhail Shiryaev + * Copyright 2016 Mikhail Shiryaev * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ * * Author : Mikhail Shiryaev * Created : 2015 - * Modified : 2015 + * Modified : 2016 */ using Scada; @@ -326,7 +326,16 @@ private static DataRow CreateInCnlRow(DataTable tblInCnl, DataTable tblFormat, DataRow newInCnlRow = tblInCnl.NewRow(); newInCnlRow["CnlNum"] = inCnl.CnlNum; newInCnlRow["Active"] = true; - newInCnlRow["Name"] = kpNameToInsert + inCnl.CnlName; + + int maxCnlNameLen = tblInCnl.Columns["Name"].MaxLength; + string cnlName = kpNameToInsert + inCnl.CnlName; + if (cnlName.Length > maxCnlNameLen) + { + cnlName = cnlName.Substring(0, maxCnlNameLen); + writer.WriteLine(string.Format(AppPhrases.InCnlNameTrancated, inCnl.CnlNum)); + } + newInCnlRow["Name"] = cnlName; + newInCnlRow["CnlTypeID"] = inCnl.CnlTypeID; newInCnlRow["ObjNum"] = objNum; newInCnlRow["KPNum"] = kpNum; @@ -342,26 +351,17 @@ private static DataRow CreateInCnlRow(DataTable tblInCnl, DataTable tblFormat, { int ind = tblFormat.DefaultView.Find(new object[] { true, inCnl.DecDigits }); if (ind >= 0) - { newInCnlRow["FormatID"] = tblFormat.DefaultView[ind]["FormatID"]; - } else - { - writer.WriteLine(string.Format( - AppPhrases.NumFormatNotFound, inCnl.CnlNum, inCnl.DecDigits)); - } + writer.WriteLine(string.Format(AppPhrases.NumFormatNotFound, inCnl.CnlNum, inCnl.DecDigits)); } else { int ind = tblFormat.DefaultView.Find(new object[] { false, DBNull.Value }); if (ind >= 0) - { newInCnlRow["FormatID"] = tblFormat.DefaultView[ind]["FormatID"]; - } else - { writer.WriteLine(string.Format(AppPhrases.TextFormatNotFound, inCnl.CnlNum)); - } } newInCnlRow["UnitID"] = string.IsNullOrEmpty(inCnl.UnitName) ? @@ -390,12 +390,21 @@ private static DataRow CreateInCnlRow(DataTable tblInCnl, DataTable tblFormat, /// Создать строку канала управления /// private static DataRow CreateCtrlCnlRow(DataTable tblCtrlCnl, SortedList cmdValList, - KPView.CtrlCnlPrototype ctrlCnl, object objNum, int kpNum, string kpNameToInsert) + KPView.CtrlCnlPrototype ctrlCnl, object objNum, int kpNum, string kpNameToInsert, StreamWriter writer) { DataRow newCtrlCnlRow = tblCtrlCnl.NewRow(); newCtrlCnlRow["CtrlCnlNum"] = ctrlCnl.CtrlCnlNum; newCtrlCnlRow["Active"] = true; - newCtrlCnlRow["Name"] = kpNameToInsert + ctrlCnl.CtrlCnlName; + + int maxCtrlCnlNameLen = tblCtrlCnl.Columns["Name"].MaxLength; + string ctrlCnlName = kpNameToInsert + ctrlCnl.CtrlCnlName; + if (ctrlCnlName.Length > maxCtrlCnlNameLen) + { + ctrlCnlName = ctrlCnlName.Substring(0, maxCtrlCnlNameLen); + writer.WriteLine(string.Format(AppPhrases.CtrlCnlNameTrancated, ctrlCnl.CtrlCnlNum)); + } + newCtrlCnlRow["Name"] = ctrlCnlName; + newCtrlCnlRow["CmdTypeID"] = ctrlCnl.CmdTypeID; newCtrlCnlRow["ObjNum"] = objNum; newCtrlCnlRow["KPNum"] = kpNum; @@ -434,7 +443,7 @@ private static bool UpdateCnls(DataTable dataTable, string descr, StreamWriter w } else { - writer.WriteLine(string.Format(descr, updRowCnt) + ". " + + writer.WriteLine(string.Format(descr, updRowCnt) + " " + string.Format(AppPhrases.ErrorsCount, errRowCnt)); foreach (DataRow row in rowsInError) writer.WriteLine(string.Format(AppPhrases.CnlError, row[0], row.RowError)); @@ -733,7 +742,7 @@ public static bool CreateChannels(List kpInfoList, bool insertKPName, { ctrlCnl.CtrlCnlNum = ctrlCnlNum; DataRow newCtrlCnlRow = CreateCtrlCnlRow(tblCtrlCnl, cmdValList, - ctrlCnl, objNum, kpNum, kpNameToInsert); + ctrlCnl, objNum, kpNum, kpNameToInsert, writer); tblCtrlCnl.Rows.Add(newCtrlCnlRow); ctrlCnlNum++; } diff --git a/ScadaAdmin/ScadaAdmin/AppCode/ImportExport.cs b/ScadaAdmin/ScadaAdmin/AppCode/ImportExport.cs index 8ee857450..f652e8f04 100644 --- a/ScadaAdmin/ScadaAdmin/AppCode/ImportExport.cs +++ b/ScadaAdmin/ScadaAdmin/AppCode/ImportExport.cs @@ -25,15 +25,13 @@ using Ionic.Zip; using Scada; -using Scada.Data; +using Scada.Data.Tables; using System; using System.Collections.Generic; using System.Data; using System.Data.SqlServerCe; using System.IO; -using System.Linq; using System.Text; -using Utils; namespace ScadaAdmin { diff --git a/ScadaAdmin/ScadaAdmin/AppCode/Tables.cs b/ScadaAdmin/ScadaAdmin/AppCode/Tables.cs index a3bda9ce7..678f6b4be 100644 --- a/ScadaAdmin/ScadaAdmin/AppCode/Tables.cs +++ b/ScadaAdmin/ScadaAdmin/AppCode/Tables.cs @@ -1,5 +1,5 @@ /* - * Copyright 2015 Mikhail Shiryaev + * Copyright 2016 Mikhail Shiryaev * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,11 @@ * * Product : Rapid SCADA * Module : SCADA-Administrator - * Summary : Access configuration tables + * Summary : Data access to the configuration tables * * Author : Mikhail Shiryaev * Created : 2010 - * Modified : 2015 + * Modified : 2016 */ using Scada; @@ -35,7 +35,7 @@ namespace ScadaAdmin { /// - /// Access configuration tables + /// Data access to the configuration tables /// Доступ к данным таблиц конфигурации /// internal static class Tables @@ -432,8 +432,8 @@ private static DataGridViewColumn[] GetInterfaceTableCols() return TranslateColHeaders("ScadaAdmin.Tables.InterfaceTable", new DataGridViewColumn[] { NewTextBoxColumn("Идент.", "ItfID"), - NewTextBoxColumn("Объект интерфейса", "Name"), - NewTextBoxColumn("Описание", "Descr") + NewTextBoxColumn("Путь", "Name"), + NewTextBoxColumn("Заголовок", "Descr") }); } diff --git a/ScadaAdmin/ScadaAdmin/FrmAbout.cs b/ScadaAdmin/ScadaAdmin/FrmAbout.cs index 9442de825..37366cf62 100644 --- a/ScadaAdmin/ScadaAdmin/FrmAbout.cs +++ b/ScadaAdmin/ScadaAdmin/FrmAbout.cs @@ -37,7 +37,7 @@ namespace ScadaAdmin /// public partial class FrmAbout : Form { - private const string Version = "4.5.0.2"; // версия приложения + private const string Version = "5.0.0.0"; // версия приложения private static FrmAbout frmAbout = null; // экземпляр формы о программе private bool inited; // форма инициализирована diff --git a/ScadaAdmin/ScadaAdmin/FrmImport.cs b/ScadaAdmin/ScadaAdmin/FrmImport.cs index 66fa2fb41..5369211c0 100644 --- a/ScadaAdmin/ScadaAdmin/FrmImport.cs +++ b/ScadaAdmin/ScadaAdmin/FrmImport.cs @@ -165,7 +165,7 @@ private void btnImport_Click(object sender, EventArgs e) else { // импорт таблицы - int minID = gbIDs.Enabled && chkStartID.Checked ? Convert.ToInt32(numStartID.Value) : 0; + int minID = gbIDs.Enabled && chkStartID.Checked ? Convert.ToInt32(numStartID.Value) : 1; int maxID = gbIDs.Enabled && chkFinalID.Checked ? Convert.ToInt32(numFinalID.Value) : int.MaxValue; int newMinID = gbIDs.Enabled && chkNewStartID.Checked ? Convert.ToInt32(numNewStartID.Value) : 0; importOK = ImportExport.ImportTable(txtFileName.Text, tableInfo, minID, maxID, newMinID, diff --git a/ScadaAdmin/ScadaAdmin/Lang/ScadaAdmin.en-GB.xml b/ScadaAdmin/ScadaAdmin/Lang/ScadaAdmin.en-GB.xml index 6398b50ab..69e9b5a3e 100644 --- a/ScadaAdmin/ScadaAdmin/Lang/ScadaAdmin.en-GB.xml +++ b/ScadaAdmin/ScadaAdmin/Lang/ScadaAdmin.en-GB.xml @@ -120,6 +120,8 @@ Command values "{0}" not found. It is imposible to create channels. Create channels. + Input channel {0} name was trancated. + Output channel {0} name was trancated. Input channel {0} format not found. Format description: numeric, number of decimal digits is {1}. Input channel {0} format not found. Format description: textual. Count of added input channels: {0}. @@ -318,7 +320,7 @@ OK Cancel Choose the configuration database file in SDF format - Configuration Databases (*.sdf)|*.dat|All Files (*.*)|*.* + Configuration Databases (*.sdf)|*.sdf|All Files (*.*)|*.* Choose the configuration database backup directory Choose SCADA-Communicator directory The configuration database file in SDF format does not exist. @@ -440,8 +442,8 @@ ID - Interface object - Description + Path + Title Number diff --git a/ScadaAdmin/ScadaAdmin/Properties/AssemblyInfo.cs b/ScadaAdmin/ScadaAdmin/Properties/AssemblyInfo.cs index 8553a2b53..c30c9552a 100644 --- a/ScadaAdmin/ScadaAdmin/Properties/AssemblyInfo.cs +++ b/ScadaAdmin/ScadaAdmin/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Rapid SCADA")] -[assembly: AssemblyCopyright("Copyright © 2010-2014")] +[assembly: AssemblyCopyright("Copyright © 2010-2016")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("4.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] +[assembly: AssemblyVersion("5.0.0.0")] +[assembly: AssemblyFileVersion("5.0.0.0")] diff --git a/ScadaComm/OpenKPs/AddressBook/Properties/AssemblyInfo.cs b/ScadaComm/OpenKPs/AddressBook/Properties/AssemblyInfo.cs index 5b66cdf60..bfcc69171 100644 --- a/ScadaComm/OpenKPs/AddressBook/Properties/AssemblyInfo.cs +++ b/ScadaComm/OpenKPs/AddressBook/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("AddressBook")] -[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyCopyright("Copyright © 2016")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/ScadaComm/OpenKPs/KpEmail/KpEmailLogic.cs b/ScadaComm/OpenKPs/KpEmail/KpEmailLogic.cs index 8599856a9..91579a302 100644 --- a/ScadaComm/OpenKPs/KpEmail/KpEmailLogic.cs +++ b/ScadaComm/OpenKPs/KpEmail/KpEmailLogic.cs @@ -27,7 +27,8 @@ */ using Scada.Comm.Devices.KpEmail; -using Scada.Data; +using Scada.Data.Models; +using Scada.Data.Tables; using System; using System.Collections.Generic; using System.IO; diff --git a/ScadaComm/OpenKPs/KpEmail/KpEmailView.cs b/ScadaComm/OpenKPs/KpEmail/KpEmailView.cs index d665ef5d5..ae62545f1 100644 --- a/ScadaComm/OpenKPs/KpEmail/KpEmailView.cs +++ b/ScadaComm/OpenKPs/KpEmail/KpEmailView.cs @@ -25,6 +25,8 @@ using Scada.Comm.Devices.AddressBook; using Scada.Comm.Devices.KpEmail; +using Scada.Data.Tables; +using System.Collections.Generic; namespace Scada.Comm.Devices { @@ -78,6 +80,32 @@ public override string KPDescr } } + /// + /// Получить прототипы каналов КП по умолчанию + /// + public override KPCnlPrototypes DefaultCnls + { + get + { + KPCnlPrototypes prototypes = new KPCnlPrototypes(); + + prototypes.InCnls.Add(new InCnlPrototype( + Localization.UseRussian ? "Отправлено писем" : "Sent emails", + BaseValues.CnlTypes.TI) + { + Signal = 1, + DecDigits = 0, + UnitName = BaseValues.UnitNames.Pcs + }); + + prototypes.CtrlCnls.Add(new CtrlCnlPrototype( + Localization.UseRussian ? "Отправка письма" : "Send email", + BaseValues.CmdTypes.Binary) { CmdNum = 1 }); + + return prototypes; + } + } + /// /// Получить параметры опроса КП по умолчанию /// diff --git a/ScadaComm/OpenKPs/KpEmail/Properties/AssemblyInfo.cs b/ScadaComm/OpenKPs/KpEmail/Properties/AssemblyInfo.cs index 0f19288cc..2821b0aa5 100644 --- a/ScadaComm/OpenKPs/KpEmail/Properties/AssemblyInfo.cs +++ b/ScadaComm/OpenKPs/KpEmail/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("KpEmail")] -[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyCopyright("Copyright © 2016")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/ScadaComm/OpenKPs/KpModbus/FrmDevTemplate.Designer.cs b/ScadaComm/OpenKPs/KpModbus/FrmDevTemplate.Designer.cs index baaace99c..eccd93616 100644 --- a/ScadaComm/OpenKPs/KpModbus/FrmDevTemplate.Designer.cs +++ b/ScadaComm/OpenKPs/KpModbus/FrmDevTemplate.Designer.cs @@ -29,8 +29,8 @@ protected override void Dispose(bool disposing) private void InitializeComponent() { this.components = new System.ComponentModel.Container(); - System.Windows.Forms.TreeNode treeNode1 = new System.Windows.Forms.TreeNode("Группы элементов"); - System.Windows.Forms.TreeNode treeNode2 = new System.Windows.Forms.TreeNode("Команды"); + System.Windows.Forms.TreeNode treeNode3 = new System.Windows.Forms.TreeNode("Группы элементов"); + System.Windows.Forms.TreeNode treeNode4 = new System.Windows.Forms.TreeNode("Команды"); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FrmDevTemplate)); this.treeView = new System.Windows.Forms.TreeView(); this.imageList = new System.Windows.Forms.ImageList(this.components); @@ -48,6 +48,7 @@ private void InitializeComponent() this.btnDelete = new System.Windows.Forms.ToolStripButton(); this.gbDevTemplate = new System.Windows.Forms.GroupBox(); this.gbElemGroup = new System.Windows.Forms.GroupBox(); + this.chkGrActive = new System.Windows.Forms.CheckBox(); this.lblGrElemCnt = new System.Windows.Forms.Label(); this.numGrElemCnt = new System.Windows.Forms.NumericUpDown(); this.txtGrName = new System.Windows.Forms.TextBox(); @@ -57,6 +58,7 @@ private void InitializeComponent() this.lblGrTableType = new System.Windows.Forms.Label(); this.cbGrTableType = new System.Windows.Forms.ComboBox(); this.gbElem = new System.Windows.Forms.GroupBox(); + this.lblByteOrderExample = new System.Windows.Forms.Label(); this.txtByteOrder = new System.Windows.Forms.TextBox(); this.lblByteOrder = new System.Windows.Forms.Label(); this.rbDouble = new System.Windows.Forms.RadioButton(); @@ -89,7 +91,6 @@ private void InitializeComponent() this.cbCmdTableType = new System.Windows.Forms.ComboBox(); this.openFileDialog = new System.Windows.Forms.OpenFileDialog(); this.saveFileDialog = new System.Windows.Forms.SaveFileDialog(); - this.lblByteOrderExample = new System.Windows.Forms.Label(); this.toolStrip.SuspendLayout(); this.gbDevTemplate.SuspendLayout(); this.gbElemGroup.SuspendLayout(); @@ -109,17 +110,17 @@ private void InitializeComponent() this.treeView.ImageList = this.imageList; this.treeView.Location = new System.Drawing.Point(13, 19); this.treeView.Name = "treeView"; - treeNode1.ImageKey = "group.png"; - treeNode1.Name = "grsNode"; - treeNode1.SelectedImageKey = "group.png"; - treeNode1.Text = "Группы элементов"; - treeNode2.ImageIndex = 2; - treeNode2.Name = "cmdsNode"; - treeNode2.SelectedImageKey = "cmds.png"; - treeNode2.Text = "Команды"; + treeNode3.ImageKey = "group.png"; + treeNode3.Name = "grsNode"; + treeNode3.SelectedImageKey = "group.png"; + treeNode3.Text = "Группы элементов"; + treeNode4.ImageIndex = 2; + treeNode4.Name = "cmdsNode"; + treeNode4.SelectedImageKey = "cmds.png"; + treeNode4.Text = "Команды"; this.treeView.Nodes.AddRange(new System.Windows.Forms.TreeNode[] { - treeNode1, - treeNode2}); + treeNode3, + treeNode4}); this.treeView.SelectedImageIndex = 0; this.treeView.ShowRootLines = false; this.treeView.Size = new System.Drawing.Size(254, 372); @@ -273,6 +274,7 @@ private void InitializeComponent() // // gbElemGroup // + this.gbElemGroup.Controls.Add(this.chkGrActive); this.gbElemGroup.Controls.Add(this.lblGrElemCnt); this.gbElemGroup.Controls.Add(this.numGrElemCnt); this.gbElemGroup.Controls.Add(this.txtGrName); @@ -284,23 +286,34 @@ private void InitializeComponent() this.gbElemGroup.Location = new System.Drawing.Point(298, 28); this.gbElemGroup.Name = "gbElemGroup"; this.gbElemGroup.Padding = new System.Windows.Forms.Padding(10, 3, 10, 10); - this.gbElemGroup.Size = new System.Drawing.Size(280, 144); + this.gbElemGroup.Size = new System.Drawing.Size(280, 167); this.gbElemGroup.TabIndex = 2; this.gbElemGroup.TabStop = false; this.gbElemGroup.Text = "Параметры группы элементов"; // + // chkGrActive + // + this.chkGrActive.AutoSize = true; + this.chkGrActive.Location = new System.Drawing.Point(13, 19); + this.chkGrActive.Name = "chkGrActive"; + this.chkGrActive.Size = new System.Drawing.Size(85, 17); + this.chkGrActive.TabIndex = 0; + this.chkGrActive.Text = "Активность"; + this.chkGrActive.UseVisualStyleBackColor = true; + this.chkGrActive.CheckedChanged += new System.EventHandler(this.chkGrActive_CheckedChanged); + // // lblGrElemCnt // this.lblGrElemCnt.AutoSize = true; - this.lblGrElemCnt.Location = new System.Drawing.Point(140, 95); + this.lblGrElemCnt.Location = new System.Drawing.Point(140, 118); this.lblGrElemCnt.Name = "lblGrElemCnt"; this.lblGrElemCnt.Size = new System.Drawing.Size(124, 13); - this.lblGrElemCnt.TabIndex = 6; + this.lblGrElemCnt.TabIndex = 7; this.lblGrElemCnt.Text = "Количество элементов"; // // numGrElemCnt // - this.numGrElemCnt.Location = new System.Drawing.Point(143, 111); + this.numGrElemCnt.Location = new System.Drawing.Point(143, 134); this.numGrElemCnt.Maximum = new decimal(new int[] { 2000, 0, @@ -313,7 +326,7 @@ private void InitializeComponent() 0}); this.numGrElemCnt.Name = "numGrElemCnt"; this.numGrElemCnt.Size = new System.Drawing.Size(124, 20); - this.numGrElemCnt.TabIndex = 7; + this.numGrElemCnt.TabIndex = 8; this.numGrElemCnt.Value = new decimal(new int[] { 1, 0, @@ -323,24 +336,24 @@ private void InitializeComponent() // // txtGrName // - this.txtGrName.Location = new System.Drawing.Point(13, 32); + this.txtGrName.Location = new System.Drawing.Point(13, 55); this.txtGrName.Name = "txtGrName"; this.txtGrName.Size = new System.Drawing.Size(254, 20); - this.txtGrName.TabIndex = 1; + this.txtGrName.TabIndex = 2; this.txtGrName.TextChanged += new System.EventHandler(this.txtGrName_TextChanged); // // lblGrName // this.lblGrName.AutoSize = true; - this.lblGrName.Location = new System.Drawing.Point(10, 16); + this.lblGrName.Location = new System.Drawing.Point(10, 39); this.lblGrName.Name = "lblGrName"; this.lblGrName.Size = new System.Drawing.Size(83, 13); - this.lblGrName.TabIndex = 0; + this.lblGrName.TabIndex = 1; this.lblGrName.Text = "Наименование"; // // numGrAddress // - this.numGrAddress.Location = new System.Drawing.Point(13, 111); + this.numGrAddress.Location = new System.Drawing.Point(13, 134); this.numGrAddress.Maximum = new decimal(new int[] { 65536, 0, @@ -353,7 +366,7 @@ private void InitializeComponent() 0}); this.numGrAddress.Name = "numGrAddress"; this.numGrAddress.Size = new System.Drawing.Size(124, 20); - this.numGrAddress.TabIndex = 5; + this.numGrAddress.TabIndex = 6; this.numGrAddress.Value = new decimal(new int[] { 1, 0, @@ -364,19 +377,19 @@ private void InitializeComponent() // lblGrAddress // this.lblGrAddress.AutoSize = true; - this.lblGrAddress.Location = new System.Drawing.Point(10, 95); + this.lblGrAddress.Location = new System.Drawing.Point(10, 118); this.lblGrAddress.Name = "lblGrAddress"; this.lblGrAddress.Size = new System.Drawing.Size(113, 13); - this.lblGrAddress.TabIndex = 4; + this.lblGrAddress.TabIndex = 5; this.lblGrAddress.Text = "Адрес нач. элемента"; // // lblGrTableType // this.lblGrTableType.AutoSize = true; - this.lblGrTableType.Location = new System.Drawing.Point(10, 55); + this.lblGrTableType.Location = new System.Drawing.Point(10, 78); this.lblGrTableType.Name = "lblGrTableType"; this.lblGrTableType.Size = new System.Drawing.Size(90, 13); - this.lblGrTableType.TabIndex = 2; + this.lblGrTableType.TabIndex = 3; this.lblGrTableType.Text = "Таблица данных"; // // cbGrTableType @@ -388,10 +401,10 @@ private void InitializeComponent() "Coils (Флаги, 0X)", "Input Registers (Входные регистры, 3X)", "Holding Registers (Регистры хранения, 4X)"}); - this.cbGrTableType.Location = new System.Drawing.Point(13, 71); + this.cbGrTableType.Location = new System.Drawing.Point(13, 94); this.cbGrTableType.Name = "cbGrTableType"; this.cbGrTableType.Size = new System.Drawing.Size(254, 21); - this.cbGrTableType.TabIndex = 3; + this.cbGrTableType.TabIndex = 4; this.cbGrTableType.SelectedIndexChanged += new System.EventHandler(this.cbGrTableType_SelectedIndexChanged); // // gbElem @@ -423,6 +436,16 @@ private void InitializeComponent() this.gbElem.TabStop = false; this.gbElem.Text = "Параметры элемента"; // + // lblByteOrderExample + // + this.lblByteOrderExample.AutoSize = true; + this.lblByteOrderExample.ForeColor = System.Drawing.SystemColors.GrayText; + this.lblByteOrderExample.Location = new System.Drawing.Point(143, 242); + this.lblByteOrderExample.Name = "lblByteOrderExample"; + this.lblByteOrderExample.Size = new System.Drawing.Size(113, 13); + this.lblByteOrderExample.TabIndex = 18; + this.lblByteOrderExample.Text = "Например, 01234567"; + // // txtByteOrder // this.txtByteOrder.Location = new System.Drawing.Point(13, 238); @@ -789,16 +812,6 @@ private void InitializeComponent() this.saveFileDialog.FilterIndex = 0; this.saveFileDialog.Title = "Сохранить файл"; // - // lblByteOrderExample - // - this.lblByteOrderExample.AutoSize = true; - this.lblByteOrderExample.ForeColor = System.Drawing.SystemColors.GrayText; - this.lblByteOrderExample.Location = new System.Drawing.Point(143, 242); - this.lblByteOrderExample.Name = "lblByteOrderExample"; - this.lblByteOrderExample.Size = new System.Drawing.Size(113, 13); - this.lblByteOrderExample.TabIndex = 18; - this.lblByteOrderExample.Text = "Например, 01234567"; - // // FrmDevTemplate // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -897,5 +910,6 @@ private void InitializeComponent() private System.Windows.Forms.Label lblByteOrder; private System.Windows.Forms.TextBox txtByteOrder; private System.Windows.Forms.Label lblByteOrderExample; + private System.Windows.Forms.CheckBox chkGrActive; } } \ No newline at end of file diff --git a/ScadaComm/OpenKPs/KpModbus/FrmDevTemplate.cs b/ScadaComm/OpenKPs/KpModbus/FrmDevTemplate.cs index 30182bcd5..d677cd6c8 100644 --- a/ScadaComm/OpenKPs/KpModbus/FrmDevTemplate.cs +++ b/ScadaComm/OpenKPs/KpModbus/FrmDevTemplate.cs @@ -374,6 +374,7 @@ private void ShowElemGroupProps(Modbus.ElemGroup elemGroup) if (elemGroup == null) { + chkGrActive.Checked = false; txtGrName.Text = ""; cbGrTableType.SelectedIndex = 0; numGrAddress.Value = 1; @@ -382,6 +383,7 @@ private void ShowElemGroupProps(Modbus.ElemGroup elemGroup) } else { + chkGrActive.Checked = elemGroup.Active; txtGrName.Text = elemGroup.Name; cbGrTableType.SelectedIndex = (int)elemGroup.TableType; numGrAddress.Value = elemGroup.Address + 1; @@ -929,6 +931,27 @@ private void treeView_AfterSelect(object sender, TreeViewEventArgs e) } + private void chkGrActive_CheckedChanged(object sender, EventArgs e) + { + // изменение наименования группы элементов + if (procChangedEv && selElemGroup != null) + { + selElemGroup.Active = chkGrActive.Checked; + Modified = true; + } + } + + private void txtGrName_TextChanged(object sender, EventArgs e) + { + // изменение наименования группы элементов + if (procChangedEv && selElemGroup != null) + { + selElemGroup.Name = txtGrName.Text; + UpdateElemGroupNode(); + Modified = true; + } + } + private void cbGrTableType_SelectedIndexChanged(object sender, EventArgs e) { // изменение типа таблицы данных группы элементов @@ -1033,17 +1056,6 @@ private void numGrElemCnt_ValueChanged(object sender, EventArgs e) } } - private void txtGrName_TextChanged(object sender, EventArgs e) - { - // изменение наименования группы элементов - if (procChangedEv && selElemGroup != null) - { - selElemGroup.Name = txtGrName.Text; - UpdateElemGroupNode(); - Modified = true; - } - } - private void txtElemName_TextChanged(object sender, EventArgs e) { // изменение наименования элемента diff --git a/ScadaComm/OpenKPs/KpModbus/FrmDevTemplate.resx b/ScadaComm/OpenKPs/KpModbus/FrmDevTemplate.resx index baf25e7ea..bb86ef3a6 100644 --- a/ScadaComm/OpenKPs/KpModbus/FrmDevTemplate.resx +++ b/ScadaComm/OpenKPs/KpModbus/FrmDevTemplate.resx @@ -125,7 +125,7 @@ AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACZTeXN0 ZW0uV2luZG93cy5Gb3Jtcy5JbWFnZUxpc3RTdHJlYW1lcgEAAAAERGF0YQcCAgAAAAkDAAAADwMAAAD8 - CgAAAk1TRnQBSQFMAgEBBAEAAZABAAGQAQABEAEAARABAAT/AQkBAAj/AUIBTQE2AQQGAAE2AQQCAAEo + CgAAAk1TRnQBSQFMAgEBBAEAAaABAAGgAQABEAEAARABAAT/AQkBAAj/AUIBTQE2AQQGAAE2AQQCAAEo AwABQAMAASADAAEBAQABCAYAAQgYAAGAAgABgAMAAoABAAGAAwABgAEAAYABAAKAAgADwAEAAcAB3AHA AQAB8AHKAaYBAAEzBQABMwEAATMBAAEzAQACMwIAAxYBAAMcAQADIgEAAykBAANVAQADTQEAA0IBAAM5 AQABgAF8Af8BAAJQAf8BAAGTAQAB1gEAAf8B7AHMAQABxgHWAe8BAAHWAucBAAGQAakBrQIAAf8BMwMA @@ -297,15 +297,15 @@ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 - YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAH2SURBVDhPrZM9aBRBFMdHo5K9JPsxm7skcPGSTQx66uHd - ztyeCl6MkMLEQg6LNCmuUGzE2t7KSlLYiKWNYEgjggF7QaIxh2BAEEEQNiDemcLZfc+ZvUlxOUOE+IMt - 5v3/+3bex5L/AnLrXOw7q1B2Z5CQwzr8b+AN0gO+cwtuzmG4eEUAp9dVTMsdYJUcQWZ50nNHh9pBYLQe - c/oC6rMY1lkkz/d2J8FqrlfdMFycETGzH+twGygNjkCZVpIkNYawcBFj7qxAYagv0StZI66Or4Q1T0jP - A8yn+5MXd4N5ckx+/W44mxMqkTQ/hcAuyGRLOukS+o6l7X9HNvEQBPRUVKJrSUnz2d+tq4WmbPJzdRNt - 2x/03emwNiHg2lkUJfoZKibV0v5gYI1FnK5vcQrbRRubRROF73zS8t6oHYCinRO+1djKD0ffTg+sAneZ - au73MyYCt2+rErW9G+Tp4SjIrLUuj2FjMrX5aqhnWk1CPTGjj97lDLHuGUzbO1FzjiuZZ83zfa23o8bP - N8eNmpb0DlDz61Rqs+H1vlx2yGhXQ6Fsz20HGXzvGfhhPLW8swM7tMtLT77OHv21ccIod6y8mr+c8f0v - J/txY8L4CHxwqsOgUdv5o2A+AebyLl1uYylmzkPZqEtwwR3Yq1nJ/+CPpPTxoBDyB/9E0dk2mVhKAAAA + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAH2SURBVDhPrZM9aBRBFMdHo5K9JPsxm7skkngfxqCnHt7u + zO2p4MUIKYwWcljYpLhCsRFreysrSWEjljaCIY0IBuwFicYcggFBBMFNI96Zwtl9LzN7k+Jyhgj6gy3m + /f/7dt7Hkv8CcutM7DvLUHFnkJD9Ovx34HXSB75zC27OYTh/SQCn11RMy11gjRxAZhWk544OdYLAaCPm + 9AU0ZjFseJE839uZBGvZfnXDcH5GxMx+rMMdwBsegwqtJknqDOHGeYy5swSlkYFEr44bcS2/FNZzQnoe + YDE9mLy4EyySQ/Lrd8PZrFCJpPkpBHZJJlvQSRfQdyxt/zOyifsgoCcij64kJV05/Lt9udSSTX6ubqJt + e4O+Ox3W8wKunkbh0c9QNamW9gYDKxdxurrBKWyWbWyVTRS+80nLu6N2AMp2VvhWc6M4Gn07ObQM3GWq + ud9PmQjcvq1K1PZekKdHoyCz0r6Yw+Zkav3VSN+0moR6YkYfvcsaYrVgMG3vRs05rmaetc4OtN9OGD/f + HDHqWtI7QM2vU6n1ZqH/5aJDJnoaChV7bjPI4PuCgR/yqcXtHdimU1568vX4wV9rx4xK18qr+csZ3/9y + fBDXjhofgQ9PdRk0ajt/lMwnwFzeo8tt9GLmPJSNugDn3KHdmpX8D/5YSh//FUK2ALq80b/U1F6dAAAA AElFTkSuQmCC diff --git a/ScadaComm/OpenKPs/KpModbus/KpModbus.csproj b/ScadaComm/OpenKPs/KpModbus/KpModbus.csproj index 2f25072cc..a605276c8 100644 --- a/ScadaComm/OpenKPs/KpModbus/KpModbus.csproj +++ b/ScadaComm/OpenKPs/KpModbus/KpModbus.csproj @@ -103,7 +103,9 @@ - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 - - - 17, 56 - - - 17, 17 - - - True - - - False - - \ No newline at end of file diff --git a/ScadaComm/ScadaCommSvc/Manager.cs b/ScadaComm/ScadaCommSvc/Manager.cs index e58e552fe..fb8d1634b 100644 --- a/ScadaComm/ScadaCommSvc/Manager.cs +++ b/ScadaComm/ScadaCommSvc/Manager.cs @@ -23,7 +23,7 @@ * Modified : 2016 */ -using Scada.Data; +using Scada.Data.Models; using System; using System.Collections.Generic; using System.Data; @@ -45,7 +45,7 @@ internal sealed class Manager /// /// Версия приложения /// - private const string AppVersion = "4.5.0.6"; + private const string AppVersion = "5.0.0.0"; /// /// Имя файла конфигурации /// diff --git a/ScadaComm/ScadaCommSvc/Properties/AssemblyInfo.cs b/ScadaComm/ScadaCommSvc/Properties/AssemblyInfo.cs index e04956f2f..7616c3ac7 100644 --- a/ScadaComm/ScadaCommSvc/Properties/AssemblyInfo.cs +++ b/ScadaComm/ScadaCommSvc/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Rapid SCADA")] -[assembly: AssemblyCopyright("Copyright © 2006-2015")] +[assembly: AssemblyCopyright("Copyright © 2006-2016")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -29,5 +29,5 @@ // Build Number // Revision // -[assembly: AssemblyVersion("4.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] +[assembly: AssemblyVersion("5.0.0.0")] +[assembly: AssemblyFileVersion("5.0.0.0")] diff --git a/ScadaComm/ScadaCommSvc/ScadaCommSvc.csproj b/ScadaComm/ScadaCommSvc/ScadaCommSvc.csproj index b1eeb9de6..d98272bf5 100644 --- a/ScadaComm/ScadaCommSvc/ScadaCommSvc.csproj +++ b/ScadaComm/ScadaCommSvc/ScadaCommSvc.csproj @@ -59,6 +59,9 @@ False ..\..\ScadaData\ScadaData\bin\Release\ScadaData.dll + + ..\..\ScadaData\ScadaData.Svc\bin\Release\ScadaData.Svc.dll + @@ -69,14 +72,11 @@ - - Component - - - InstMain.cs - + + Component + Component @@ -87,10 +87,6 @@ - - Designer - InstMain.cs - Designer SvcMain.cs diff --git a/ScadaComm/ScadaCommSvc/ServerCommEx.cs b/ScadaComm/ScadaCommSvc/ServerCommEx.cs index 4eb886d95..36550b8f6 100644 --- a/ScadaComm/ScadaCommSvc/ServerCommEx.cs +++ b/ScadaComm/ScadaCommSvc/ServerCommEx.cs @@ -25,7 +25,8 @@ using Scada.Client; using Scada.Comm.Devices; -using Scada.Data; +using Scada.Data.Models; +using Scada.Data.Tables; using System.Collections.Generic; using Utils; diff --git a/ScadaComm/ScadaCommSvc/InstMain.cs b/ScadaComm/ScadaCommSvc/SvcInstaller.cs similarity index 70% rename from ScadaComm/ScadaCommSvc/InstMain.cs rename to ScadaComm/ScadaCommSvc/SvcInstaller.cs index c1abb3e24..a1e417d5d 100644 --- a/ScadaComm/ScadaCommSvc/InstMain.cs +++ b/ScadaComm/ScadaCommSvc/SvcInstaller.cs @@ -1,5 +1,5 @@ /* - * Copyright 2014 Mikhail Shiryaev + * Copyright 2016 Mikhail Shiryaev * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,14 +19,12 @@ * Summary : Service installer * * Author : Mikhail Shiryaev - * Created : 2006 - * Modified : 2006 + * Created : 2016 + * Modified : 2016 */ -using System; -using System.Collections.Generic; +using Scada.Svc; using System.ComponentModel; -using System.Configuration.Install; namespace Scada.Comm.Svc { @@ -35,11 +33,16 @@ namespace Scada.Comm.Svc /// Инсталлятор службы /// [RunInstaller(true)] - public partial class InstMain : Installer + public class SvcInstaller : BaseSvcInstaller { - public InstMain() + /// + /// Конструктор + /// + public SvcInstaller() { - InitializeComponent(); + Init( + "ScadaCommService", + "SCADA-Communicator interacts with controllers, transmits data to SCADA-Server"); } } -} \ No newline at end of file +} diff --git a/ScadaData/ScadaData.Svc/BaseSvcInstaller.cs b/ScadaData/ScadaData.Svc/BaseSvcInstaller.cs new file mode 100644 index 000000000..497a4512b --- /dev/null +++ b/ScadaData/ScadaData.Svc/BaseSvcInstaller.cs @@ -0,0 +1,71 @@ +/* + * Copyright 2016 Mikhail Shiryaev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Product : Rapid SCADA + * Module : ScadaData.Svc + * Summary : The base class for Windows service installer + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + */ + +using System; +using System.Configuration.Install; +using System.ServiceProcess; + +namespace Scada.Svc +{ + /// + /// The base class for Windows service installer + /// Базовый класс установщика службы Windows + /// + public abstract class BaseSvcInstaller : Installer + { + /// + /// Инициализировать установщик службы + /// + protected void Init(string defSvcName, string defDescr) + { + // загрузка и проверка свойств службы + SvcProps svcProps = new SvcProps(); + + if (!svcProps.LoadFromFile()) + { + svcProps.ServiceName = defSvcName; + svcProps.Description = defDescr; + } + + if (string.IsNullOrEmpty(svcProps.ServiceName)) + throw new ScadaException(SvcProps.ServiceNameEmptyError); + + // настройка установщика + ServiceInstaller serviceInstaller = new ServiceInstaller(); + ServiceProcessInstaller serviceProcessInstaller = new ServiceProcessInstaller(); + + serviceInstaller.ServiceName = svcProps.ServiceName; + serviceInstaller.DisplayName = svcProps.ServiceName; + serviceInstaller.Description = svcProps.Description ?? ""; + serviceInstaller.StartType = ServiceStartMode.Automatic; + + serviceProcessInstaller.Account = ServiceAccount.LocalSystem; + serviceProcessInstaller.Password = null; + serviceProcessInstaller.Username = null; + + Installers.AddRange(new Installer[] {serviceInstaller, serviceProcessInstaller}); + } + } +} diff --git a/ScadaData/ScadaData.Svc/Properties/AssemblyInfo.cs b/ScadaData/ScadaData.Svc/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..c44e170f3 --- /dev/null +++ b/ScadaData/ScadaData.Svc/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ScadaData.Svc")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Rapid SCADA")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("827ccedc-f418-4261-9448-ea28f9033a4d")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ScadaData/ScadaData.Svc/ScadaData.Svc.csproj b/ScadaData/ScadaData.Svc/ScadaData.Svc.csproj new file mode 100644 index 000000000..3c771fdc6 --- /dev/null +++ b/ScadaData/ScadaData.Svc/ScadaData.Svc.csproj @@ -0,0 +1,58 @@ + + + + + Debug + AnyCPU + {827CCEDC-F418-4261-9448-EA28F9033A4D} + Library + Properties + Scada.Svc + ScadaData.Svc + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + bin\Release\ScadaData.Svc.XML + + + + ..\ScadaData\bin\Release\ScadaData.dll + + + + + + + + + + Component + + + + + + + \ No newline at end of file diff --git a/ScadaData/ScadaData.Svc/SvcProps.cs b/ScadaData/ScadaData.Svc/SvcProps.cs new file mode 100644 index 000000000..11e74d3ea --- /dev/null +++ b/ScadaData/ScadaData.Svc/SvcProps.cs @@ -0,0 +1,136 @@ +/* + * Copyright 2016 Mikhail Shiryaev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Product : Rapid SCADA + * Module : ScadaData.Svc + * Summary : Windows service properties + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + */ + +using System; +using System.IO; +using System.Reflection; +using System.Xml; + +namespace Scada.Svc +{ + /// + /// Windows service properties + /// Свойства службы Windows + /// + public class SvcProps + { + /// + /// Имя файла, содержащего свойства службы + /// + public const string SvcPropsFileName = "svc_config.xml"; + /// + /// Сообщение об ошибке, что имя службы пустое + /// + public static readonly string ServiceNameEmptyError = Localization.UseRussian ? + "Имя службы не должно быть пустым." : + "Service name must not be empty."; + + + /// + /// Конструктор + /// + public SvcProps() + : this("", "") + { + } + + /// + /// Конструктор + /// + public SvcProps(string serviceName, string description) + { + ServiceName = serviceName; + Description = description; + } + + + /// + /// Получить или установить имя службы + /// + public string ServiceName { get; set; } + + /// + /// Получить или установить описание + /// + public string Description { get; set; } + + + /// + /// Загрузить свойства службы + /// + public bool LoadFromFile(string fileName, out string errMsg) + { + ServiceName = ""; + Description = ""; + + try + { + XmlDocument xmlDoc = new XmlDocument(); + xmlDoc.Load(fileName); + + XmlNode node = xmlDoc.DocumentElement.SelectSingleNode("ServiceName"); + ServiceName = node == null ? "" : node.InnerText; + + if (string.IsNullOrEmpty(ServiceName)) + throw new Exception(ServiceNameEmptyError); + + node = xmlDoc.DocumentElement.SelectSingleNode("Description"); + Description = node == null ? "" : node.InnerText; + + errMsg = ""; + return true; + } + catch (Exception ex) + { + errMsg = (Localization.UseRussian ? + "Ошибка при загрузке свойств службы: " : + "Error loading service properties: ") + ex.Message; + return false; + } + } + + /// + /// Загрузить свойства службы + /// + public bool LoadFromFile() + { + string path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + string fileName = path + Path.DirectorySeparatorChar + SvcPropsFileName; + + if (File.Exists(fileName)) + { + string errMsg; + if (LoadFromFile(fileName, out errMsg)) + return true; + else + throw new ScadaException(errMsg); + } + else + { + return false; + } + } + } +} diff --git a/ScadaData/ScadaData.sln b/ScadaData/ScadaData.sln index af164b750..0c618f178 100644 --- a/ScadaData/ScadaData.sln +++ b/ScadaData/ScadaData.sln @@ -1,11 +1,13 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2013 -VisualStudioVersion = 12.0.31101.0 +# Visual Studio 14 +VisualStudioVersion = 14.0.24720.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScadaData", "ScadaData\ScadaData.csproj", "{C5DC293F-13CA-435F-A7DB-4BA91639C292}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScadaDataTest", "ScadaDataTest\ScadaDataTest.csproj", "{31415B61-53C3-4746-B586-CFD7FC16CED2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScadaData.Svc", "ScadaData.Svc\ScadaData.Svc.csproj", "{827CCEDC-F418-4261-9448-EA28F9033A4D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -20,6 +22,10 @@ Global {31415B61-53C3-4746-B586-CFD7FC16CED2}.Debug|Any CPU.Build.0 = Debug|Any CPU {31415B61-53C3-4746-B586-CFD7FC16CED2}.Release|Any CPU.ActiveCfg = Release|Any CPU {31415B61-53C3-4746-B586-CFD7FC16CED2}.Release|Any CPU.Build.0 = Release|Any CPU + {827CCEDC-F418-4261-9448-EA28F9033A4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {827CCEDC-F418-4261-9448-EA28F9033A4D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {827CCEDC-F418-4261-9448-EA28F9033A4D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {827CCEDC-F418-4261-9448-EA28F9033A4D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ScadaData/ScadaData/Cache.cs b/ScadaData/ScadaData/Cache.cs index 3e112d34a..d2d5cb1eb 100644 --- a/ScadaData/ScadaData/Cache.cs +++ b/ScadaData/ScadaData/Cache.cs @@ -50,14 +50,19 @@ protected CacheItem() /// /// Конструктор /// - protected internal CacheItem(TValue value, DateTime valueAge, DateTime nowDT) + protected internal CacheItem(TKey key, TValue value, DateTime valueAge, DateTime nowDT) { + Key = key; Value = value; ValueAge = valueAge; ValueRefrDT = nowDT; AccessDT = nowDT; } + /// + /// Получить или установить ключ + /// + public TKey Key { get; set; } /// /// Получить или установить кешированное значение /// @@ -126,31 +131,32 @@ public Cache(TimeSpan storePeriod, int capacity) /// /// Добавить значение в кеш /// - public void AddValue(TKey key, TValue value) + public CacheItem AddValue(TKey key, TValue value) { - AddValue(key, value, DateTime.MinValue, DateTime.Now); + return AddValue(key, value, DateTime.MinValue, DateTime.Now); } /// /// Добавить значение в кеш /// - public void AddValue(TKey key, TValue value, DateTime valueAge) + public CacheItem AddValue(TKey key, TValue value, DateTime valueAge) { - AddValue(key, value, valueAge, DateTime.Now); + return AddValue(key, value, valueAge, DateTime.Now); } /// /// Добавить значение в кеш /// - public void AddValue(TKey key, TValue value, DateTime valueAge, DateTime nowDT) + public CacheItem AddValue(TKey key, TValue value, DateTime valueAge, DateTime nowDT) { if (key == null) throw new ArgumentNullException("key"); lock (this) { - CacheItem cacheItem = new CacheItem(value, valueAge, nowDT); + CacheItem cacheItem = new CacheItem(key, value, valueAge, nowDT); items.Add(key, cacheItem); + return cacheItem; } } @@ -173,16 +179,46 @@ public CacheItem GetItem(TKey key, DateTime nowDT) lock (this) { + // получение запрошенного элемента CacheItem item; if (items.TryGetValue(key, out item)) - { item.AccessDT = nowDT; - return item; - } - else - { - return null; - } + + // автоматическая очистка устаревших элементов + if (nowDT - LastRemoveDT > StorePeriod) + RemoveOutdatedItems(nowDT); + + return item; + } + } + + /// + /// Получить элемент по ключу, обновив время доступа, + /// или создать новый пустой элемент, если ключ не содержится в кеше + /// + public CacheItem GetOrCreateItem(TKey key, DateTime nowDT) + { + lock (this) + { + CacheItem cacheItem = GetItem(key, nowDT); + if (cacheItem == null) + cacheItem = AddValue(key, default(TValue), DateTime.MinValue, nowDT); + return cacheItem; + } + } + + /// + /// Получить все элементы для просмотра без обновления времени доступа + /// + public CacheItem[] GetAllItemsForWatching() + { + lock (this) + { + CacheItem[] itemsCopy = new CacheItem[items.Count]; + int i = 0; + foreach (CacheItem item in items.Values) + itemsCopy[i++] = item; + return itemsCopy; } } diff --git a/ScadaData/ScadaData/Client/BaseView.cs b/ScadaData/ScadaData/Client/BaseView.cs index 8ccbcb0ac..c16c45d9b 100644 --- a/ScadaData/ScadaData/Client/BaseView.cs +++ b/ScadaData/ScadaData/Client/BaseView.cs @@ -23,7 +23,8 @@ * Modified : 2016 */ -using Scada.Data; +using Scada.Data.Models; +using System; using System.Collections.Generic; using System.IO; @@ -33,17 +34,26 @@ namespace Scada.Client /// The base class for view /// Базовый класс представления /// - public abstract class BaseView + /// + /// Derived views must provide thread safe read access in case that the object is not being changed. + /// Write operations must be synchronized + /// Дочерние представления должны обеспечивать потокобезопасный доступ на чтение при условии, + /// что объект не изменяется. Операции записи должны синхронизироваться + public abstract class BaseView : ISupportLoading { /// /// Конструктор /// public BaseView() { - ItfObjName = ""; + Title = ""; + Path = ""; + CnlSet = new HashSet(); CnlList = new List(); + CtrlCnlSet = new HashSet(); CtrlCnlList = new List(); StoredOnServer = true; + BaseAge = DateTime.MinValue; Stamp = 0; } @@ -54,9 +64,27 @@ public BaseView() public string Title { get; set; } /// - /// Получить или установить наименование объекта интерфейса + /// Получить или установить путь файла представления + /// + /// Если файл представления хранится на сервере, + /// то путь указывается относительно директории интерфейса + public string Path { get; set; } + + /// + /// Получить имя файла представления /// - public string ItfObjName { get; set; } + public string FileName + { + get + { + return System.IO.Path.GetFileName(Path); + } + } + + /// + /// Получить множество номеров входных каналов, которые используются в представлении + /// + public HashSet CnlSet { get; protected set; } /// /// Получить упорядоченный без повторений список номеров входных каналов, @@ -64,6 +92,11 @@ public BaseView() /// public List CnlList { get; protected set; } + /// + /// Получить множество номеров каналов управления, которые используются в представлении + /// + public HashSet CtrlCnlSet { get; protected set; } + /// /// Получить упорядоченный без повторений список номеров каналов управления, /// которые используются в представлении @@ -75,19 +108,36 @@ public BaseView() /// public bool StoredOnServer { get; protected set; } + /// + /// Получить или установить время последнего изменения базы конфигурации, + /// для которого выполнена привязка каналов + /// + public DateTime BaseAge { get; set; } + /// /// Получить или установить уникальную метку объекта в пределах некоторого набора данных /// /// Используется для контроля целостности данных при получении представления из кеша public long Stamp { get; set; } + /// + /// Получить объект для синхронизации доступа к представлению + /// + public object SyncRoot + { + get + { + return this; + } + } + /// - /// Добавить номер входного канала в список, сохраняя упорядоченность и уникальность его элементов + /// Добавить номер входного канала в множество и в список /// protected void AddCnlNum(int cnlNum) { - if (cnlNum > 0) + if (cnlNum > 0 && CnlSet.Add(cnlNum)) { int index = CnlList.BinarySearch(cnlNum); if (index < 0) @@ -96,11 +146,11 @@ protected void AddCnlNum(int cnlNum) } /// - /// Добавить номер канала управления в список, сохраняя упорядоченность и уникальность его элементов + /// Добавить номер канала управления в множество и в список /// protected void AddCtrlCnlNum(int ctrlCnlNum) { - if (ctrlCnlNum > 0) + if (ctrlCnlNum > 0 && CtrlCnlSet.Add(ctrlCnlNum)) { int index = CtrlCnlList.BinarySearch(ctrlCnlNum); if (index < 0) @@ -123,12 +173,19 @@ public virtual void BindCnlProps(InCnlProps[] cnlPropsArr) { } + /// + /// Привязать свойства каналов управления к элементам представления + /// + public virtual void BindCtrlCnlProps(CtrlCnlProps[] ctrlCnlPropsArr) + { + } + /// /// Определить, что входной канал используется в представлении /// public virtual bool ContainsCnl(int cnlNum) { - return CnlList.BinarySearch(cnlNum) >= 0; + return CnlSet.Contains(cnlNum); } /// @@ -136,11 +193,8 @@ public virtual bool ContainsCnl(int cnlNum) /// public virtual bool ContainsAllCnls(IEnumerable cnlNums) { - foreach (int cnlNum in cnlNums) - if (!ContainsCnl(cnlNum)) - return false; - - return true; + // в случае пустых CnlSet и cnlNums возвращает false + return CnlSet.IsProperSupersetOf(cnlNums); } /// @@ -148,7 +202,7 @@ public virtual bool ContainsAllCnls(IEnumerable cnlNums) /// public virtual bool ContainsCtrlCnl(int ctrlCnlNum) { - return CtrlCnlList.BinarySearch(ctrlCnlNum) >= 0; + return CtrlCnlSet.Contains(ctrlCnlNum); } /// @@ -157,9 +211,11 @@ public virtual bool ContainsCtrlCnl(int ctrlCnlNum) public virtual void Clear() { Title = ""; - ItfObjName = ""; + Path = ""; CnlList.Clear(); CtrlCnlList.Clear(); + CnlSet.Clear(); + CtrlCnlSet.Clear(); } } } diff --git a/ScadaData/ScadaData/Client/CommSettings.cs b/ScadaData/ScadaData/Client/CommSettings.cs index d1a43302e..738c38d88 100644 --- a/ScadaData/ScadaData/Client/CommSettings.cs +++ b/ScadaData/ScadaData/Client/CommSettings.cs @@ -90,19 +90,6 @@ public CommSettings(string serverHost, int serverPort, string serverUser, string public int ServerTimeout { get; set; } - /// - /// Установить значения настроек по умолчанию - /// - private void SetToDefault() - { - ServerHost = "localhost"; - ServerPort = 10000; - ServerUser = ""; - ServerPwd = "12345"; - ServerTimeout = 10000; - } - - /// /// Создать новый объект настроек /// @@ -124,14 +111,27 @@ public bool Equals(ISettings settings) /// public bool Equals(CommSettings commSettings) { - return commSettings == null ? false : + return commSettings == null ? false : + commSettings == this ? true : ServerHost == commSettings.ServerHost && ServerPort == commSettings.ServerPort && ServerUser == commSettings.ServerUser && ServerPwd == commSettings.ServerPwd && ServerTimeout == commSettings.ServerTimeout; } /// - /// Создать копию настроек соединения со SCADA-Сервером + /// Установить значения настроек по умолчанию + /// + public void SetToDefault() + { + ServerHost = "localhost"; + ServerPort = 10000; + ServerUser = ""; + ServerPwd = "12345"; + ServerTimeout = 10000; + } + + /// + /// Создать копию настроек /// public CommSettings Clone() { @@ -139,7 +139,43 @@ public CommSettings Clone() } /// - /// Загрузить настройки соединения со SCADA-Сервером из файла + /// Загрузить настройки из заданного XML-узла + /// + public void LoadFromXml(XmlNode commSettingsNode) + { + if (commSettingsNode == null) + throw new ArgumentNullException("commSettingsNode"); + + XmlNodeList xmlNodeList = commSettingsNode.SelectNodes("Param"); + + foreach (XmlElement xmlElement in xmlNodeList) + { + string name = xmlElement.GetAttribute("name").Trim(); + string nameL = name.ToLowerInvariant(); + string val = xmlElement.GetAttribute("value"); + + try + { + if (nameL == "serverhost") + ServerHost = val; + else if (nameL == "serverport") + ServerPort = int.Parse(val); + else if (nameL == "serveruser") + ServerUser = val; + else if (nameL == "serverpwd") + ServerPwd = val; + else if (nameL == "servertimeout") + ServerTimeout = int.Parse(val); + } + catch + { + throw new ScadaException(string.Format(CommonPhrases.IncorrectXmlParamVal, name)); + } + } + } + + /// + /// Загрузить настройки из файла /// public bool LoadFromFile(string fileName, out string errMsg) { @@ -152,34 +188,9 @@ public bool LoadFromFile(string fileName, out string errMsg) if (!File.Exists(fileName)) throw new FileNotFoundException(string.Format(CommonPhrases.NamedFileNotFound, fileName)); - XmlDocument xmlDoc = new XmlDocument(); // обрабатываемый XML-документ + XmlDocument xmlDoc = new XmlDocument(); xmlDoc.Load(fileName); - - XmlNodeList xmlNodeList = xmlDoc.DocumentElement.SelectNodes("Param"); - foreach (XmlElement xmlElement in xmlNodeList) - { - string name = xmlElement.GetAttribute("name").Trim(); - string nameL = name.ToLowerInvariant(); - string val = xmlElement.GetAttribute("value"); - - try - { - if (nameL == "serverhost") - ServerHost = val; - else if (nameL == "serverport") - ServerPort = int.Parse(val); - else if (nameL == "serveruser") - ServerUser = val; - else if (nameL == "serverpwd") - ServerPwd = val; - else if (nameL == "servertimeout") - ServerTimeout = int.Parse(val); - } - catch - { - throw new ScadaException(string.Format(CommonPhrases.IncorrectXmlParamVal, name)); - } - } + LoadFromXml(xmlDoc.DocumentElement); errMsg = ""; return true; @@ -192,7 +203,7 @@ public bool LoadFromFile(string fileName, out string errMsg) } /// - /// Загрузить настройки соединения со SCADA-Сервером из файла + /// Загрузить настройки из файла /// [Obsolete] public void LoadFromFile(string fileName, Log log) @@ -208,7 +219,24 @@ public void LoadFromFile(string fileName, Log log) } /// - /// Сохранить настройки соединения со SCADA-Сервером в файле + /// Сохранить настройки в заданный XML-элемент + /// + public void SaveToXml(XmlElement commSettingsElem) + { + commSettingsElem.AppendParamElem("ServerHost", ServerHost, + "Имя компьютера или IP-адрес SCADA-Сервера", "SCADA-Server host or IP address"); + commSettingsElem.AppendParamElem("ServerPort", ServerPort, + "Номер TCP-порта SCADA-Сервера", "SCADA-Server TCP port number"); + commSettingsElem.AppendParamElem("ServerUser", ServerUser, + "Имя пользователя для подключения", "User name for the connection"); + commSettingsElem.AppendParamElem("ServerPwd", ServerPwd, + "Пароль пользователя для подключения", "User password for the connection"); + commSettingsElem.AppendParamElem("ServerTimeout", ServerTimeout, + "Таймаут ожидания ответа, мс", "Response timeout, ms"); + } + + /// + /// Сохранить настройки в файле /// public bool SaveToFile(string fileName, out string errMsg) { @@ -221,17 +249,7 @@ public bool SaveToFile(string fileName, out string errMsg) XmlElement rootElem = xmlDoc.CreateElement("CommSettings"); xmlDoc.AppendChild(rootElem); - - rootElem.AppendParamElem("ServerHost", ServerHost, - "Имя компьютера или IP-адрес SCADA-Сервера", "SCADA-Server host or IP address"); - rootElem.AppendParamElem("ServerPort", ServerPort, - "Номер TCP-порта SCADA-Сервера", "SCADA-Server TCP port number"); - rootElem.AppendParamElem("ServerUser", ServerUser, - "Имя пользователя для подключения", "User name for the connection"); - rootElem.AppendParamElem("ServerPwd", ServerPwd, - "Пароль пользователя для подключения", "User password for the connection"); - rootElem.AppendParamElem("ServerTimeout", ServerTimeout, - "Таймаут ожидания ответа, мс", "Response timeout, ms"); + SaveToXml(rootElem); xmlDoc.Save(fileName); errMsg = ""; diff --git a/ScadaData/ScadaData/Client/DataAccess.cs b/ScadaData/ScadaData/Client/DataAccess.cs index 3d6c769e8..0e8d3d7f3 100644 --- a/ScadaData/ScadaData/Client/DataAccess.cs +++ b/ScadaData/ScadaData/Client/DataAccess.cs @@ -16,15 +16,17 @@ * * Product : Rapid SCADA * Module : ScadaData - * Summary : Thread safe access to the client cache data + * Summary : Handy and thread safe access to the client cache data * * Author : Mikhail Shiryaev * Created : 2016 * Modified : 2016 */ -using Scada.Data; +using Scada.Data.Models; +using Scada.Data.Tables; using System; +using System.Collections.Generic; using System.Data; using System.IO; using Utils; @@ -32,11 +34,9 @@ namespace Scada.Client { /// - /// Thread safe access to the client cache data - /// Потокобезопасный доступ к данным кеша клиентов + /// Handy and thread safe access to the client cache data + /// Удобный и потокобезопасный доступ к данным кеша клиентов /// - /// The class replaces Scada.Web.MainData - /// Класс заменяет Scada.Web.MainData public class DataAccess { /// @@ -48,23 +48,6 @@ public class DataAccess /// protected readonly Log log; - /// - /// Объект для синхронизации доступа к таблицам базы конфигурации - /// - protected readonly object baseLock; - /// - /// Объект для синхронизации достапа к свойствам входных каналов - /// - protected readonly object cnlPropsLock; - /// - /// Объект для синхронизации достапа к свойствам каналов управления - /// - protected readonly object ctrlCnlPropsLock; - /// - /// Объект для синхронизации достапа к текущим даным - /// - protected readonly object curDataLock; - /// /// Конструктор, ограничивающий создание объекта без параметров @@ -85,11 +68,6 @@ public DataAccess(DataCache dataCache, Log log) this.dataCache = dataCache; this.log = log; - - baseLock = new object(); - cnlPropsLock = new object(); - ctrlCnlPropsLock = new object(); - curDataLock = new object(); } @@ -110,28 +88,27 @@ public DataCache DataCache /// protected string GetRoleNameFromBase(int roleID, string defaultRoleName) { - lock (baseLock) + try { - try - { - dataCache.RefreshBaseTables(); - - DataTable tblRole = dataCache.BaseTables.RightTable; - BaseTables.CheckIsNotEmpty(tblRole, true); - tblRole.DefaultView.RowFilter = "RoleID = " + roleID; + dataCache.RefreshBaseTables(); + BaseTables baseTables = dataCache.BaseTables; - return tblRole.DefaultView.Count > 0 ? - (string)tblRole.DefaultView[0]["Name"] : - defaultRoleName; - } - catch (Exception ex) + lock (baseTables.SyncRoot) { - log.WriteException(ex, Localization.UseRussian ? - "Ошибка при получении наименования роли по идентификатору {0}" : - "Error getting role name by ID {0}", roleID); - return defaultRoleName; + BaseTables.CheckColumnsExist(baseTables.RoleTable, true); + DataView viewRole = baseTables.RoleTable.DefaultView; + viewRole.Sort = "RoleID"; + int rowInd = viewRole.Find(roleID); + return rowInd >= 0 ? (string)viewRole[rowInd]["Name"] : defaultRoleName; } } + catch (Exception ex) + { + log.WriteException(ex, Localization.UseRussian ? + "Ошибка при получении наименования роли по идентификатору {0}" : + "Error getting role name by ID {0}", roleID); + return defaultRoleName; + } } @@ -142,21 +119,21 @@ public InCnlProps GetCnlProps(int cnlNum) { try { - lock (baseLock) + if (cnlNum > 0) { dataCache.RefreshBaseTables(); - } - lock (cnlPropsLock) - { - // сохранение ссылки на свойства каналов, - // т.к. свойство CnlProps может быть изменено из другого потока + // необходимо сохранить ссылку, т.к. объект может быть пересоздан другим потоком InCnlProps[] cnlProps = dataCache.CnlProps; // поиск свойств заданного канала int ind = Array.BinarySearch(cnlProps, cnlNum, InCnlProps.IntComp); return ind >= 0 ? cnlProps[ind] : null; } + else + { + return null; + } } catch (Exception ex) { @@ -174,21 +151,14 @@ public CtrlCnlProps GetCtrlCnlProps(int ctrlCnlNum) { try { - lock (baseLock) - { - dataCache.RefreshBaseTables(); - } + dataCache.RefreshBaseTables(); - lock (ctrlCnlPropsLock) - { - // сохранение ссылки на свойства каналов, - // т.к. свойство CtrlCnlProps может быть изменено из другого потока - CtrlCnlProps[] ctrlCnlProps = dataCache.CtrlCnlProps; + // необходимо сохранить ссылку, т.к. объект может быть пересоздан другим потоком + CtrlCnlProps[] ctrlCnlProps = dataCache.CtrlCnlProps; - // поиск свойств заданного канала - int ind = Array.BinarySearch(ctrlCnlProps, ctrlCnlNum, CtrlCnlProps.IntComp); - return ind >= 0 ? ctrlCnlProps[ind] : null; - } + // поиск свойств заданного канала + int ind = Array.BinarySearch(ctrlCnlProps, ctrlCnlNum, CtrlCnlProps.IntComp); + return ind >= 0 ? ctrlCnlProps[ind] : null; } catch (Exception ex) { @@ -200,125 +170,343 @@ public CtrlCnlProps GetCtrlCnlProps(int ctrlCnlNum) } /// - /// Получить свойства представления по идентификатору + /// Получить свойства статуса входного канала по значению статуса + /// + public CnlStatProps GetCnlStatProps(int stat) + { + try + { + dataCache.RefreshBaseTables(); + CnlStatProps cnlStatProps; + return dataCache.CnlStatProps.TryGetValue(stat, out cnlStatProps) ? + cnlStatProps : null; + } + catch (Exception ex) + { + log.WriteException(ex, Localization.UseRussian ? + "Ошибка при получении цвета по статусу {0}" : + "Error getting color by status {0}", stat); + return null; + } + } + + /// + /// Привязать свойства входных каналов и каналов управления к элементам представления /// - /// Используется таблица объектов интерфейса - public ViewProps GetViewProps(int viewID) + public void BindCnlProps(BaseView view) { - lock (baseLock) + try { - try + dataCache.RefreshBaseTables(); + DateTime baseAge = dataCache.BaseTables.BaseAge; + if (view != null && view.BaseAge != baseAge && baseAge > DateTime.MinValue) { - dataCache.RefreshBaseTables(); + lock (view.SyncRoot) + { + view.BaseAge = baseAge; + view.BindCnlProps(dataCache.CnlProps); + view.BindCtrlCnlProps(dataCache.CtrlCnlProps); + } + } + } + catch (Exception ex) + { + log.WriteException(ex, Localization.UseRussian ? + "Ошибка при привязке свойств каналов к элементам представления" : + "Error binding channel properties to the view elements"); + } + } + + /// + /// Получить свойства объекта пользовательского интерфейса по идентификатору + /// + public UiObjProps GetUiObjProps(int uiObjID) + { + try + { + dataCache.RefreshBaseTables(); - DataTable tblInterface = dataCache.BaseTables.InterfaceTable; - BaseTables.CheckIsNotEmpty(tblInterface, true); - tblInterface.DefaultView.RowFilter = "ItfID = " + viewID; + // необходимо сохранить ссылку, т.к. объект может быть пересоздан другим потоком + BaseTables baseTables = dataCache.BaseTables; - if (tblInterface.DefaultView.Count > 0) + lock (baseTables.SyncRoot) + { + BaseTables.CheckColumnsExist(baseTables.InterfaceTable, true); + DataView viewInterface = baseTables.InterfaceTable.DefaultView; + viewInterface.Sort = "ItfID"; + int rowInd = viewInterface.Find(uiObjID); + + if (rowInd >= 0) { - ViewProps viewProps = new ViewProps(viewID); - viewProps.FileName = (string)tblInterface.DefaultView[0]["Name"]; - viewProps.ViewTypeCode = Path.GetExtension(viewProps.FileName); - return viewProps; + DataRowView rowView = viewInterface[rowInd]; + UiObjProps uiObjProps = UiObjProps.Parse((string)rowView["Name"]); + uiObjProps.UiObjID = uiObjID; + uiObjProps.Title = (string)rowView["Descr"]; + return uiObjProps; } else { return null; } } - catch (Exception ex) - { - log.WriteException(ex, Localization.UseRussian ? - "Ошибка при получении свойств представления по ид.={0}" : - "Error getting view properties by ID={0}", viewID); - return null; - } + } + catch (Exception ex) + { + log.WriteException(ex, Localization.UseRussian ? + "Ошибка при получении свойств объекта пользовательского интерфейса по ид.={0}" : + "Error getting user interface object properties by ID={0}", uiObjID); + return null; } } /// - /// Получить идентификатор пользователя по имени + /// Получить список свойств объектов пользовательского интерфейса /// - public int GetUserID(string username) + public List GetUiObjPropsList(UiObjProps.BaseUiTypes baseUiTypes) { - lock (baseLock) + List list = new List(); + + try { - try - { - username = username ?? ""; - dataCache.RefreshBaseTables(); + dataCache.RefreshBaseTables(); + BaseTables baseTables = dataCache.BaseTables; - DataTable tblUser = dataCache.BaseTables.UserTable; - BaseTables.CheckIsNotEmpty(tblUser, true); - tblUser.DefaultView.RowFilter = "Name = '" + username + "'"; + lock (baseTables.SyncRoot) + { + BaseTables.CheckColumnsExist(baseTables.InterfaceTable, true); + DataView viewInterface = baseTables.InterfaceTable.DefaultView; + viewInterface.Sort = "ItfID"; - return tblUser.DefaultView.Count > 0 ? - (int)tblUser.DefaultView[0]["UserID"] : - BaseValues.EmptyDataID; + foreach (DataRowView rowView in viewInterface) + { + UiObjProps uiObjProps = UiObjProps.Parse((string)rowView["Name"]); + if (baseUiTypes.HasFlag(uiObjProps.BaseUiType)) + { + uiObjProps.UiObjID = (int)rowView["ItfID"]; + uiObjProps.Title = (string)rowView["Descr"]; + list.Add(uiObjProps); + } + } } - catch (Exception ex) + } + catch (Exception ex) + { + log.WriteException(ex, Localization.UseRussian ? + "Ошибка при получении списка свойств объектов пользовательского интерфейса" : + "Error getting list of user interface object properties"); + } + + return list; + } + + /// + /// Получить права на объекты пользовательского интерфейса по идентификатору роли + /// + public Dictionary GetUiObjRights(int roleID) + { + Dictionary rightsDict = new Dictionary(); + + try + { + dataCache.RefreshBaseTables(); + BaseTables baseTables = dataCache.BaseTables; + + lock (baseTables.SyncRoot) { - log.WriteException(ex, Localization.UseRussian ? - "Ошибка при получении идентификатора пользователя по имени \"{0}\"" : - "Error getting user ID by name \"{0}\"", username); - return BaseValues.EmptyDataID; + BaseTables.CheckColumnsExist(baseTables.RightTable, true); + DataView viewRight = baseTables.RightTable.DefaultView; + viewRight.Sort = "RoleID"; + + foreach (DataRowView rowView in viewRight.FindRows(roleID)) + { + int uiObjID = (int)rowView["ItfID"]; + EntityRights rights = new EntityRights((bool)rowView["ViewRight"], (bool)rowView["CtrlRight"]); + rightsDict[uiObjID] = rights; + } } } + catch (Exception ex) + { + log.WriteException(ex, Localization.UseRussian ? + "Ошибка при получении прав на объекты пользовательского интерфейса для роли с ид.={0}" : + "Error getting access rights on user interface objects for the role with ID={0}", roleID); + } + + return rightsDict; } /// - /// Получить наименование роли по идентификатору + /// Получить имя пользователя по идентификатору /// - public string GetRoleName(int roleID) + public string GetUserName(int userID) { - string roleName = BaseValues.Roles.GetRoleName(roleID); // стандартное имя роли - return BaseValues.Roles.Custom <= roleID && roleID < BaseValues.Roles.Err ? - GetRoleNameFromBase(roleID, roleName) : - roleName; + try + { + dataCache.RefreshBaseTables(); + BaseTables baseTables = dataCache.BaseTables; + + lock (baseTables.SyncRoot) + { + BaseTables.CheckColumnsExist(baseTables.UserTable, true); + DataView viewUser = baseTables.UserTable.DefaultView; + viewUser.Sort = "UserID"; + int rowInd = viewUser.Find(userID); + return rowInd >= 0 ? (string)viewUser[rowInd]["Name"] : ""; + } + } + catch (Exception ex) + { + log.WriteException(ex, Localization.UseRussian ? + "Ошибка при получении имени пользователя по ид.={0}" : + "Error getting user name by ID={0}", userID); + return null; + } } - + /// - /// Получить цвет по статусу + /// Получить свойства пользователя по идентификатору /// - public string GetColorByStat(int stat, string defaultColor) + public UserProps GetUserProps(int userID) { - lock (baseLock) + try { - try - { - dataCache.RefreshBaseTables(); + dataCache.RefreshBaseTables(); + BaseTables baseTables = dataCache.BaseTables; - DataTable tblEvType = dataCache.BaseTables.EvTypeTable; - BaseTables.CheckIsNotEmpty(tblEvType, true); - tblEvType.DefaultView.RowFilter = "CnlStatus = " + stat; + lock (baseTables.SyncRoot) + { + BaseTables.CheckColumnsExist(baseTables.UserTable, true); + DataView viewUser = baseTables.UserTable.DefaultView; + viewUser.Sort = "UserID"; + int rowInd = viewUser.Find(userID); - if (tblEvType.DefaultView.Count > 0) + if (rowInd >= 0) { - object colorObj = tblEvType.DefaultView[0]["Color"]; - if (colorObj != DBNull.Value) - return colorObj.ToString(); + UserProps userProps = new UserProps(userID); + userProps.UserName = (string)viewUser[rowInd]["Name"]; + userProps.RoleID = (int)viewUser[rowInd]["RoleID"]; + userProps.RoleName = GetRoleName(userProps.RoleID); + return userProps; + } + else + { + return null; } } - catch (Exception ex) + } + catch (Exception ex) + { + log.WriteException(ex, Localization.UseRussian ? + "Ошибка при получении свойств пользователя по ид.={0}" : + "Error getting user properties by ID={0}", userID); + return null; + } + } + + /// + /// Получить идентификатор пользователя по имени + /// + public int GetUserID(string username) + { + try + { + username = username ?? ""; + dataCache.RefreshBaseTables(); + BaseTables baseTables = dataCache.BaseTables; + + lock (baseTables.SyncRoot) + { + BaseTables.CheckColumnsExist(baseTables.UserTable, true); + DataView viewUser = baseTables.UserTable.DefaultView; + viewUser.Sort = "Name"; + int rowInd = viewUser.Find(username); + return rowInd >= 0 ? (int)viewUser[rowInd]["UserID"] : BaseValues.EmptyDataID; + } + } + catch (Exception ex) + { + log.WriteException(ex, Localization.UseRussian ? + "Ошибка при получении идентификатора пользователя по имени \"{0}\"" : + "Error getting user ID by name \"{0}\"", username); + return BaseValues.EmptyDataID; + } + } + + /// + /// Получить наименование объекта по номеру + /// + public string GetObjName(int objNum) + { + try + { + dataCache.RefreshBaseTables(); + BaseTables baseTables = dataCache.BaseTables; + + lock (baseTables.SyncRoot) { - log.WriteException(ex, Localization.UseRussian ? - "Ошибка при получении цвета по статусу {0}" : - "Error getting color by status {0}", stat); + BaseTables.CheckColumnsExist(baseTables.ObjTable, true); + DataView viewObj = baseTables.ObjTable.DefaultView; + viewObj.Sort = "ObjNum"; + int rowInd = viewObj.Find(objNum); + return rowInd >= 0 ? (string)viewObj[rowInd]["Name"] : ""; } + } + catch (Exception ex) + { + log.WriteException(ex, Localization.UseRussian ? + "Ошибка при получении наименования объекта по номеру {0}" : + "Error getting object name by number {0}", objNum); + return ""; + } + } + + /// + /// Получить наименование КП по номеру + /// + public string GetKPName(int kpNum) + { + try + { + dataCache.RefreshBaseTables(); + BaseTables baseTables = dataCache.BaseTables; - return defaultColor; + lock (baseTables.SyncRoot) + { + BaseTables.CheckColumnsExist(baseTables.ObjTable, true); + DataView viewObj = baseTables.KPTable.DefaultView; + viewObj.Sort = "KPNum"; + int rowInd = viewObj.Find(kpNum); + return rowInd >= 0 ? (string)viewObj[rowInd]["Name"] : ""; + } + } + catch (Exception ex) + { + log.WriteException(ex, Localization.UseRussian ? + "Ошибка при получении наименования КП по номеру {0}" : + "Error getting device name by number {0}", kpNum); + return ""; } } + /// + /// Получить наименование роли по идентификатору + /// + public string GetRoleName(int roleID) + { + string roleName = BaseValues.Roles.GetRoleName(roleID); // стандартное имя роли + return BaseValues.Roles.Custom <= roleID && roleID < BaseValues.Roles.Err ? + GetRoleNameFromBase(roleID, roleName) : + roleName; + } + /// /// Получить текущие данные входного канала /// public SrezTableLight.CnlData GetCurCnlData(int cnlNum) { - DateTime dateTime; - return GetCurCnlData(cnlNum, out dateTime); + DateTime dataAge; + return GetCurCnlData(cnlNum, out dataAge); } /// @@ -326,25 +514,66 @@ public SrezTableLight.CnlData GetCurCnlData(int cnlNum) /// public SrezTableLight.CnlData GetCurCnlData(int cnlNum, out DateTime dataAge) { - lock (curDataLock) + try + { + SrezTableLight.Srez snapshot = dataCache.GetCurSnapshot(out dataAge); + SrezTableLight.CnlData cnlData; + return snapshot != null && snapshot.GetCnlData(cnlNum, out cnlData) ? + cnlData : SrezTableLight.CnlData.Empty; + } + catch (Exception ex) + { + log.WriteException(ex, Localization.UseRussian ? + "Ошибка при получении текущих данных входного канала {0}" : + "Error getting current data of the input channel {0}", cnlNum); + + dataAge = DateTime.MinValue; + return SrezTableLight.CnlData.Empty; + } + } + + /// + /// Получить отображаемое событие на основе данных события + /// + /// Метод всегда возвращает объект, не равный null + public DispEvent GetDispEvent(EventTableLight.Event ev, DataFormatter dataFormatter) + { + DispEvent dispEvent = new DispEvent(); + + try { - try + dispEvent.Num = ev.Number; + dispEvent.Time = ev.DateTime.ToLocalizedString(); + dispEvent.Ack = ev.Checked ? CommonPhrases.EventAck : CommonPhrases.EventNotAck; + + InCnlProps cnlProps = GetCnlProps(ev.CnlNum); + CnlStatProps cnlStatProps = GetCnlStatProps(ev.NewCnlStat); + + if (cnlProps == null) { - SrezTableLight.Srez snapshot = dataCache.GetCurSnapshot(out dataAge); - SrezTableLight.CnlData cnlData; - return snapshot != null && snapshot.GetCnlData(cnlNum, out cnlData) ? - cnlData : SrezTableLight.CnlData.Empty; + dispEvent.Obj = GetObjName(ev.ObjNum); + dispEvent.KP = GetKPName(ev.KPNum); } - catch (Exception ex) + else { - log.WriteException(ex, Localization.UseRussian ? - "Ошибка при получении текущих данных входного канала {0}" : - "Error getting current data of the input channel {0}", cnlNum); - - dataAge = DateTime.MinValue; - return SrezTableLight.CnlData.Empty; + dispEvent.Obj = cnlProps.ObjName; + dispEvent.KP = cnlProps.KPName; + dispEvent.Cnl = cnlProps.CnlName; + dispEvent.Color = dataFormatter.GetCnlValColor( + ev.NewCnlVal, ev.NewCnlStat, cnlProps, cnlStatProps); + dispEvent.Sound = cnlProps.EvSound; } + + dispEvent.Text = dataFormatter.GetEventText(ev, cnlProps, cnlStatProps); + } + catch (Exception ex) + { + log.WriteException(ex, Localization.UseRussian ? + "Ошибка при получении отображаемого события на основе данных события" : + "Error getting displayed event based on the event data"); } + + return dispEvent; } } } diff --git a/ScadaData/ScadaData/Client/DataCache.cs b/ScadaData/ScadaData/Client/DataCache.cs index c08718f98..bf5dcfc05 100644 --- a/ScadaData/ScadaData/Client/DataCache.cs +++ b/ScadaData/ScadaData/Client/DataCache.cs @@ -23,8 +23,10 @@ * Modified : 2016 */ -using Scada.Data; +using Scada.Data.Models; +using Scada.Data.Tables; using System; +using System.Collections.Generic; using System.Data; using System.Threading; using Utils; @@ -40,17 +42,38 @@ namespace Scada.Client public class DataCache { /// - /// Время актуальности архивных данных в кэше + /// Вместимость кеша таблиц часовых срезов /// - protected static readonly TimeSpan BaseValidSpan = TimeSpan.FromSeconds(1); + protected const int HourCacheCapacity = 100; /// - /// Время актуальности архивных данных в кэше + /// Вместимость кеша таблиц событий + /// + protected const int EventCacheCapacity = 100; + + /// + /// Период хранения таблиц часовых срезов в кеше с момента последнего доступа + /// + protected static readonly TimeSpan HourCacheStorePeriod = TimeSpan.FromMinutes(10); + /// + /// Период хранения таблиц событий в кеше с момента последнего доступа + /// + protected static readonly TimeSpan EventCacheStorePeriod = TimeSpan.FromMinutes(10); + /// + /// Время актуальности таблиц базы конфигурации + /// + protected static readonly TimeSpan BaseValidSpan = TimeSpan.FromSeconds(5); + /// + /// Время актуальности текущих и архивных данных /// protected static readonly TimeSpan DataValidSpan = TimeSpan.FromMilliseconds(500); /// /// Время ожидания снятия блокировки базы конфигурации /// protected static readonly TimeSpan WaitBaseLock = TimeSpan.FromSeconds(5); + /// + /// Разделитель значений внутри поля таблицы + /// + protected static readonly char[] FieldSeparator = new char[] { ';' }; /// @@ -67,7 +90,7 @@ public class DataCache /// protected readonly object baseLock; /// - /// Объект для синхронизации достапа к текущим даным + /// Объект для синхронизации достапа к текущим данным /// protected readonly object curDataLock; @@ -76,17 +99,13 @@ public class DataCache /// protected DateTime baseRefrDT; /// - /// Время последнего изменения успешно считанной базы конфигурации - /// - protected DateTime baseAge; - /// /// Таблица текущего среза /// protected SrezTableLight tblCur; /// - /// Время последего успешного обновления таблицы текущего среза + /// Время последнего успешного обновления таблицы текущего среза /// - protected DateTime curRefrDT; + protected DateTime curDataRefrDT; /// @@ -113,35 +132,67 @@ public DataCache(ServerComm serverComm, Log log) curDataLock = new object(); baseRefrDT = DateTime.MinValue; - baseAge = DateTime.MinValue; tblCur = new SrezTableLight(); - curRefrDT = DateTime.MinValue; + curDataRefrDT = DateTime.MinValue; BaseTables = new BaseTables(); CnlProps = new InCnlProps[0]; CtrlCnlProps = new CtrlCnlProps[0]; + CnlStatProps = new SortedList(); + HourTableCache = new Cache(HourCacheStorePeriod, HourCacheCapacity); + EventTableCache = new Cache(EventCacheStorePeriod, EventCacheCapacity); } /// /// Получить таблицы базы конфигурации /// - /// Таблицы после загрузки не изменяются экземпляром данного класса. - /// При обновлении таблиц объект таблиц пересоздаётся, обеспечивая целостность + /// При обновлении объект таблиц пересоздаётся, обеспечивая целостность. + /// Таблицы после загрузки не изменяются экземпляром данного класса и не должны изменяться извне, + /// таким образом, чтение данных из таблиц является потокобезопасным. + /// Однако, при использовании DataTable.DefaultView небходимо синхронизировать доступ к таблицам + /// с помощью вызова lock (BaseTables.SyncRoot) public BaseTables BaseTables { get; protected set; } /// /// Получить свойства входных каналов /// - /// Свойства каналов автоматически создаются после обновления таблиц базы конфигурации + /// Массив пересоздаётся после обновления таблиц базы конфигурации. + /// Массив после инициализации не изменяется экземпляром данного класса и не должен изменяться извне, + /// таким образом, чтение его данных является потокобезопасным + /// public InCnlProps[] CnlProps { get; protected set; } /// /// Получить свойства входных каналов /// - /// Свойства каналов автоматически создаются после обновления таблиц базы конфигурации + /// Массив пересоздаётся после обновления таблиц базы конфигурации. + /// Массив после инициализации не изменяется экземпляром данного класса и не должен изменяться извне, + /// таким образом, чтение его данных является потокобезопасным + /// public CtrlCnlProps[] CtrlCnlProps { get; protected set; } + /// + /// Получить свойства статусов входных каналов + /// + /// Список пересоздаётся после обновления таблиц базы конфигурации. + /// Список после инициализации не изменяется экземпляром данного класса и не должен изменяться извне, + /// таким образом, чтение его данных является потокобезопасным + /// + public SortedList CnlStatProps { get; protected set; } + + /// + /// Получить кеш таблиц часовых срезов + /// + /// Использовать вне данного класса только для получения состояния кеша + public Cache HourTableCache { get; protected set; } + + /// + /// Получить кеш таблиц событий + /// + /// Использовать вне данного класса только для получения состояния кеша + public Cache EventTableCache { get; protected set; } + /// /// Заполнить свойства входных каналов @@ -155,74 +206,91 @@ protected void FillCnlProps() "Fill input channels properties"); DataTable tblInCnl = BaseTables.InCnlTable; + DataView viewObj = BaseTables.ObjTable.DefaultView; + DataView viewKP = BaseTables.KPTable.DefaultView; + DataView viewParam = BaseTables.ParamTable.DefaultView; + DataView viewFormat = BaseTables.FormatTable.DefaultView; + DataView viewUnit = BaseTables.UnitTable.DefaultView; + + // установка сортировки для последующего поиска строк + viewObj.Sort = "ObjNum"; + viewKP.Sort = "KPNum"; + viewParam.Sort = "ParamID"; + viewFormat.Sort = "FormatID"; + viewUnit.Sort = "UnitID"; + int inCnlCnt = tblInCnl.Rows.Count; // количество входных каналов InCnlProps[] newCnlProps = new InCnlProps[inCnlCnt]; for (int i = 0; i < inCnlCnt; i++) { - DataRowView rowView = tblInCnl.DefaultView[i]; + DataRow inCnlRow = tblInCnl.Rows[i]; InCnlProps cnlProps = new InCnlProps(); - cnlProps.CnlNum = (int)rowView["CnlNum"]; // определение свойств, не использующих внешних ключей - cnlProps.CnlName = (string)rowView["Name"]; - cnlProps.CtrlCnlNum = (int)rowView["CtrlCnlNum"]; - cnlProps.EvSound = (bool)rowView["EvSound"]; - - // определение номера и наименования объекта - cnlProps.ObjNum = (int)rowView["ObjNum"]; - DataTable tblObj = BaseTables.ObjTable; - tblObj.DefaultView.RowFilter = "ObjNum = " + cnlProps.ObjNum; - cnlProps.ObjName = tblObj.DefaultView.Count > 0 ? (string)tblObj.DefaultView[0]["Name"] : ""; - - // определение номера и наименования КП - cnlProps.KPNum = (int)rowView["KPNum"]; - DataTable tblKP = BaseTables.KPTable; - tblKP.DefaultView.RowFilter = "KPNum = " + cnlProps.KPNum; - cnlProps.KPName = tblKP.DefaultView.Count > 0 ? (string)tblKP.DefaultView[0]["Name"] : ""; + cnlProps.CnlNum = (int)inCnlRow["CnlNum"]; + cnlProps.CnlName = (string)inCnlRow["Name"]; + cnlProps.CnlTypeID = (int)inCnlRow["CnlTypeID"]; + cnlProps.ObjNum = (int)inCnlRow["ObjNum"]; + cnlProps.KPNum = (int)inCnlRow["KPNum"]; + cnlProps.Signal = (int)inCnlRow["Signal"]; + cnlProps.FormulaUsed = (bool)inCnlRow["FormulaUsed"]; + cnlProps.Formula = (string)inCnlRow["Formula"]; + cnlProps.Averaging = (bool)inCnlRow["Averaging"]; + cnlProps.ParamID = (int)inCnlRow["ParamID"]; + cnlProps.UnitID = (int)inCnlRow["UnitID"]; + cnlProps.CtrlCnlNum = (int)inCnlRow["CtrlCnlNum"]; + cnlProps.EvEnabled = (bool)inCnlRow["EvEnabled"]; + cnlProps.EvSound = (bool)inCnlRow["EvSound"]; + cnlProps.EvOnChange = (bool)inCnlRow["EvOnChange"]; + cnlProps.EvOnUndef = (bool)inCnlRow["EvOnUndef"]; + cnlProps.LimLowCrash = (double)inCnlRow["LimLowCrash"]; + cnlProps.LimLow = (double)inCnlRow["LimLow"]; + cnlProps.LimHigh = (double)inCnlRow["LimHigh"]; + cnlProps.LimHighCrash = (double)inCnlRow["LimHighCrash"]; + + // определение наименования объекта + int objRowInd = viewObj.Find(cnlProps.ObjNum); + if (objRowInd >= 0) + cnlProps.ObjName = (string)viewObj[objRowInd]["Name"]; + + // определение наименования КП + int kpRowInd = viewKP.Find(cnlProps.KPNum); + if (kpRowInd >= 0) + cnlProps.KPName = (string)viewKP[kpRowInd]["Name"]; // определение наименования параметра и имени файла значка - DataTable tblParam = BaseTables.ParamTable; - tblParam.DefaultView.RowFilter = "ParamID = " + rowView["ParamID"]; - if (tblParam.DefaultView.Count > 0) + int paramRowInd = viewParam.Find(cnlProps.ParamID); + if (paramRowInd >= 0) { - DataRowView paramRowView = tblParam.DefaultView[0]; + DataRowView paramRowView = viewParam[paramRowInd]; cnlProps.ParamName = (string)paramRowView["Name"]; - object iconFileName = paramRowView["IconFileName"]; - cnlProps.IconFileName = iconFileName == DBNull.Value ? "" : iconFileName.ToString(); - } - else - { - cnlProps.ParamName = ""; - cnlProps.IconFileName = ""; + cnlProps.IconFileName = (string)paramRowView["IconFileName"]; } // определение формата вывода - DataTable tblFormat = BaseTables.FormatTable; - tblFormat.DefaultView.RowFilter = "FormatID = " + rowView["FormatID"]; - if (tblFormat.DefaultView.Count > 0) + int formatRowInd = viewFormat.Find(inCnlRow["FormatID"]); + if (formatRowInd >= 0) { - DataRowView formatRowView = tblFormat.DefaultView[0]; + DataRowView formatRowView = viewFormat[formatRowInd]; cnlProps.ShowNumber = (bool)formatRowView["ShowNumber"]; cnlProps.DecDigits = (int)formatRowView["DecDigits"]; } // определение размерностей - DataTable tblUnit = BaseTables.UnitTable; - tblUnit.DefaultView.RowFilter = "UnitID = " + rowView["UnitID"]; - if (tblUnit.DefaultView.Count > 0) + int unitRowInd = viewUnit.Find(cnlProps.UnitID); + if (unitRowInd >= 0) { - string sign = (string)tblUnit.DefaultView[0]["Sign"]; - cnlProps.UnitArr = sign.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); - for (int j = 0; j < cnlProps.UnitArr.Length; j++) - cnlProps.UnitArr[j] = cnlProps.UnitArr[j].Trim(); - if (cnlProps.UnitArr.Length == 1 && cnlProps.UnitArr[0] == "") + DataRowView unitRowView = viewUnit[unitRowInd]; + cnlProps.UnitName = (string)unitRowView["Name"]; + cnlProps.UnitSign = (string)unitRowView["Sign"]; + string[] unitArr = cnlProps.UnitArr = + cnlProps.UnitSign.Split(FieldSeparator, StringSplitOptions.RemoveEmptyEntries); + for (int j = 0; j < unitArr.Length; j++) + unitArr[j] = unitArr[j].Trim(); + if (unitArr.Length == 1 && unitArr[0] == "") cnlProps.UnitArr = null; } - else - { - cnlProps.UnitArr = null; - } newCnlProps[i] = cnlProps; } @@ -242,6 +310,111 @@ protected void FillCnlProps() /// protected void FillCtrlCnlProps() { + try + { + log.WriteAction(Localization.UseRussian ? + "Заполнение свойств каналов управления" : + "Fill output channels properties"); + + DataTable tblCtrlCnl = BaseTables.CtrlCnlTable; + DataView viewObj = BaseTables.ObjTable.DefaultView; + DataView viewKP = BaseTables.KPTable.DefaultView; + DataView viewCmdVal = BaseTables.CmdValTable.DefaultView; + + // установка сортировки для последующего поиска строк + viewObj.Sort = "ObjNum"; + viewKP.Sort = "KPNum"; + viewCmdVal.Sort = "CmdValID"; + + int ctrlCnlCnt = tblCtrlCnl.Rows.Count; + CtrlCnlProps[] newCtrlCnlProps = new CtrlCnlProps[ctrlCnlCnt]; + + for (int i = 0; i < ctrlCnlCnt; i++) + { + DataRow ctrlCnlRow = tblCtrlCnl.Rows[i]; + CtrlCnlProps ctrlCnlProps = new CtrlCnlProps(); + + // определение свойств, не использующих внешних ключей + ctrlCnlProps.CtrlCnlNum = (int)ctrlCnlRow["CtrlCnlNum"]; + ctrlCnlProps.CtrlCnlName = (string)ctrlCnlRow["Name"]; + ctrlCnlProps.CmdTypeID = (int)ctrlCnlRow["CmdTypeID"]; + ctrlCnlProps.ObjNum = (int)ctrlCnlRow["ObjNum"]; + ctrlCnlProps.KPNum = (int)ctrlCnlRow["KPNum"]; + ctrlCnlProps.CmdNum = (int)ctrlCnlRow["CmdNum"]; + ctrlCnlProps.CmdValID = (int)ctrlCnlRow["CmdValID"]; + ctrlCnlProps.FormulaUsed = (bool)ctrlCnlRow["FormulaUsed"]; + ctrlCnlProps.Formula = (string)ctrlCnlRow["Formula"]; + ctrlCnlProps.EvEnabled = (bool)ctrlCnlRow["EvEnabled"]; + + // определение наименования объекта + int objRowInd = viewObj.Find(ctrlCnlProps.ObjNum); + if (objRowInd >= 0) + ctrlCnlProps.ObjName = (string)viewObj[objRowInd]["Name"]; + + // определение наименования КП + int kpRowInd = viewKP.Find(ctrlCnlProps.KPNum); + if (kpRowInd >= 0) + ctrlCnlProps.KPName = (string)viewKP[kpRowInd]["Name"]; + + // определение значений команды + int cmdValInd = viewCmdVal.Find(ctrlCnlProps.CmdValID); + if (cmdValInd >= 0) + { + DataRowView cmdValRowView = viewCmdVal[cmdValInd]; + ctrlCnlProps.CmdValName = (string)cmdValRowView["Name"]; + ctrlCnlProps.CmdVal = (string)cmdValRowView["Val"]; + string[] cmdValArr = ctrlCnlProps.CmdValArr = + ctrlCnlProps.CmdVal.Split(FieldSeparator, StringSplitOptions.RemoveEmptyEntries); + for (int j = 0; j < cmdValArr.Length; j++) + cmdValArr[j] = cmdValArr[j].Trim(); + if (cmdValArr.Length == 1 && cmdValArr[0] == "") + ctrlCnlProps.CmdValArr = null; + } + + newCtrlCnlProps[i] = ctrlCnlProps; + } + + CtrlCnlProps = newCtrlCnlProps; + } + catch (Exception ex) + { + log.WriteException(ex, (Localization.UseRussian ? + "Ошибка при заполнении свойств каналов управления: " : + "Error filling output channels properties")); + } + } + + /// + /// Заполнить свойства статусов входных каналов + /// + protected void FillCnlStatProps() + { + try + { + log.WriteAction(Localization.UseRussian ? + "Заполнение свойств статусов входных каналов" : + "Fill input channel statuses properties"); + + DataTable tblEvType = BaseTables.EvTypeTable; + int statusCnt = tblEvType.Rows.Count; + SortedList newCnlStatProps = new SortedList(statusCnt); + + for (int i = 0; i < statusCnt; i++) + { + DataRow row = tblEvType.Rows[i]; + CnlStatProps cnlStatProps = new CnlStatProps((int)row["CnlStatus"]) { + Color = (string)row["Color"], Name = (string)row["Name"] }; + newCnlStatProps.Add(cnlStatProps.Status, cnlStatProps); + } + + CnlStatProps = newCnlStatProps; + } + catch (Exception ex) + { + log.WriteException(ex, (Localization.UseRussian ? + "Ошибка при заполнении свойств статусов входных каналов: " : + "Error filling input channel statuses properties")); + } } /// @@ -252,27 +425,28 @@ protected void RefreshCurData() try { DateTime utcNowDT = DateTime.UtcNow; - if (utcNowDT - curRefrDT > DataValidSpan) // данные устарели + if (utcNowDT - curDataRefrDT > DataValidSpan) // данные устарели { - curRefrDT = utcNowDT; - DateTime newCurAge = serverComm.ReceiveFileAge(ServerComm.Dirs.Cur, SrezAdapter.CurTableName); + curDataRefrDT = utcNowDT; + DateTime newCurTableAge = serverComm.ReceiveFileAge(ServerComm.Dirs.Cur, SrezAdapter.CurTableName); - if (newCurAge == DateTime.MinValue) + if (newCurTableAge == DateTime.MinValue) // файл среза не существует или нет связи с сервером { - throw new ScadaException(Localization.UseRussian ? + tblCur.Clear(); + tblCur.FileModTime = DateTime.MinValue; + log.WriteError(Localization.UseRussian ? "Не удалось принять время изменения файла текущих данных." : "Unable to receive the current data file modification time."); } - else if (tblCur.FileModTime != newCurAge) // файл среза изменён + else if (tblCur.FileModTime != newCurTableAge) // файл среза изменён { if (serverComm.ReceiveSrezTable(SrezAdapter.CurTableName, tblCur)) { - tblCur.FileModTime = newCurAge; + tblCur.FileModTime = newCurTableAge; tblCur.LastFillTime = utcNowDT; } else { - curRefrDT = DateTime.MinValue; tblCur.FileModTime = DateTime.MinValue; } } @@ -280,9 +454,7 @@ protected void RefreshCurData() } catch (Exception ex) { - curRefrDT = DateTime.MinValue; tblCur.FileModTime = DateTime.MinValue; - log.WriteException(ex, Localization.UseRussian ? "Ошибка при обновлении текущих данных" : "Error refreshing the current data"); @@ -291,7 +463,7 @@ protected void RefreshCurData() /// - /// Обновить таблицы базы конфигурации и свойства каналов + /// Обновить таблицы базы конфигурации, свойства каналов и статусов /// public void RefreshBaseTables() { @@ -307,15 +479,14 @@ public void RefreshBaseTables() DateTime newBaseAge = serverComm.ReceiveFileAge(ServerComm.Dirs.BaseDAT, BaseTables.GetFileName(BaseTables.InCnlTable)); - if (newBaseAge == DateTime.MinValue) + if (newBaseAge == DateTime.MinValue) // база конфигурации не существует или нет связи с сервером { throw new ScadaException(Localization.UseRussian ? "Не удалось принять время изменения базы конфигурации." : "Unable to receive the configuration database modification time."); } - else if (baseAge != newBaseAge) // база конфигурации изменена + else if (BaseTables.BaseAge != newBaseAge) // база конфигурации изменена { - baseAge = newBaseAge; log.WriteAction(Localization.UseRussian ? "Обновление таблиц базы конфигурации" : "Refresh the tables of the configuration database"); @@ -328,33 +499,34 @@ public void RefreshBaseTables() Thread.Sleep(ScadaUtils.ThreadDelay); } - // получение данных таблиц - foreach (DataTable dataTable in BaseTables.AllTables) + // загрузка данных в таблицы + BaseTables newBaseTables = new BaseTables() { BaseAge = newBaseAge }; + foreach (DataTable dataTable in newBaseTables.AllTables) { string tableName = BaseTables.GetFileName(dataTable); if (!serverComm.ReceiveBaseTable(tableName, dataTable)) { - log.WriteError(string.Format(Localization.UseRussian ? + throw new ScadaException(string.Format(Localization.UseRussian ? "Не удалось принять таблицу {0}" : "Unable to receive the table {0}", tableName)); - - baseRefrDT = DateTime.MinValue; - baseAge = DateTime.MinValue; } } + BaseTables = newBaseTables; - // заполнение свойств каналов - FillCnlProps(); - FillCtrlCnlProps(); + // заполнение свойств каналов и статусов + lock (BaseTables.SyncRoot) + { + FillCnlProps(); + FillCtrlCnlProps(); + FillCnlStatProps(); + } } } } catch (Exception ex) { - baseRefrDT = DateTime.MinValue; - baseAge = DateTime.MinValue; - + BaseTables.BaseAge = DateTime.MinValue; log.WriteException(ex, Localization.UseRussian ? "Ошибка при обновлении таблиц базы конфигурации" : "Error refreshing the tables of the configuration database"); @@ -365,7 +537,8 @@ public void RefreshBaseTables() /// /// Получить текущий срез из кеша или от сервера /// - /// Возвращаемый срез после загрузки не изменяется экземпляром данного класса + /// Возвращаемый срез после загрузки не изменяется экземпляром данного класса, + /// таким образом, чтение его данных является потокобезопасным public SrezTableLight.Srez GetCurSnapshot(out DateTime dataAge) { lock (curDataLock) @@ -388,33 +561,180 @@ public SrezTableLight.Srez GetCurSnapshot(out DateTime dataAge) } /// - /// Получить тренд минутных данных заданного канала за сутки + /// Получить таблицу часовых данных за сутки из кеша или от сервера /// - /// Возвращаемый тренд после загрузки не изменяется экземпляром данного класса. + /// Возвращаемая таблица после загрузки не изменяется экземпляром данного класса, + /// таким образом, чтение её данных является потокобезопасным. /// Метод всегда возвращает объект, не равный null - public Trend GetMinTrend(int cnlNum, DateTime date) + public SrezTableLight GetHourTable(DateTime date) { - return new Trend(cnlNum); + try + { + // получение таблицы часовых срезов из кеша + date = date.Date; + DateTime utcNowDT = DateTime.UtcNow; + Cache.CacheItem cacheItem = HourTableCache.GetOrCreateItem(date, utcNowDT); + + // блокировка доступа только к одной таблице часовых срезов + lock (cacheItem) + { + SrezTableLight table = cacheItem.Value; // таблица, которую необходимо получить + DateTime tableAge = cacheItem.ValueAge; // время изменения файла таблицы + bool tableIsNotValid = utcNowDT - cacheItem.ValueRefrDT > DataValidSpan; // таблица могла устареть + + // получение таблицы часовых срезов от сервера + if (table == null || tableIsNotValid) + { + string tableName = SrezAdapter.BuildHourTableName(date); + DateTime newTableAge = serverComm.ReceiveFileAge(ServerComm.Dirs.Hour, tableName); + + if (newTableAge == DateTime.MinValue) // файл таблицы не существует или нет связи с сервером + { + table = null; + // не засорять лог + /*log.WriteError(string.Format(Localization.UseRussian ? + "Не удалось принять время изменения таблицы часовых данных {0}" : + "Unable to receive modification time of the hourly data table {0}", tableName));*/ + } + else if (newTableAge != tableAge) // файл таблицы изменён + { + table = new SrezTableLight(); + if (serverComm.ReceiveSrezTable(tableName, table)) + { + table.FileModTime = newTableAge; + table.LastFillTime = utcNowDT; + } + else + { + throw new ScadaException(Localization.UseRussian ? + "Не удалось принять таблицу часовых срезов." : + "Unable to receive hourly data table."); + } + } + + if (table == null) + table = new SrezTableLight(); + + // обновление таблицы в кеше + HourTableCache.UpdateItem(cacheItem, table, newTableAge, utcNowDT); + } + + return table; + } + } + catch (Exception ex) + { + log.WriteException(ex, Localization.UseRussian ? + "Ошибка при получении таблицы часовых данных за {0} из кэша или от сервера" : + "Error getting hourly data table for {0} from the cache or from the server", + date.ToLocalizedDateString()); + return new SrezTableLight(); + } } /// - /// Получить таблицу часового среза за сутки из кеша или от сервера + /// Получить таблицу событий за сутки из кеша или от сервера /// - /// Возвращаемая таблица после загрузки не изменяется экземпляром данного класса. + /// Возвращаемая таблица после загрузки не изменяется экземпляром данного класса, + /// таким образом, чтение её данных является потокобезопасным. /// Метод всегда возвращает объект, не равный null - public SrezTableLight GetHourTable(DateTime date) + public EventTableLight GetEventTable(DateTime date) { - return new SrezTableLight(); + try + { + // получение таблицы событий из кеша + date = date.Date; + DateTime utcNowDT = DateTime.UtcNow; + Cache.CacheItem cacheItem = EventTableCache.GetOrCreateItem(date, utcNowDT); + + // блокировка доступа только к одной таблице событий + lock (cacheItem) + { + EventTableLight table = cacheItem.Value; // таблица, которую необходимо получить + DateTime tableAge = cacheItem.ValueAge; // время изменения файла таблицы + bool tableIsNotValid = utcNowDT - cacheItem.ValueRefrDT > DataValidSpan; // таблица могла устареть + + // получение таблицы событий от сервера + if (table == null || tableIsNotValid) + { + string tableName = EventAdapter.BuildEvTableName(date); + DateTime newTableAge = serverComm.ReceiveFileAge(ServerComm.Dirs.Events, tableName); + + if (newTableAge == DateTime.MinValue) // файл таблицы не существует или нет связи с сервером + { + table = null; + // не засорять лог + /*log.WriteError(string.Format(Localization.UseRussian ? + "Не удалось принять время изменения таблицы событий {0}" : + "Unable to receive modification time of the event table {0}", tableName));*/ + } + else if (newTableAge != tableAge) // файл таблицы изменён + { + table = new EventTableLight(); + if (serverComm.ReceiveEventTable(tableName, table)) + { + table.FileModTime = newTableAge; + table.LastFillTime = utcNowDT; + } + else + { + throw new ScadaException(Localization.UseRussian ? + "Не удалось принять таблицу событий." : + "Unable to receive event table."); + } + } + + if (table == null) + table = new EventTableLight(); + + // обновление таблицы в кеше + EventTableCache.UpdateItem(cacheItem, table, newTableAge, utcNowDT); + } + + return table; + } + } + catch (Exception ex) + { + log.WriteException(ex, Localization.UseRussian ? + "Ошибка при получении таблицы событий за {0} из кэша или от сервера" : + "Error getting event table for {0} from the cache or from the server", + date.ToLocalizedDateString()); + return new EventTableLight(); + } } /// - /// Получить таблицу событий за сутки из кеша или от сервера + /// Получить тренд минутных данных заданного канала за сутки /// - /// Возвращаемая таблица после загрузки не изменяется экземпляром данного класса. + /// Возвращаемый тренд после загрузки не изменяется экземпляром данного класса, + /// таким образом, чтение его данных является потокобезопасным. /// Метод всегда возвращает объект, не равный null - public EventTableLight GetEventTable(DateTime date) + public Trend GetMinTrend(DateTime date, int cnlNum) { - return new EventTableLight(); + Trend trend = new Trend(cnlNum); + + try + { + if (serverComm.ReceiveTrend(SrezAdapter.BuildMinTableName(date), date, trend)) + { + trend.LastFillTime = DateTime.UtcNow; // единообразно с часовыми данными и событиями + } + else + { + throw new ScadaException(Localization.UseRussian ? + "Не удалось принять тренд." : + "Unable to receive trend."); + } + } + catch (Exception ex) + { + log.WriteException(ex, Localization.UseRussian ? + "Ошибка при получении тренда минутных данных за {0}" : + "Error getting minute data trend for {0}", date.ToLocalizedDateString()); + } + + return trend; } } } \ No newline at end of file diff --git a/ScadaData/ScadaData/Client/DataFormatter.cs b/ScadaData/ScadaData/Client/DataFormatter.cs index ce103c4be..eb88b5020 100644 --- a/ScadaData/ScadaData/Client/DataFormatter.cs +++ b/ScadaData/ScadaData/Client/DataFormatter.cs @@ -23,11 +23,10 @@ * Modified : 2016 */ -using Scada.Data; +using Scada.Data.Models; +using Scada.Data.Tables; using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Text; namespace Scada.Client @@ -39,18 +38,14 @@ namespace Scada.Client public class DataFormatter { /// - /// Делегат получения цвета по статусу + /// Делегат получения свойств статуса входного канала /// - public delegate string GetColorByStatDelegate(int stat, string defaultColor); + public delegate CnlStatProps GetCnlStatPropsDelegate(int stat); - /// - /// Количество знаков дробной части по умолчанию - /// - protected const int DefDecDig = 3; /// /// Пустое значение входного канала /// - protected const string EmptyVal = "---"; + public const string EmptyVal = "---"; /// /// Отсутствующее значение входного канала /// @@ -58,7 +53,11 @@ public class DataFormatter /// /// Обозначение следующего часа /// - protected const string NextHourVal = "***"; + protected const string NextHourVal = "*"; + /// + /// Количество знаков дробной части по умолчанию + /// + protected const int DefDecDig = 3; /// /// Цвет значения по умолчанию /// @@ -193,13 +192,40 @@ public void FormatCnlVal(double val, int stat, InCnlProps cnlProps, string decSe } /// - /// Получить цвет значения входного канала + /// Получить текст события + /// + public string GetEventText(EventTableLight.Event ev, InCnlProps cnlProps, CnlStatProps cnlStatProps) + { + if (string.IsNullOrEmpty(ev.Descr)) + { + // текст в формате "<статус>: <значение>" + StringBuilder sbText = cnlStatProps == null ? + new StringBuilder() : new StringBuilder(cnlStatProps.Name); + + if (ev.NewCnlStat > BaseValues.CnlStatuses.Undefined) + { + if (sbText.Length > 0) + sbText.Append(": "); + sbText.Append(FormatCnlVal(ev.NewCnlVal, ev.NewCnlStat, cnlProps, true)); + } + + return sbText.ToString(); + } + else + { + // только описание события + return ev.Descr; + } + } + + /// + /// Получить цвет значения входного канала /// - public string GetCnlValColor(double val, int stat, InCnlProps cnlProps, GetColorByStatDelegate getColorByStat) + public string GetCnlValColor(double val, int stat, InCnlProps cnlProps, CnlStatProps cnlStatProps) { try { - if (cnlProps == null || getColorByStat == null) + if (cnlProps == null) { return DefColor; } @@ -211,7 +237,8 @@ public string GetCnlValColor(double val, int stat, InCnlProps cnlProps, GetColor stat == BaseValues.CnlStatuses.FormulaError || stat == BaseValues.CnlStatuses.Unreliable) { - return getColorByStat(stat, DefColor); + return cnlStatProps == null || string.IsNullOrEmpty(cnlStatProps.Color) ? + DefColor : cnlStatProps.Color; } else { @@ -240,19 +267,19 @@ public bool CurDataVisible(DateTime dataAge, DateTime nowDT, out string emptyVal /// /// Определить необходимость отображения часовых данных /// - public bool HourDataVisible(DateTime dataAge, DateTime nowDT, int stat, out string emptyVal) + public bool HourDataVisible(DateTime dataAge, DateTime nowDT, bool snapshotExists, out string emptyVal) { - if (stat > 0 || dataAge.Date < nowDT.Date) + if (snapshotExists || dataAge.Date < nowDT.Date) { emptyVal = EmptyVal; - return true; + return snapshotExists; } else if (dataAge.Date > nowDT.Date) { emptyVal = NoVal; return false; } - else // dataDT.Date == nowDT.Date + else // dataAge.Date == nowDT.Date { if (dataAge.Hour > nowDT.Hour + 1) { @@ -267,7 +294,7 @@ public bool HourDataVisible(DateTime dataAge, DateTime nowDT, int stat, out stri else { emptyVal = EmptyVal; - return true; + return snapshotExists; } } } diff --git a/ScadaData/ScadaData/Client/ISupportLoading.cs b/ScadaData/ScadaData/Client/ISupportLoading.cs new file mode 100644 index 000000000..02efb6a29 --- /dev/null +++ b/ScadaData/ScadaData/Client/ISupportLoading.cs @@ -0,0 +1,46 @@ +/* + * Copyright 2016 Mikhail Shiryaev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Product : Rapid SCADA + * Module : ScadaData + * Summary : Specifies objects that support loading + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + */ + +using System.IO; + +namespace Scada.Client +{ + /// + /// Specifies objects that support loading + /// Определяет объекты, поддерживающие загрузку + /// + public interface ISupportLoading + { + /// + /// Загрузить объект из потока + /// + void LoadFromStream(Stream stream); + + /// + /// Очистить объект + /// + void Clear(); + } +} diff --git a/ScadaData/ScadaData/Client/ServerComm.cs b/ScadaData/ScadaData/Client/ServerComm.cs index 96af3e751..b774fd773 100644 --- a/ScadaData/ScadaData/Client/ServerComm.cs +++ b/ScadaData/ScadaData/Client/ServerComm.cs @@ -25,15 +25,14 @@ #undef DETAILED_LOG // выводить в журнал подробную информацию об обмене данными со SCADA-Сервером +using Scada.Data.Tables; using System; -using System.Collections.Generic; -using System.Text; -using System.Threading; +using System.Data; using System.IO; using System.Net; using System.Net.Sockets; -using System.Data; -using Scada.Data; +using System.Text; +using System.Threading; using Utils; namespace Scada.Client @@ -78,6 +77,7 @@ public enum CommStates /// /// Роли пользователей /// + [Obsolete("Use BaseValues.Roles")] public enum Roles { /// @@ -301,27 +301,33 @@ public string CommStateDescr switch (commState) { case CommStates.Disconnected: - stateDescr.Append(Localization.UseRussian ? "соединение не установлено" : + stateDescr.Append(Localization.UseRussian ? + "соединение не установлено" : "not connected"); break; case CommStates.Connected: - stateDescr.Append(Localization.UseRussian ? "соединение установлено" : + stateDescr.Append(Localization.UseRussian ? + "соединение установлено" : "connected"); break; case CommStates.Authorized: - stateDescr.Append(Localization.UseRussian ? "авторизация успешна" : + stateDescr.Append(Localization.UseRussian ? + "авторизация успешна" : "authentication is successful"); break; case CommStates.NotReady: - stateDescr.Append(Localization.UseRussian ? "SCADA-Сервер не готов" : + stateDescr.Append(Localization.UseRussian ? + "SCADA-Сервер не готов" : "SCADA-Server isn't ready"); break; case CommStates.Error: - stateDescr.Append(Localization.UseRussian ? "ошибка обмена данными" : + stateDescr.Append(Localization.UseRussian ? + "ошибка обмена данными" : "communication error"); break; case CommStates.WaitResponse: - stateDescr.Append(Localization.UseRussian ? "ожидание ответа" : + stateDescr.Append(Localization.UseRussian ? + "ожидание ответа" : "waiting for response"); break; } @@ -460,22 +466,24 @@ protected bool Connect() // обработка считанных данных if (bytesRead == buf.Length && CheckDataFormat(buf, 0x01)) { - Roles role = (Roles)buf[3]; + int roleID = buf[3]; - if (role == Roles.App) + if (roleID == BaseValues.Roles.App) { commState = CommStates.Authorized; } - else if (role < Roles.Err) + else if (roleID < BaseValues.Roles.Err) { - errMsg = Localization.UseRussian ? "Недостаточно прав для соединения со SCADA-Сервером" : + errMsg = Localization.UseRussian ? + "Недостаточно прав для соединения со SCADA-Сервером" : "Insufficient rights to connect to SCADA-Server"; WriteAction(errMsg, Log.ActTypes.Error); commState = CommStates.Error; } - else // role == Roles.Err + else // roleID == BaseValues.Roles.Err { - errMsg = Localization.UseRussian ? "Неверное имя пользователя или пароль" : + errMsg = Localization.UseRussian ? + "Неверное имя пользователя или пароль" : "User name or password is incorrect"; WriteAction(errMsg, Log.ActTypes.Error); commState = CommStates.Error; @@ -492,7 +500,8 @@ protected bool Connect() } else { - errMsg = Localization.UseRussian ? "Неверный формат ответа SCADA-Сервера на запрос версии" : + errMsg = Localization.UseRussian ? + "Неверный формат ответа SCADA-Сервера на запрос версии" : "Incorrect SCADA-Server response to version request"; WriteAction(errMsg, Log.ActTypes.Error); commState = CommStates.Error; @@ -500,7 +509,8 @@ protected bool Connect() } catch (Exception ex) { - errMsg = (Localization.UseRussian ? "Ошибка при установке соединения со SCADA-Сервером: " : + errMsg = (Localization.UseRussian ? + "Ошибка при установке соединения со SCADA-Сервером: " : "Error connecting to SCADA-Server: ") + ex.Message; WriteAction(errMsg, Log.ActTypes.Exception); } @@ -529,7 +539,8 @@ protected void Disconnect() if (tcpClient != null) { - WriteAction(Localization.UseRussian ? "Разрыв соединения со SCADA-Сервером" : + WriteAction(Localization.UseRussian ? + "Разрыв соединения со SCADA-Сервером" : "Disconnect from SCADA-Server", Log.ActTypes.Action); if (netStream != null) @@ -546,7 +557,8 @@ protected void Disconnect() } catch (Exception ex) { - errMsg = (Localization.UseRussian ? "Ошибка при разрыве соединения со SCADA-Сервером: " : + errMsg = (Localization.UseRussian ? + "Ошибка при разрыве соединения со SCADA-Сервером: " : "Error disconnecting from SCADA-Server: ") + ex.Message; WriteAction(errMsg, Log.ActTypes.Exception); } @@ -588,7 +600,8 @@ protected void ClearNetStream() } catch (Exception ex) { - errMsg = (Localization.UseRussian ? "Ошибка при очистке сетевого потока: " : + errMsg = (Localization.UseRussian ? + "Ошибка при очистке сетевого потока: " : "Error clear network stream: ") + ex.Message; WriteAction(errMsg, Log.ActTypes.Exception); } @@ -599,92 +612,107 @@ protected void ClearNetStream() /// protected bool RestoreConnection() { - bool connectNeeded = false; // требуется повторное соединение - DateTime now = DateTime.Now; - - if (commState >= CommStates.Authorized) + try { - if (now - restConnSuccDT > PingSpan) + bool connectNeeded = false; // требуется повторное соединение + DateTime now = DateTime.Now; + + if (commState >= CommStates.Authorized) { - // проверка соединения - try + if (now - restConnSuccDT > PingSpan) { - WriteAction(Localization.UseRussian ? "Запрос состояния SCADA-Сервера" : - "Request SCADA-Server state", Log.ActTypes.Action); - commState = CommStates.WaitResponse; - - // запрос состояния SCADA-Сервера (ping) - byte[] buf = new byte[3]; - buf[0] = 0x03; - buf[1] = 0x00; - buf[2] = 0x02; - netStream.Write(buf, 0, 3); - - // приём результата - buf = new byte[4]; - netStream.Read(buf, 0, 4); - - // обработка результата - if (CheckDataFormat(buf, 0x02)) + // проверка соединения + try { - commState = buf[3] > 0 ? CommStates.Authorized : CommStates.NotReady; + WriteAction(Localization.UseRussian ? + "Запрос состояния SCADA-Сервера" : + "Request SCADA-Server state", Log.ActTypes.Action); + commState = CommStates.WaitResponse; + + // запрос состояния SCADA-Сервера (ping) + byte[] buf = new byte[3]; + buf[0] = 0x03; + buf[1] = 0x00; + buf[2] = 0x02; + netStream.Write(buf, 0, 3); + + // приём результата + buf = new byte[4]; + netStream.Read(buf, 0, 4); + + // обработка результата + if (CheckDataFormat(buf, 0x02)) + { + commState = buf[3] > 0 ? CommStates.Authorized : CommStates.NotReady; + } + else + { + errMsg = Localization.UseRussian ? + "Неверный формат ответа SCADA-Сервера на запрос состояния" : + "Incorrect SCADA-Server response to state request"; + WriteAction(errMsg, Log.ActTypes.Error); + commState = CommStates.Error; + connectNeeded = true; + } } - else + catch { - errMsg = Localization.UseRussian ? - "Неверный формат ответа SCADA-Сервера на запрос состояния" : - "Incorrect SCADA-Server response to state request"; - WriteAction(errMsg, Log.ActTypes.Error); - commState = CommStates.Error; connectNeeded = true; } } - catch - { - connectNeeded = true; - } - } - } - else if (now - restConnErrDT > ConnectSpan) - { - connectNeeded = true; - } - - // соединение при необходимости - if (connectNeeded) - { - if (tcpClient != null) - Disconnect(); - - if (Connect()) - { - restConnSuccDT = now; - restConnErrDT = DateTime.MinValue; - return true; } - else + else if (now - restConnErrDT > ConnectSpan) { - restConnSuccDT = DateTime.MinValue; - restConnErrDT = now; - return false; + connectNeeded = true; } - } - else - { - ClearNetStream(); // очистка потока данных TCP-клиента - if (commState >= CommStates.Authorized) + // соединение при необходимости + if (connectNeeded) { - restConnSuccDT = now; - return true; + if (tcpClient != null) + Disconnect(); + + if (Connect()) + { + restConnSuccDT = now; + restConnErrDT = DateTime.MinValue; + return true; + } + else + { + restConnSuccDT = DateTime.MinValue; + restConnErrDT = now; + return false; + } } else { - errMsg = Localization.UseRussian ? "Невозможно соединиться со SCADA-Сервером. Повторите попытку." : - "Unable to connect to SCADA-Server. Try again."; - return false; + ClearNetStream(); // очистка потока данных TCP-клиента + + if (commState >= CommStates.Authorized) + { + restConnSuccDT = now; + return true; + } + else + { + errMsg = Localization.UseRussian ? + "Невозможно соединиться со SCADA-Сервером. Повторите попытку." : + "Unable to connect to SCADA-Server. Try again."; + WriteAction(errMsg, Log.ActTypes.Error); + return false; + } } } + catch (Exception ex) + { + errMsg = (Localization.UseRussian ? + "Ошибка при восстановлении соединения со SCADA-Сервером: " : + "Error restoring connection with SCADA-Server: ") + ex.Message; + WriteAction(errMsg, Log.ActTypes.Exception); + commState = CommStates.Error; + return false; + } } /// @@ -703,7 +731,7 @@ protected void RestoreReceiveTimeout() /// /// Принять файл от SCADA-Сервера /// - protected bool ReceiveFile(Dirs dir, string fileName, Stream inStream) + protected bool ReceiveFileToStream(Dirs dir, string fileName, Stream inStream) { bool result = false; string filePath = DirToString(dir) + fileName; @@ -711,18 +739,19 @@ protected bool ReceiveFile(Dirs dir, string fileName, Stream inStream) try { #if DETAILED_LOG - WriteAction(string.Format(Localization.UseRussian ? "Приём файла {0} от SCADA-Сервера" : + WriteAction(string.Format(Localization.UseRussian ? + "Приём файла {0} от SCADA-Сервера" : "Receive file {0} from SCADA-Server", filePath), Log.ActTypes.Action); #endif commState = CommStates.WaitResponse; tcpClient.ReceiveTimeout = commSettings.ServerTimeout; - const int dataSize = 10240; // размер запрашиваемых данных 10 кБ - const byte dataSizeL = dataSize % 256; - const byte dataSizeH = dataSize / 256; + const int DataSize = 50 * 1024; // размер запрашиваемых данных 50 КБ + const byte DataSizeL = DataSize % 256; + const byte DataSizeH = DataSize / 256; - byte[] buf = new byte[6 + dataSize]; // буфер отправляемых и получаемых данных + byte[] buf = new byte[6 + DataSize]; // буфер отправляемых и получаемых данных bool open = true; // выполняется открытие файла bool stop = false; // признак завершения приёма данных @@ -739,8 +768,8 @@ protected bool ReceiveFile(Dirs dir, string fileName, Stream inStream) buf[3] = (byte)dir; buf[4] = fileNameLen; Array.Copy(Encoding.Default.GetBytes(fileName), 0, buf, 5, fileNameLen); - buf[cmdLen - 2] = dataSizeL; - buf[cmdLen - 1] = dataSizeH; + buf[cmdLen - 2] = DataSizeL; + buf[cmdLen - 1] = DataSizeH; netStream.Write(buf, 0, cmdLen); } else @@ -749,8 +778,8 @@ protected bool ReceiveFile(Dirs dir, string fileName, Stream inStream) buf[0] = 0x05; buf[1] = 0x00; buf[2] = 0x0A; - buf[3] = dataSizeL; - buf[4] = dataSizeH; + buf[3] = DataSizeL; + buf[4] = DataSizeH; netStream.Write(buf, 0, 5); } @@ -763,7 +792,7 @@ protected bool ReceiveFile(Dirs dir, string fileName, Stream inStream) if (bytesRead == headerLen) { dataSizeRead = buf[headerLen - 2] + 256 * buf[headerLen - 1]; - if (0 < dataSizeRead && dataSizeRead <= dataSize) + if (0 < dataSizeRead && dataSizeRead <= DataSize) bytesRead += ReadNetStream(buf, headerLen, dataSizeRead); } @@ -777,7 +806,7 @@ protected bool ReceiveFile(Dirs dir, string fileName, Stream inStream) { inStream.Write(buf, 6, dataSizeRead); commState = CommStates.Authorized; - stop = dataSizeRead < dataSize; + stop = dataSizeRead < DataSize; } else { @@ -793,7 +822,7 @@ protected bool ReceiveFile(Dirs dir, string fileName, Stream inStream) { inStream.Write(buf, 5, dataSizeRead); commState = CommStates.Authorized; - stop = dataSizeRead < dataSize; + stop = dataSizeRead < DataSize; } } else @@ -817,7 +846,8 @@ protected bool ReceiveFile(Dirs dir, string fileName, Stream inStream) } catch (Exception ex) { - errMsg = string.Format(Localization.UseRussian ? "Ошибка при приёме файла {0} от SCADA-Сервера: " : + errMsg = string.Format(Localization.UseRussian ? + "Ошибка при приёме файла {0} от SCADA-Сервера: " : "Error receiving file {0} from SCADA-Server: ", filePath) + ex.Message; WriteAction(errMsg, Log.ActTypes.Exception); Disconnect(); @@ -844,7 +874,8 @@ protected bool SendCommand(int userID, int ctrlCnl, double cmdVal, byte[] cmdDat { if (RestoreConnection()) { - WriteAction(Localization.UseRussian ? "Отправка команды ТУ SCADA-Серверу" : + WriteAction(Localization.UseRussian ? + "Отправка команды ТУ SCADA-Серверу" : "Send telecommand to SCADA-Server", Log.ActTypes.Action); commState = CommStates.WaitResponse; @@ -901,7 +932,8 @@ protected bool SendCommand(int userID, int ctrlCnl, double cmdVal, byte[] cmdDat } else { - errMsg = Localization.UseRussian ? "Неверный формат ответа SCADA-Сервера на команду ТУ" : + errMsg = Localization.UseRussian ? + "Неверный формат ответа SCADA-Сервера на команду ТУ" : "Incorrect SCADA-Server response to telecommand"; WriteAction(errMsg, Log.ActTypes.Error); commState = CommStates.Error; @@ -910,7 +942,8 @@ protected bool SendCommand(int userID, int ctrlCnl, double cmdVal, byte[] cmdDat } catch (Exception ex) { - errMsg = (Localization.UseRussian ? "Ошибка при отправке команды ТУ SCADA-Серверу: " : + errMsg = (Localization.UseRussian ? + "Ошибка при отправке команды ТУ SCADA-Серверу: " : "Error sending telecommand to SCADA-Server: ") + ex.Message; WriteAction(errMsg, Log.ActTypes.Exception); Disconnect(); @@ -933,7 +966,7 @@ public bool CheckUser(string username, string password, out int roleID) { Monitor.Enter(tcpLock); bool result = false; - roleID = (int)Roles.Disabled; + roleID = BaseValues.Roles.Disabled; errMsg = ""; try @@ -1015,7 +1048,7 @@ public bool ReceiveBaseTable(string tableName, DataTable dataTable) { using (MemoryStream memStream = new MemoryStream()) { - if (ReceiveFile(Dirs.BaseDAT, tableName, memStream)) + if (ReceiveFileToStream(Dirs.BaseDAT, tableName, memStream)) { BaseAdapter adapter = new BaseAdapter(); adapter.Stream = memStream; @@ -1035,7 +1068,8 @@ public bool ReceiveBaseTable(string tableName, DataTable dataTable) } catch (Exception ex) { - errMsg = (Localization.UseRussian ? "Ошибка при приёме таблицы базы конфигурации от SCADA-Сервера: " : + errMsg = (Localization.UseRussian ? + "Ошибка при приёме таблицы базы конфигурации от SCADA-Сервера: " : "Error receiving configuration database table from SCADA-Server: ") + ex.Message; WriteAction(errMsg, Log.ActTypes.Exception); } @@ -1075,7 +1109,7 @@ public bool ReceiveSrezTable(string tableName, SrezTableLight srezTableLight) // приём данных using (MemoryStream memStream = new MemoryStream()) { - if (ReceiveFile(dir, tableName, memStream)) + if (ReceiveFileToStream(dir, tableName, memStream)) { SrezAdapter adapter = new SrezAdapter(); adapter.Stream = memStream; @@ -1098,7 +1132,8 @@ public bool ReceiveSrezTable(string tableName, SrezTableLight srezTableLight) } catch (Exception ex) { - errMsg = (Localization.UseRussian ? "Ошибка при приёме таблицы срезов от SCADA-Сервера: " : + errMsg = (Localization.UseRussian ? + "Ошибка при приёме таблицы срезов от SCADA-Сервера: " : "Error receiving data table from SCADA-Server: ") + ex.Message; WriteAction(errMsg, Log.ActTypes.Exception); } @@ -1138,7 +1173,7 @@ public bool ReceiveTrend(string tableName, DateTime date, Trend trend) if (tableName == SrezAdapter.CurTableName) { - tableType = (byte)0x01; + tableType = 0x01; year = month = day = 0; } else @@ -1187,7 +1222,7 @@ public bool ReceiveTrend(string tableName, DateTime date, Trend trend) { Trend.Point point; int pos = i * 18 + 7; - point.DateTime = Arithmetic.DecodeDateTime(BitConverter.ToDouble(buf, pos)); + point.DateTime = ScadaUtils.DecodeDateTime(BitConverter.ToDouble(buf, pos)); point.Val = BitConverter.ToDouble(buf, pos + 8); point.Stat = BitConverter.ToUInt16(buf, pos + 16); @@ -1220,7 +1255,8 @@ public bool ReceiveTrend(string tableName, DateTime date, Trend trend) } catch (Exception ex) { - errMsg = (Localization.UseRussian ? "Ошибка при приёме тренда входного канала от SCADA-Сервера: " : + errMsg = (Localization.UseRussian ? + "Ошибка при приёме тренда входного канала от SCADA-Сервера: " : "Error receiving input channel trend from SCADA-Server: ") + ex.Message; WriteAction(errMsg, Log.ActTypes.Exception); Disconnect(); @@ -1251,7 +1287,7 @@ public bool ReceiveEventTable(string tableName, EventTableLight eventTableLight) { using (MemoryStream memStream = new MemoryStream()) { - if (ReceiveFile(Dirs.Events, tableName, memStream)) + if (ReceiveFileToStream(Dirs.Events, tableName, memStream)) { EventAdapter adapter = new EventAdapter(); adapter.Stream = memStream; @@ -1274,7 +1310,8 @@ public bool ReceiveEventTable(string tableName, EventTableLight eventTableLight) } catch (Exception ex) { - errMsg = (Localization.UseRussian ? "Ошибка при приёме таблицы событий от SCADA-Сервера: " : + errMsg = (Localization.UseRussian ? + "Ошибка при приёме таблицы событий от SCADA-Сервера: " : "Error receiving event table from SCADA-Server: ") + ex.Message; WriteAction(errMsg, Log.ActTypes.Exception); } @@ -1290,6 +1327,17 @@ public bool ReceiveEventTable(string tableName, EventTableLight eventTableLight) /// Принять представление от SCADA-Сервера /// public bool ReceiveView(string fileName, BaseView view) + { + bool result = ReceiveUiObj(fileName, view); + if (result && view != null) + view.Path = fileName; + return result; + } + + /// + /// Принять объект пользовательского интерфейса от SCADA-Сервера + /// + public bool ReceiveUiObj(string fileName, ISupportLoading uiObj) { Monitor.Enter(tcpLock); bool result = false; @@ -1303,9 +1351,9 @@ public bool ReceiveView(string fileName, BaseView view) { using (MemoryStream memStream = new MemoryStream()) { - if (ReceiveFile(Dirs.Itf, fileName, memStream)) + if (ReceiveFileToStream(Dirs.Itf, fileName, memStream)) { - view.LoadFromStream(memStream); + uiObj.LoadFromStream(memStream); result = true; } } @@ -1313,17 +1361,15 @@ public bool ReceiveView(string fileName, BaseView view) } finally { - // очистка представления, если не удалось получить новые данные if (!result) - view.Clear(); - // установка наименования объекта интерфейса - view.ItfObjName = Path.GetFileName(fileName); + uiObj.Clear(); } } catch (Exception ex) { - errMsg = (Localization.UseRussian ? "Ошибка при приёме представления от SCADA-Сервера: " : - "Error receiving view from SCADA-Server: ") + ex.Message; + errMsg = (Localization.UseRussian ? + "Ошибка при приёме объекта пользовательского интерфейса от SCADA-Сервера: " : + "Error receiving user interface object from SCADA-Server: ") + ex.Message; WriteAction(errMsg, Log.ActTypes.Exception); } finally @@ -1335,31 +1381,14 @@ public bool ReceiveView(string fileName, BaseView view) } /// - /// Принять объект интерфейса от SCADA-Сервера + /// Принять файл от SCADA-Сервера /// - public bool ReceiveItfObj(string fileName, Stream stream) + public bool ReceiveFile(Dirs dir, string fileName, Stream stream) { - Monitor.Enter(tcpLock); - bool result = false; - errMsg = ""; - - try - { - if (RestoreConnection() && ReceiveFile(Dirs.Itf, fileName, stream)) - result = true; - } - catch (Exception ex) - { - errMsg = (Localization.UseRussian ? "Ошибка при приёме объекта интерфейса от SCADA-Сервера: " : - "Error receiving interface object from SCADA-Server: ") + ex.Message; - WriteAction(errMsg, Log.ActTypes.Exception); - } - finally + lock (tcpLock) { - Monitor.Exit(tcpLock); + return RestoreConnection() && ReceiveFileToStream(dir, fileName, stream); } - - return result; } /// @@ -1407,7 +1436,7 @@ public DateTime ReceiveFileAge(Dirs dir, string fileName) if (CheckDataFormat(buf, 0x0C)) { double dt = BitConverter.ToDouble(buf, 4); - result = dt == 0.0 ? DateTime.MinValue : Arithmetic.DecodeDateTime(dt); + result = dt == 0.0 ? DateTime.MinValue : ScadaUtils.DecodeDateTime(dt); commState = CommStates.Authorized; } else @@ -1550,7 +1579,8 @@ public bool ReceiveCommand(out int kpNum, out int cmdNum, out double cmdVal, out } catch (Exception ex) { - errMsg = (Localization.UseRussian ? "Ошибка при приёме команды ТУ от SCADA-Сервера: " : + errMsg = (Localization.UseRussian ? + "Ошибка при приёме команды ТУ от SCADA-Сервера: " : "Error requesting telecommand from SCADA-Server: ") + ex.Message; WriteAction(errMsg, Log.ActTypes.Exception); Disconnect(); @@ -1631,7 +1661,8 @@ public bool SendSrez(SrezTableLight.Srez curSrez, out bool result) } catch (Exception ex) { - errMsg = (Localization.UseRussian ? "Ошибка при отправке текущего среза SCADA-Серверу: " : + errMsg = (Localization.UseRussian ? + "Ошибка при отправке текущего среза SCADA-Серверу: " : "Error sending current data to SCADA-Server: ") + ex.Message; WriteAction(errMsg, Log.ActTypes.Exception); Disconnect(); @@ -1671,7 +1702,7 @@ public bool SendArchive(SrezTableLight.Srez arcSrez, out bool result) buf[1] = (byte)(cmdLen / 256); buf[2] = 0x04; - double arcDT = Arithmetic.EncodeDateTime(arcSrez.DateTime); + double arcDT = ScadaUtils.EncodeDateTime(arcSrez.DateTime); byte[] bytes = BitConverter.GetBytes(arcDT); Array.Copy(bytes, 0, buf, 3, 8); @@ -1716,7 +1747,8 @@ public bool SendArchive(SrezTableLight.Srez arcSrez, out bool result) } catch (Exception ex) { - errMsg = (Localization.UseRussian ? "Ошибка при отправке архивного среза SCADA-Серверу: " : + errMsg = (Localization.UseRussian ? + "Ошибка при отправке архивного среза SCADA-Серверу: " : "Error sending archive data to SCADA-Server: ") + ex.Message; WriteAction(errMsg, Log.ActTypes.Exception); Disconnect(); @@ -1756,7 +1788,7 @@ public bool SendEvent(EventTableLight.Event aEvent, out bool result) buf[1] = (byte)(cmdLen / 256); buf[2] = 0x05; - double evDT = Arithmetic.EncodeDateTime(aEvent.DateTime); + double evDT = ScadaUtils.EncodeDateTime(aEvent.DateTime); byte[] bytes = BitConverter.GetBytes(evDT); Array.Copy(bytes, 0, buf, 3, 8); @@ -1812,7 +1844,8 @@ public bool SendEvent(EventTableLight.Event aEvent, out bool result) } catch (Exception ex) { - errMsg = (Localization.UseRussian ? "Ошибка при отправке события SCADA-Серверу: " : + errMsg = (Localization.UseRussian ? + "Ошибка при отправке события SCADA-Серверу: " : "Error sending event to SCADA-Server: ") + ex.Message; WriteAction(errMsg, Log.ActTypes.Exception); Disconnect(); @@ -1840,7 +1873,8 @@ public bool CheckEvent(int userID, DateTime date, int evNum, out bool result) { if (RestoreConnection()) { - WriteAction(Localization.UseRussian ? "Отправка команды квитирования события SCADA-Серверу" : + WriteAction(Localization.UseRussian ? + "Отправка команды квитирования события SCADA-Серверу" : "Send check event command to SCADA-Server", Log.ActTypes.Action); commState = CommStates.WaitResponse; @@ -1910,7 +1944,7 @@ public void Close() /// /// Получить роль пользователя по её идентификатору /// - [Obsolete("Get rid of using ServerComm.Roles. Use BaseValues.Roles instead")] + [Obsolete("Use BaseValues.Roles")] public static Roles GetRole(int roleID) { if ((int)Roles.Admin <= roleID && roleID <= (int)Roles.App) diff --git a/ScadaData/ScadaData/Client/ViewCache.cs b/ScadaData/ScadaData/Client/ViewCache.cs index 373767375..085819fa3 100644 --- a/ScadaData/ScadaData/Client/ViewCache.cs +++ b/ScadaData/ScadaData/Client/ViewCache.cs @@ -23,7 +23,7 @@ * Modified : 2016 */ -using Scada.Data; +using Scada.Data.Models; using System; using Utils; @@ -40,7 +40,7 @@ public class ViewCache /// protected const int Capacity = int.MaxValue; /// - /// Период хранения в кеше + /// Период хранения в кеше с момента последнего доступа /// protected static readonly TimeSpan StorePeriod = TimeSpan.FromMinutes(10); /// @@ -61,11 +61,6 @@ public class ViewCache /// protected readonly Log log; - /// - /// Объект кеша представлений - /// - protected Cache cache; - /// /// Конструктор, ограничивающий создание объекта без параметров @@ -90,103 +85,145 @@ public ViewCache(ServerComm serverComm, DataAccess dataAccess, Log log) this.dataAccess = dataAccess; this.log = log; - cache = new Cache(StorePeriod, Capacity); + Cache = new Cache(StorePeriod, Capacity); } /// - /// Получить представление из кэша или от сервера + /// Получить объект кеша представлений /// - public T GetView(int viewID, bool throwOnError = false) where T : BaseView + /// Использовать вне данного класса только для получения состояния кеша + public Cache Cache { get; protected set; } + + + /// + /// Получить свойства представления, вызвав исключение в случае неудачи + /// + protected UiObjProps GetViewProps(int viewID) { - try + UiObjProps viewProps = dataAccess.GetUiObjProps(viewID); + + if (viewProps == null) { - T view = null; + throw new ScadaException(Localization.UseRussian ? + "Отсутствуют свойства представления." : + "View properties are missing."); + } - // получение представления из кеша - DateTime utcNowDT = DateTime.UtcNow; - Cache.CacheItem cacheItem = cache.GetItem(viewID, utcNowDT); - BaseView viewFromCache; - DateTime viewAge; // время изменения файла представления - bool viewIsNotValid; // представление могло устареть + return viewProps; + } - if (cacheItem == null) + /// + /// Загрузить представление от сервера + /// + protected bool LoadView(Type viewType, int viewID, DateTime viewAge, + ref BaseView view, out DateTime newViewAge) + { + UiObjProps viewProps = GetViewProps(viewID); + newViewAge = serverComm.ReceiveFileAge(ServerComm.Dirs.Itf, viewProps.Path); + + if (newViewAge == DateTime.MinValue) + { + throw new ScadaException(Localization.UseRussian ? + "Не удалось принять время изменения файла представления." : + "Unable to receive view file modification time."); + } + else if (newViewAge != viewAge) // файл представления изменён + { + // создание и загрузка нового представления + if (view == null) + view = (BaseView)Activator.CreateInstance(viewType); + + if (serverComm.ReceiveView(viewProps.Path, view)) { - viewFromCache = null; - viewAge = DateTime.MinValue; - viewIsNotValid = true; + return true; } else { - viewFromCache = cacheItem.Value; - viewAge = cacheItem.ValueAge; - viewIsNotValid = utcNowDT - cacheItem.ValueRefrDT > ViewValidSpan; + throw new ScadaException(Localization.UseRussian ? + "Не удалось принять представление." : + "Unable to receive view."); } + } + else + { + return false; + } + } + + + /// + /// Получить представление из кэша или от сервера + /// + /// Метод используется, если тип предсталения неизвестен на момент компиляции + public BaseView GetView(Type viewType, int viewID, bool throwOnError = false) + { + try + { + if (viewType == null) + throw new ArgumentNullException("viewType"); - // получение представления от сервера - if (viewFromCache == null || viewIsNotValid) + // получение представления из кеша + DateTime utcNowDT = DateTime.UtcNow; + Cache.CacheItem cacheItem = Cache.GetOrCreateItem(viewID, utcNowDT); + + // блокировка доступа только к одному представлению + lock (cacheItem) { - ViewProps viewProps = dataAccess.GetViewProps(viewID); + BaseView view = null; // представление, которое необходимо получить + BaseView viewFromCache = cacheItem.Value; // представление из кеша + DateTime viewAge = cacheItem.ValueAge; // время изменения файла представления + DateTime newViewAge; // новое время изменения файла представления - if (viewProps == null) + if (viewFromCache == null) { - if (throwOnError) - throw new ScadaException(Localization.UseRussian ? - "Отсутствуют свойства представления." : - "View properties are missing."); - } - else - { - DateTime newViewAge = serverComm.ReceiveFileAge(ServerComm.Dirs.Itf, viewProps.FileName); + // создание нового представления + view = (BaseView)Activator.CreateInstance(viewType); - if (newViewAge == DateTime.MinValue) + if (view.StoredOnServer) { - if (throwOnError) - throw new ScadaException(Localization.UseRussian ? - "Не удалось принять время изменения файла представления." : - "Unable to receive view file modification time."); + if (LoadView(viewType, viewID, viewAge, ref view, out newViewAge)) + Cache.UpdateItem(cacheItem, view, newViewAge, utcNowDT); } - else if (newViewAge != viewAge) // файл представления изменён + else { - // создание и загрузка нового представления - view = (T)Activator.CreateInstance(typeof(T)); - if (serverComm.ReceiveView(viewProps.FileName, view)) - { - if (cacheItem == null) - // добавление представления в кеш - cache.AddValue(viewID, view, newViewAge, utcNowDT); - else - // обновление представления в кеше - cache.UpdateItem(cacheItem, view, newViewAge, utcNowDT); - } - else - { - if (throwOnError) - throw new ScadaException(Localization.UseRussian ? - "Не удалось принять представление." : - "Unable to receive view."); - } + UiObjProps viewProps = GetViewProps(viewID); + view.Path = viewProps.Path; + Cache.UpdateItem(cacheItem, view, DateTime.Now, utcNowDT); } } - } - - // использование представление из кеша - if (view == null && viewFromCache != null) - { - view = viewFromCache as T; - if (view == null && throwOnError) - throw new ScadaException(Localization.UseRussian ? - "Несоответствие типа представления." : - "View type mismatch."); - } + else if (viewFromCache.StoredOnServer) + { + // представление могло устареть + bool viewIsNotValid = utcNowDT - cacheItem.ValueRefrDT > ViewValidSpan; - return view; + if (viewIsNotValid && LoadView(viewType, viewID, viewAge, ref view, out newViewAge)) + Cache.UpdateItem(cacheItem, view, newViewAge, utcNowDT); + } + + // использование представления из кеша + if (view == null && viewFromCache != null) + { + if (viewFromCache.GetType().Equals(viewType)) + view = viewFromCache; + else + throw new ScadaException(Localization.UseRussian ? + "Несоответствие типа представления." : + "View type mismatch."); + } + + // привязка свойств каналов или обновление существующей привязки + if (view != null) + dataAccess.BindCnlProps(view); + + return view; + } } catch (Exception ex) { string errMsg = string.Format(Localization.UseRussian ? - "Ошибка при получении представления с ид.={0} из кэша или от сервера" : - "Error getting view with ID={0} from the cache or from the server", viewID); + "Ошибка при получении представления с ид.={0} из кэша или от сервера: {1}" : + "Error getting view with ID={0} from the cache or from the server: {1}", viewID, ex.Message); log.WriteException(ex, errMsg); if (throwOnError) @@ -196,22 +233,42 @@ public T GetView(int viewID, bool throwOnError = false) where T : BaseView } } + /// + /// Получить представление из кэша или от сервера + /// + public T GetView(int viewID, bool throwOnError = false) where T : BaseView + { + return GetView(typeof(T), viewID, throwOnError) as T; + } + /// /// Получить уже загруженное представление только из кэша /// - public BaseView GetViewFromCache(int viewID) + public BaseView GetViewFromCache(int viewID, bool throwOnFail = false) { try { - Cache.CacheItem cacheItem = cache.GetItem(viewID, DateTime.UtcNow); - return cacheItem == null ? null : cacheItem.Value; + Cache.CacheItem cacheItem = Cache.GetItem(viewID, DateTime.UtcNow); + BaseView view = cacheItem == null ? null : cacheItem.Value; + + if (view == null && throwOnFail) + throw new ScadaException(string.Format(Localization.UseRussian ? + "Представление не найдено в кэше" : + "The view is not found in the cache", viewID)); + + return view; } catch (Exception ex) { - log.WriteException(ex, Localization.UseRussian ? - "Ошибка при получении представления с ид.={0} из кэша" : - "Error getting view with ID={0} from the cache", viewID); - return null; + string errMsg = string.Format(Localization.UseRussian ? + "Ошибка при получении представления с ид.={0} из кэша: {1}" : + "Error getting view with ID={0} from the cache: {1}", viewID, ex.Message); + log.WriteException(ex, errMsg); + + if (throwOnFail) + throw new ScadaException(errMsg); + else + return null; } } } diff --git a/ScadaData/ScadaData/CommonPhrases.cs b/ScadaData/ScadaData/CommonPhrases.cs index 7625fa18f..890b83704 100644 --- a/ScadaData/ScadaData/CommonPhrases.cs +++ b/ScadaData/ScadaData/CommonPhrases.cs @@ -73,6 +73,8 @@ static CommonPhrases() public static string IncorrectXmlAttrVal { get; private set; } public static string IncorrectXmlParamVal { get; private set; } public static string XmlNodeNotFound { get; private set; } + public static string EventAck { get; private set; } + public static string EventNotAck { get; private set; } public static string CmdTypeTable { get; private set; } public static string CmdValTable { get; private set; } @@ -133,7 +135,7 @@ private static void SetToDefault() NonemptyRequired = "Требуется непустое значение."; DateTimeRequired = "Требуется дата и время."; LineLengthLimit = "Длина строки должна быть не более {0} символов."; - NotNumber = "\"{0}\" не является числом"; + NotNumber = "\"{0}\" не является числом."; LoadImageError = "Ошибка при загрузке изображения из файла:\n{0}"; LoadHyperlinkError = "Ошибка при загрузке гиперссылки из файла:\n{0}"; IncorrectFileFormat = "Некорректный формат файла."; @@ -143,6 +145,8 @@ private static void SetToDefault() IncorrectXmlAttrVal = "Некорректное значение XML-атрибута \"{0}\"."; IncorrectXmlParamVal = "Некорректное значение параметра \"{0}\"."; XmlNodeNotFound = "XML-узел \"{0}\" не найден внутри узла \"{1}\"."; + EventAck = "Да"; + EventNotAck = "Нет"; CmdTypeTable = "Типы команд"; CmdValTable = "Значения команд"; @@ -216,6 +220,8 @@ public static void Init() IncorrectXmlAttrVal = dict.GetPhrase("IncorrectXmlAttrVal", IncorrectXmlAttrVal); IncorrectXmlParamVal = dict.GetPhrase("IncorrectXmlParamVal", IncorrectXmlParamVal); XmlNodeNotFound = dict.GetPhrase("XmlNodeNotFound", XmlNodeNotFound); + EventAck = dict.GetPhrase("EventAck", EventAck); + EventNotAck = dict.GetPhrase("EventNotAck", EventNotAck); CmdTypeTable = dict.GetPhrase("CmdTypeTable", CmdTypeTable); CmdValTable = dict.GetPhrase("CmdValTable", CmdValTable); diff --git a/ScadaData/ScadaData/Data/Arithmetic.cs b/ScadaData/ScadaData/Data/Arithmetic.cs deleted file mode 100644 index c932a7e00..000000000 --- a/ScadaData/ScadaData/Data/Arithmetic.cs +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2014 Mikhail Shiryaev - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * - * Product : Rapid SCADA - * Module : ScadaData - * Summary : Auxiliary math calculations - * - * Author : Mikhail Shiryaev - * Created : 2005 - * Modified : 2013 - */ - -using System; -using System.Globalization; - -namespace Scada.Data -{ - /// - /// Auxiliary math calculations - /// Вспомогательные математические расчёты - /// - public static class Arithmetic - { - /// - /// Начало отсчёта времени в Delphi - /// - private static readonly DateTime DelphiTimeBegin = new DateTime(1899, 12, 30); - - /// - /// Выделить час, минуту и секунду из вещественного значения времени формата Delphi - /// - public static void DecodeTime(double time, out int hour, out int min, out int sec) - { - const double hh = 1.0 / 24; // 1 час - const double mm = 1.0 / 24 / 60; // 1 мин - const double ms = 1.0 / 24 / 60 / 60 / 1000; // 1 мс - - if (time < 0) - time = -time; - - time += ms; - time -= Math.Truncate(time); // (int)time работает некорректно для чисел time > int.MaxValue - hour = (int)(time * 24); - time -= hour * hh; - min = (int)(time * 24 * 60); - time -= min * mm; - sec = (int)(time * 24 * 60 * 60); - } - - /// - /// Преобразовать дату и время в формат DateTime из вещественного значения времени формата Delphi - /// - public static DateTime DecodeDateTime(double dateTime) - { - return DelphiTimeBegin.AddDays(dateTime); - } - - /// - /// Закодировать дату и время в вещественное значение времени формата Delphi - /// - public static double EncodeDateTime(DateTime dateTime) - { - return (dateTime - DelphiTimeBegin).TotalDays; - } - - /// - /// Извлечь дату из имени файла (без директории) таблицы срезов или событий - /// - public static DateTime ExtractDate(string fileName) - { - try - { - return DateTime.ParseExact(fileName.Substring(1, 6), "yyMMdd", CultureInfo.CurrentCulture); - } - catch - { - return DateTime.MinValue; - } - } - } -} diff --git a/ScadaData/ScadaData/Data/ViewProps.cs b/ScadaData/ScadaData/Data/Models/CnlStatProps.cs similarity index 60% rename from ScadaData/ScadaData/Data/ViewProps.cs rename to ScadaData/ScadaData/Data/Models/CnlStatProps.cs index 9f4a2776a..d650f7392 100644 --- a/ScadaData/ScadaData/Data/ViewProps.cs +++ b/ScadaData/ScadaData/Data/Models/CnlStatProps.cs @@ -16,25 +16,25 @@ * * Product : Rapid SCADA * Module : ScadaData - * Summary : View properties + * Summary : Input channel status properties * * Author : Mikhail Shiryaev * Created : 2016 * Modified : 2016 */ -namespace Scada.Data +namespace Scada.Data.Models { /// - /// View properties - /// Свойства представления + /// Input channel status properties + /// Свойства статуса входного канала /// - public class ViewProps + public class CnlStatProps { /// /// Конструктор /// - public ViewProps() + public CnlStatProps() : this(0) { @@ -43,27 +43,27 @@ public ViewProps() /// /// Конструктор /// - public ViewProps(int viewID) + public CnlStatProps(int stat) { - ViewID = viewID; - FileName = ""; - ViewTypeCode = ""; + Status = stat; + Name = ""; + Color = ""; } /// - /// Получить или установить идентификатор представления + /// Получить или установить значение статуса /// - public int ViewID { get; set; } + public int Status { get; set; } /// - /// Получить или установить имя файла представления + /// Получить или установить наименование /// - public string FileName { get; set; } + public string Name { get; set; } /// - /// Получить или установить код типа представления + /// Получить или установить цвет /// - public string ViewTypeCode { get; set; } + public string Color { get; set; } } } diff --git a/ScadaData/ScadaData/Data/Command.cs b/ScadaData/ScadaData/Data/Models/Command.cs similarity index 99% rename from ScadaData/ScadaData/Data/Command.cs rename to ScadaData/ScadaData/Data/Models/Command.cs index b468bad9f..2c1eaa77d 100644 --- a/ScadaData/ScadaData/Data/Command.cs +++ b/ScadaData/ScadaData/Data/Models/Command.cs @@ -23,10 +23,11 @@ * Modified : 2016 */ +using Scada.Data.Tables; using System; using System.Text; -namespace Scada.Data +namespace Scada.Data.Models { /// /// Telecontrol command diff --git a/ScadaData/ScadaData/Data/CtrlCnlProps.cs b/ScadaData/ScadaData/Data/Models/CtrlCnlProps.cs similarity index 91% rename from ScadaData/ScadaData/Data/CtrlCnlProps.cs rename to ScadaData/ScadaData/Data/Models/CtrlCnlProps.cs index 5f846e563..8bf8681a4 100644 --- a/ScadaData/ScadaData/Data/CtrlCnlProps.cs +++ b/ScadaData/ScadaData/Data/Models/CtrlCnlProps.cs @@ -23,10 +23,11 @@ * Modified : 2016 */ +using Scada.Data.Tables; using System; using System.Collections; -namespace Scada.Data +namespace Scada.Data.Models { /// /// Output channel properties @@ -77,7 +78,9 @@ public CtrlCnlProps(int ctrlCnlNum, string ctrlCnlName, int cmdTypeID) KPNum = 0; KPName = ""; CmdNum = 0; + CmdValID = 0; CmdValName = ""; + CmdVal = ""; CmdValArr = null; FormulaUsed = false; Formula = ""; @@ -125,6 +128,11 @@ public CtrlCnlProps(int ctrlCnlNum, string ctrlCnlName, int cmdTypeID) /// public int CmdNum { get; set; } + /// + /// Получить или установить идентификатор значений команды + /// + public int CmdValID { get; set; } + /// /// Получить или установить наименование значений команды /// @@ -133,6 +141,11 @@ public CtrlCnlProps(int ctrlCnlNum, string ctrlCnlName, int cmdTypeID) /// /// Получить или установить значения команды /// + public string CmdVal { get; set; } + + /// + /// Получить или установить массив значений команды + /// public string[] CmdValArr { get; set; } /// diff --git a/ScadaData/ScadaData/Data/Models/DispEvent.cs b/ScadaData/ScadaData/Data/Models/DispEvent.cs new file mode 100644 index 000000000..076c75afa --- /dev/null +++ b/ScadaData/ScadaData/Data/Models/DispEvent.cs @@ -0,0 +1,97 @@ +/* + * Copyright 2016 Mikhail Shiryaev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Product : Rapid SCADA + * Module : ScadaData + * Summary : Displayed event + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + */ + +namespace Scada.Data.Models +{ + /// + /// Displayed event + /// Отображаемое событие + /// + /// Свойства имеют короткие имена для передачи в формате JSON + public class DispEvent + { + /// + /// Конструктор + /// + public DispEvent() + { + Num = 0; + Time = ""; + Obj = ""; + KP = ""; + Cnl = ""; + Text = ""; + Ack = ""; + Color = ""; + Sound = false; + } + + + /// + /// Получить или установить порядковый номер + /// + public int Num { get; set; } + + /// + /// Получить или установить отформатированную дату и время + /// + public string Time { get; set; } + + /// + /// Получить или установить наименование объекта + /// + public string Obj { get; set; } + + /// + /// Получить или установить наименование КП + /// + public string KP { get; set; } + + /// + /// Получить или установить наименование входного канала + /// + public string Cnl { get; set; } + + /// + /// Получить или установить текст события + /// + public string Text { get; set; } + + /// + /// Получить или установить информацию о квитировании + /// + public string Ack { get; set; } + + /// + /// Получить или установить цвет + /// + public string Color { get; set; } + + /// + /// Получить или установить признак воспроизведения звука + /// + public bool Sound { get; set; } + } +} diff --git a/ScadaData/ScadaData/Data/Models/EntityRights.cs b/ScadaData/ScadaData/Data/Models/EntityRights.cs new file mode 100644 index 000000000..1ef7925ba --- /dev/null +++ b/ScadaData/ScadaData/Data/Models/EntityRights.cs @@ -0,0 +1,61 @@ +/* + * Copyright 2016 Mikhail Shiryaev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Product : Rapid SCADA + * Module : ScadaData + * Summary : Rights to access some entity + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + */ + +namespace Scada.Data.Models +{ + /// + /// Rights to access some entity + /// Права на доступ к некоторой сущности + /// + public struct EntityRights + { + /// + /// Отсутствие прав + /// + public static readonly EntityRights NoRights = new EntityRights(false, false); + + + /// + /// Конструктор + /// + public EntityRights(bool viewRight, bool ctrlRight) + : this() + { + ViewRight = viewRight; + ControlRight = ctrlRight; + } + + + /// + /// Получить или установить право на просмотр + /// + public bool ViewRight { get; set; } + + /// + /// Получить или установить право на управление + /// + public bool ControlRight { get; set; } + } +} diff --git a/ScadaData/ScadaData/Data/InCnlProps.cs b/ScadaData/ScadaData/Data/Models/InCnlProps.cs similarity index 90% rename from ScadaData/ScadaData/Data/InCnlProps.cs rename to ScadaData/ScadaData/Data/Models/InCnlProps.cs index 0f767eeba..2ad6dfdc0 100644 --- a/ScadaData/ScadaData/Data/InCnlProps.cs +++ b/ScadaData/ScadaData/Data/Models/InCnlProps.cs @@ -23,10 +23,11 @@ * Modified : 2015 */ +using Scada.Data.Tables; using System; using System.Collections; -namespace Scada.Data +namespace Scada.Data.Models { /// /// Input channel properties @@ -85,7 +86,9 @@ public InCnlProps(int cnlNum, string cnlName, int cnlTypeID) IconFileName = ""; ShowNumber = true; DecDigits = 3; + UnitID = 0; UnitName = ""; + UnitSign = ""; UnitArr = null; CtrlCnlNum = 0; EvEnabled = false; @@ -179,6 +182,11 @@ public InCnlProps(int cnlNum, string cnlName, int cnlTypeID) /// public int DecDigits { get; set; } + /// + /// Получить или установить идентификатор размерности + /// + public int UnitID { get; set; } + /// /// Получить или установить наименование размерности /// @@ -187,8 +195,24 @@ public InCnlProps(int cnlNum, string cnlName, int cnlTypeID) /// /// Получить или установить размерности /// + public string UnitSign { get; set; } + + /// + /// Получить или установить массив размерностей + /// public string[] UnitArr { get; set; } + /// + /// Получить размерность числового канала, если она единственная + /// + public string SingleUnit + { + get + { + return ShowNumber && UnitArr != null && UnitArr.Length == 1 ? UnitArr[0] : ""; + } + } + /// /// Получить или установить номер канала управления /// diff --git a/ScadaData/ScadaData/Data/Models/UiObjProps.cs b/ScadaData/ScadaData/Data/Models/UiObjProps.cs new file mode 100644 index 000000000..26ef7b74f --- /dev/null +++ b/ScadaData/ScadaData/Data/Models/UiObjProps.cs @@ -0,0 +1,191 @@ +/* + * Copyright 2016 Mikhail Shiryaev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Product : Rapid SCADA + * Module : ScadaData + * Summary : User interface object properties + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + */ + +using System; + +namespace Scada.Data.Models +{ + /// + /// User interface object properties + /// Свойства объекта пользовательского интерфейса + /// + public class UiObjProps + { + /// + /// Базовые типы объектов пользовательского интерфейса + /// + [Flags] + public enum BaseUiTypes + { + /// + /// Представление (по умолчанию) + /// + View, + /// + /// Отчёт + /// + Report, + /// + /// Окно данных + /// + DataWnd + } + + /// + /// Виды пути + /// + public enum PathKinds + { + /// + /// Не определён + /// + Undefined, + /// + /// Файл + /// + File, + /// + /// Ссылка + /// + Url + } + + + /// + /// Конструктор + /// + public UiObjProps() + : this(0) + { + } + + /// + /// Конструктор + /// + public UiObjProps(int viewID) + { + UiObjID = viewID; + Title = ""; + Path = ""; + TypeCode = ""; + BaseUiType = BaseUiTypes.View; + } + + + /// + /// Получить или установить идентификатор объекта пользовательского интерфейса + /// + public int UiObjID { get; set; } + + /// + /// Получить или установить заголовок + /// + public string Title { get; set; } + + /// + /// Получить или установить путь + /// + public string Path { get; set; } + + /// + /// Получить или установить код типа + /// + public string TypeCode { get; set; } + + /// + /// Получить или установить базовый тип + /// + public BaseUiTypes BaseUiType { get; set; } + + /// + /// Получить признак, что объект интерфейса пустой + /// + public bool IsEmpty + { + get + { + return string.IsNullOrEmpty(Title) && string.IsNullOrEmpty(Path); + } + } + + /// + /// Получить вид пути + /// + public PathKinds PathKind + { + get + { + if (string.IsNullOrEmpty(Path)) + return PathKinds.Undefined; + else if (Path.Contains("://")) + return PathKinds.Url; + else + return PathKinds.File; + } + } + + + /// + /// Извлечь путь, код типа и базовый тип объекта интерфейса из заданной строки + /// + public static UiObjProps Parse(string s) + { + s = s ?? ""; + int sepInd = s.IndexOf('@'); + string path = (sepInd >= 0 ? s.Substring(0, sepInd) : s).Trim(); + string typeCode = sepInd >= 0 ? s.Substring(sepInd + 1).Trim() : ""; + BaseUiTypes baseUiType = BaseUiTypes.View; + + if (typeCode.EndsWith("Rep", StringComparison.Ordinal)) + { + baseUiType = BaseUiTypes.Report; + } + else if (typeCode.EndsWith("Wnd", StringComparison.Ordinal)) + { + baseUiType = BaseUiTypes.DataWnd; + } + else if (typeCode == "") + { + if (path.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || + path.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) + { + typeCode = "WebPageView"; + } + else + { + string ext = System.IO.Path.GetExtension(path); + typeCode = ext == null ? "" : ext.TrimStart('.'); + } + } + + return new UiObjProps() + { + Path = path, + TypeCode = typeCode, + BaseUiType = baseUiType + }; + } + } +} diff --git a/ScadaData/ScadaData/Data/Models/UserProps.cs b/ScadaData/ScadaData/Data/Models/UserProps.cs new file mode 100644 index 000000000..84203614b --- /dev/null +++ b/ScadaData/ScadaData/Data/Models/UserProps.cs @@ -0,0 +1,78 @@ +/* + * Copyright 2016 Mikhail Shiryaev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Product : Rapid SCADA + * Module : ScadaData + * Summary : User properties + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + */ + +using System; +using System.IO; + +namespace Scada.Data.Models +{ + /// + /// User properties + /// Свойства пользователя + /// + public class UserProps + { + /// + /// Конструктор + /// + public UserProps() + : this(0) + { + + } + + /// + /// Конструктор + /// + public UserProps(int userID) + { + UserID = userID; + UserName = ""; + RoleID = 0; + RoleName = ""; + } + + + /// + /// Получить или установить идентификатор пользователя + /// + public int UserID { get; set; } + + /// + /// Получить или установить имя пользователя + /// + public string UserName { get; set; } + + /// + /// Получить или установить идентификатор роли пользователя + /// + public int RoleID { get; set; } + + /// + /// Получить или установить наименование роли пользователя + /// + public string RoleName { get; set; } + } +} diff --git a/ScadaData/ScadaData/Data/Tables/Adapter.cs b/ScadaData/ScadaData/Data/Tables/Adapter.cs new file mode 100644 index 000000000..7e336c186 --- /dev/null +++ b/ScadaData/ScadaData/Data/Tables/Adapter.cs @@ -0,0 +1,170 @@ +/* + * Copyright 2016 Mikhail Shiryaev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Product : Rapid SCADA + * Module : ScadaData + * Summary : The base class for adapter + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + */ + +using System; +using System.Globalization; +using System.IO; + +namespace Scada.Data.Tables +{ + /// + /// The base class for adapter + /// Базовый класс адаптера + /// + public abstract class Adapter + { + /// + /// Директория таблицы срезов + /// + protected string directory; + /// + /// Входной и выходной поток + /// + protected Stream ioStream; + /// + /// Имя файла таблицы срезов + /// + protected string tableName; + /// + /// Полное имя файла таблицы срезов + /// + protected string fileName; + /// + /// Доступ к данным выполняется через файл на диске + /// + protected bool fileMode; + + + /// + /// Конструктор + /// + public Adapter() + { + directory = ""; + ioStream = null; + tableName = ""; + fileName = ""; + fileMode = true; + } + + + /// + /// Получить или установить директорию таблицы срезов + /// + public string Directory + { + get + { + return directory; + } + set + { + ioStream = null; + fileMode = true; + if (directory != value) + { + directory = value; + fileName = directory + tableName; + } + } + } + + /// + /// Получить или установить входной и выходной поток (вместо директории) + /// + public Stream Stream + { + get + { + return ioStream; + } + set + { + directory = ""; + ioStream = value; + fileName = tableName; + fileMode = false; + } + } + + /// + /// Получить или установить имя файла таблицы срезов + /// + public string TableName + { + get + { + return tableName; + } + set + { + if (tableName != value) + { + tableName = value; + fileName = directory + tableName; + } + } + } + + /// + /// Получить или установить полное имя файла таблицы срезов + /// + public string FileName + { + get + { + return fileName; + } + set + { + if (fileName != value) + { + directory = Path.GetDirectoryName(value); + ioStream = null; + tableName = Path.GetFileName(value); + fileName = value; + fileMode = true; + } + } + } + + + /// + /// Извлечь дату из имени файла таблицы срезов или событий (без директории) + /// + protected DateTime ExtractDate(string tableName) + { + try + { + return DateTime.ParseExact(tableName.Substring(1, 6), "yyMMdd", CultureInfo.InvariantCulture); + } + catch + { + return DateTime.MinValue; + } + } + + } +} diff --git a/ScadaData/ScadaData/Data/BaseAdapter.cs b/ScadaData/ScadaData/Data/Tables/BaseAdapter.cs similarity index 86% rename from ScadaData/ScadaData/Data/BaseAdapter.cs rename to ScadaData/ScadaData/Data/Tables/BaseAdapter.cs index 904f3fadd..394fc053e 100644 --- a/ScadaData/ScadaData/Data/BaseAdapter.cs +++ b/ScadaData/ScadaData/Data/Tables/BaseAdapter.cs @@ -62,13 +62,13 @@ using System.Data; using System.Text; -namespace Scada.Data +namespace Scada.Data.Tables { /// /// Adapter for reading and writing configuration database tables /// Адаптер для чтения и записи таблиц базы конфигурации /// - public class BaseAdapter + public class BaseAdapter : Adapter { /// /// Определение поля таблицы @@ -145,119 +145,13 @@ protected struct DataTypes /// protected static readonly int MaxStringLen = Encoding.UTF8.GetMaxCharCount(MaxStringDataSize); - /// - /// Директория базы конфигурации - /// - protected string directory; - /// - /// Входной и выходной поток - /// - protected Stream ioStream; - /// - /// Имя файла таблицы базы конфигурации - /// - protected string tableName; - /// - /// Полное имя файла таблицы базы конфигурации - /// - protected string fileName; - /// - /// Доступ к данным выполняется через файл на диске - /// - protected bool fileMode; - /// /// Конструктор /// public BaseAdapter() + : base() { - directory = ""; - ioStream = null; - tableName = ""; - fileName = ""; - fileMode = true; - } - - - /// - /// Получить или установить директорию базы конфигурации - /// - public string Directory - { - get - { - return directory; - } - set - { - ioStream = null; - fileMode = true; - if (directory != value) - { - directory = value; - fileName = directory + tableName; - } - } - } - - /// - /// Получить или установить входной и выходной поток (вместо директории) - /// - public Stream Stream - { - get - { - return ioStream; - } - set - { - directory = ""; - ioStream = value; - fileName = tableName; - fileMode = false; - } - } - - /// - /// Получить или установить имя файла таблицы базы конфигурации - /// - public string TableName - { - get - { - return tableName; - } - set - { - if (tableName != value) - { - tableName = value; - fileName = directory + tableName; - } - } - } - - /// - /// Получить или установить полное имя файла таблицы базы конфигурации - /// - public string FileName - { - get - { - return fileName; - } - set - { - if (fileName != value) - { - directory = Path.GetDirectoryName(value); - ioStream = null; - tableName = Path.GetFileName(value); - fileName = value; - fileMode = true; - } - } } @@ -275,7 +169,7 @@ protected static object BytesToObj(byte[] bytes, int index, int dataType) case DataTypes.Boolean: return bytes[index] > 0; case DataTypes.DateTime: - return Arithmetic.DecodeDateTime(BitConverter.ToDouble(bytes, index)); + return ScadaUtils.DecodeDateTime(BitConverter.ToDouble(bytes, index)); case DataTypes.String: int strDataSize = BitConverter.ToUInt16(bytes, index); index += 2; @@ -582,7 +476,7 @@ public void Update(DataTable dataTable) rowBuf[bufInd] = (byte)(isNull ? 0 : (bool)val ? 1 : 0); break; case DataTypes.DateTime: - double dtVal = isNull ? 0.0 : Arithmetic.EncodeDateTime((DateTime)val); + double dtVal = isNull ? 0.0 : ScadaUtils.EncodeDateTime((DateTime)val); Array.Copy(BitConverter.GetBytes(dtVal), 0, rowBuf, bufInd, fieldDef.DataSize); break; default: diff --git a/ScadaData/ScadaData/Data/BaseTables.cs b/ScadaData/ScadaData/Data/Tables/BaseTables.cs similarity index 83% rename from ScadaData/ScadaData/Data/BaseTables.cs rename to ScadaData/ScadaData/Data/Tables/BaseTables.cs index e71eb75e9..419d26b6e 100644 --- a/ScadaData/ScadaData/Data/BaseTables.cs +++ b/ScadaData/ScadaData/Data/Tables/BaseTables.cs @@ -26,12 +26,15 @@ using System; using System.Data; -namespace Scada.Data +namespace Scada.Data.Tables { /// /// The tables of the configuration database /// Таблицы базы конфигурации /// + /// + /// After using DataTable.DefaultView.RowFilter restore the empty value + /// После использования DataTable.DefaultView.RowFilter нужно вернуть пустое значение public class BaseTables { /// @@ -59,6 +62,8 @@ public BaseTables() FormatTable = new DataTable("Format"), FormulaTable = new DataTable("Formula") }; + + BaseAge = DateTime.MinValue; } @@ -152,6 +157,22 @@ public BaseTables() /// public DataTable[] AllTables { get; protected set; } + /// + /// Получить или установить время последнего изменения успешно считанной базы конфигурации + /// + public DateTime BaseAge { get; set; } + + /// + /// Получить объект для синхронизации доступа к таблицам + /// + public object SyncRoot + { + get + { + return this; + } + } + /// /// Получить имя файла таблицы без директории @@ -165,9 +186,9 @@ public static string GetFileName(DataTable dataTable) } /// - /// Проверить, что таблица не пуста + /// Проверить, что колонки таблицы существуют /// - public static bool CheckIsNotEmpty(DataTable dataTable, bool throwOnEmpty = false) + public static bool CheckColumnsExist(DataTable dataTable, bool throwOnFail = false) { if (dataTable == null) throw new ArgumentNullException("dataTable"); @@ -176,11 +197,11 @@ public static bool CheckIsNotEmpty(DataTable dataTable, bool throwOnEmpty = fals { return true; } - else if (throwOnEmpty) + else if (throwOnFail) { throw new ScadaException(string.Format(Localization.UseRussian ? - "Таблица [{0}] пуста." : - "The table [{0}] is empty.", dataTable.TableName)); + "Таблица [{0}] не содержит колонок." : + "The table [{0}] does not contain columns.", dataTable.TableName)); } else { diff --git a/ScadaData/ScadaData/Data/BaseValues.cs b/ScadaData/ScadaData/Data/Tables/BaseValues.cs similarity index 79% rename from ScadaData/ScadaData/Data/BaseValues.cs rename to ScadaData/ScadaData/Data/Tables/BaseValues.cs index 54c72ccc8..7ba9029c1 100644 --- a/ScadaData/ScadaData/Data/BaseValues.cs +++ b/ScadaData/ScadaData/Data/Tables/BaseValues.cs @@ -27,13 +27,13 @@ using System.Collections.Generic; using System.Text; -namespace Scada.Data +namespace Scada.Data.Tables { /// /// The main values from the configuration database /// Основные значения из базы конфигурации /// - public class BaseValues + public static class BaseValues { /// /// Роли пользователей @@ -83,7 +83,7 @@ public static string GetRoleName(int roleID) return Localization.UseRussian ? "Гость" : "Guest"; else if (roleID == App) return Localization.UseRussian ? "Приложение" : "Application"; - if (Custom <= roleID && roleID < Err) + else if (Custom <= roleID && roleID < Err) return Localization.UseRussian ? "Настраиваемая роль" : "Custom role"; else if (roleID == Err) return Localization.UseRussian ? "Ошибка" : "Error"; @@ -276,6 +276,89 @@ public static class CnlStatuses public const int Alarm = 114; } + /// + /// Наименования размерностей + /// + public static class UnitNames + { + /// + /// Откл - Вкл + /// + public static string OffOn; + /// + /// Нет - Есть + /// + public static string NoYes; + /// + /// Шт. + /// + public static string Pcs; + + /// + /// Статический конструктор + /// + static UnitNames() + { + if (Localization.UseRussian) + { + OffOn = "Откл - Вкл"; + NoYes = "Нет - Есть"; + Pcs = "шт."; + } + else + { + OffOn = "Off - On"; + NoYes = "No - Yes"; + Pcs = "pcs."; + } + } + } + + /// + /// Наименования значений команд + /// + public static class CmdValNames + { + /// + /// Откл + /// + public static string Off; + /// + /// Вкл + /// + public static string On; + /// + /// Откл - Вкл + /// + public static string OffOn; + /// + /// Выполнить + /// + public static string Execute; + + /// + /// Статический конструктор + /// + static CmdValNames() + { + if (Localization.UseRussian) + { + Off = "Откл"; + On = "Вкл"; + OffOn = "Откл - Вкл"; + Execute = "Выполнить"; + } + else + { + Off = "Off"; + On = "On"; + OffOn = "Off - On"; + Execute = "Execute"; + } + } + } + + /// /// Идентификатор пустых или неопределённых данных /// diff --git a/ScadaData/ScadaData/Data/EventAdapter.cs b/ScadaData/ScadaData/Data/Tables/EventAdapter.cs similarity index 86% rename from ScadaData/ScadaData/Data/EventAdapter.cs rename to ScadaData/ScadaData/Data/Tables/EventAdapter.cs index 00a55ad1f..ce4e3a33e 100644 --- a/ScadaData/ScadaData/Data/EventAdapter.cs +++ b/ScadaData/ScadaData/Data/Tables/EventAdapter.cs @@ -48,13 +48,13 @@ using System.IO; using System.Text; -namespace Scada.Data +namespace Scada.Data.Tables { /// /// Adapter for reading and writing event tables /// Адаптер для чтения и записи таблиц событий /// - public class EventAdapter + public class EventAdapter : Adapter { /// /// Размер данных события в файле @@ -69,119 +69,13 @@ public class EventAdapter /// public const int MaxDataLen = 50; - /// - /// Директория таблицы событий - /// - protected string directory; - /// - /// Входной и выходной поток - /// - protected Stream ioStream; - /// - /// Имя файла таблицы событий - /// - protected string tableName; - /// - /// Полное имя файла таблицы событий - /// - protected string fileName; - /// - /// Доступ к данным выполняется через файл на диске - /// - protected bool fileMode; - /// /// Конструктор /// public EventAdapter() + : base() { - directory = ""; - ioStream = null; - tableName = ""; - fileName = ""; - fileMode = true; - } - - - /// - /// Получить или установить директорию таблицы событий - /// - public string Directory - { - get - { - return directory; - } - set - { - ioStream = null; - fileMode = true; - if (directory != value) - { - directory = value; - fileName = directory + tableName; - } - } - } - - /// - /// Получить или установить входной и выходной поток (вместо директории) - /// - public Stream Stream - { - get - { - return ioStream; - } - set - { - directory = ""; - ioStream = value; - fileName = tableName; - fileMode = false; - } - } - - /// - /// Получить или установить имя файла таблицы событий - /// - public string TableName - { - get - { - return tableName; - } - set - { - if (tableName != value) - { - tableName = value; - fileName = directory + tableName; - } - } - } - - /// - /// Получить или установить полное имя файла таблицы событий - /// - public string FileName - { - get - { - return fileName; - } - set - { - if (fileName != value) - { - directory = Path.GetDirectoryName(value); - ioStream = null; - tableName = Path.GetFileName(value); - fileName = value; - fileMode = true; - } - } } @@ -262,7 +156,7 @@ protected EventTableLight.Event CreateEvent(DataRowView rowView) protected byte[] CreateEventBuffer(EventTableLight.Event ev) { byte[] evBuf = new byte[EventDataSize]; - Array.Copy(BitConverter.GetBytes(Arithmetic.EncodeDateTime(ev.DateTime)), 0, evBuf, 0, 8); + Array.Copy(BitConverter.GetBytes(ScadaUtils.EncodeDateTime(ev.DateTime)), 0, evBuf, 0, 8); evBuf[8] = (byte)(ev.ObjNum % 256); evBuf[9] = (byte)(ev.ObjNum / 256); evBuf[10] = (byte)(ev.KPNum % 256); @@ -313,7 +207,7 @@ protected void FillObj(object dest) throw new ScadaException("Destination object is invalid."); // определение даты событий в таблице - DateTime date = Arithmetic.ExtractDate(tableName); + DateTime date = ExtractDate(tableName); // подготовка объекта для хранения данных if (eventTableLight != null) @@ -377,7 +271,7 @@ protected void FillObj(object dest) double time = BitConverter.ToDouble(eventBuf, 0); int hour, min, sec; - Arithmetic.DecodeTime(time, out hour, out min, out sec); + ScadaUtils.DecodeTime(time, out hour, out min, out sec); ev.DateTime = new DateTime(date.Year, date.Month, date.Day, hour, min, sec); ev.ObjNum = BitConverter.ToUInt16(eventBuf, 8); diff --git a/ScadaData/ScadaData/Data/EventTableLight.cs b/ScadaData/ScadaData/Data/Tables/EventTableLight.cs similarity index 75% rename from ScadaData/ScadaData/Data/EventTableLight.cs rename to ScadaData/ScadaData/Data/Tables/EventTableLight.cs index dfc5078f1..7a2457fe0 100644 --- a/ScadaData/ScadaData/Data/EventTableLight.cs +++ b/ScadaData/ScadaData/Data/Tables/EventTableLight.cs @@ -25,9 +25,8 @@ using System; using System.Collections.Generic; -using System.Text; -namespace Scada.Data +namespace Scada.Data.Tables { /// /// Event table for fast read data access @@ -120,7 +119,7 @@ public Event() } /// - /// Фильтры таблицы событий + /// Фильтры (типы фильтров) событий /// [Flags] public enum EventFilters @@ -147,6 +146,91 @@ public enum EventFilters Cnls = 8 } + /// + /// Фильтр событий + /// + public class EventFilter + { + /// + /// Конструктор + /// + public EventFilter() + : this(EventFilters.None) + { + } + /// + /// Конструктор + /// + public EventFilter(EventFilters filters) + { + Filters = filters; + ObjNum = 0; + KPNum = 0; + ParamID = 0; + CnlNums = null; + } + + /// + /// Получить или установить типы применяемых фильтров + /// + public EventFilters Filters { get; set; } + /// + /// Получить или установить номер объекта для фильтрации + /// + public int ObjNum { get; set; } + /// + /// Получить или установить номер КП для фильтрации + /// + public int KPNum { get; set; } + /// + /// Получить или установить ид. параметра для фильтрации + /// + public int ParamID { get; set; } + /// + /// Получить или установить номера входны каналов для фильтрации + /// + public ISet CnlNums { get; set; } + + /// + /// Проверить корректность фильтра + /// + public bool Check(bool throwOnFail = true) + { + if (Filters.HasFlag(EventFilters.Cnls) && CnlNums == null) + { + if (throwOnFail) + throw new ScadaException("Event filter is incorrect."); + else + return false; + } + else + { + return true; + } + } + /// + /// Проверить, что событие удовлетворяет фильтру + /// + public bool Satisfied(Event ev) + { + // если используется фильтр по номерам каналов, CnlNums должно быть не равно null + if (Filters == EventFilters.Cnls) + { + // быстрая проверка фильтра только по номерам каналов + return CnlNums.Contains(ev.CnlNum); + } + else + { + // полная проверка условий фильтра + return + (!Filters.HasFlag(EventFilters.Obj) || ObjNum == ev.ObjNum) && + (!Filters.HasFlag(EventFilters.KP) || KPNum == ev.KPNum) && + (!Filters.HasFlag(EventFilters.Param) || ParamID == ev.ParamID) && + (!Filters.HasFlag(EventFilters.Cnls) || CnlNums.Contains(ev.CnlNum)); + } + } + } + /// /// Имя таблицы /// @@ -297,6 +381,7 @@ public List AllEvents /// /// Получить отфильтрованный список событий /// + [Obsolete] public List FilteredEvents { get @@ -318,6 +403,7 @@ public List FilteredEvents /// /// Получить или установить фильтры таблицы событий /// + [Obsolete] public EventFilters Filters { get @@ -339,6 +425,7 @@ public EventFilters Filters /// /// Получить или установить номер объекта, по которому фильтруется таблица /// + [Obsolete] public int ObjNumFilter { get @@ -360,6 +447,7 @@ public int ObjNumFilter /// /// Получить или установить номер КП, по которому фильтруется таблица /// + [Obsolete] public int KPNumFilter { get @@ -381,6 +469,7 @@ public int KPNumFilter /// /// Получить или установить номер параметра, по которому фильтруется таблица /// + [Obsolete] public int ParamNumFilter { get @@ -402,6 +491,7 @@ public int ParamNumFilter /// /// Получить или установить список каналов, по которому фильтруется таблица /// + [Obsolete] public List CnlsFilter { get @@ -456,6 +546,7 @@ public void Clear() /// /// Получить часть отфильтрованного списка событий, начиная с заданного номера события /// + [Obsolete] public List GetEvents(int startEvNum) { if (eventsCache == null || this.startEvNum != startEvNum) @@ -481,6 +572,7 @@ public List GetEvents(int startEvNum) /// /// Получить конечную часть отфильтрованного списка событий /// + [Obsolete] public List GetLastEvents(int count) { if (lastEventsCache == null || lastEvCnt != count) @@ -517,5 +609,66 @@ public List GetLastEvents(int count) return lastEventsCache; } + + /// + /// Получить отфильтрованные события + /// + public List GetFilteredEvents(EventFilter filter) + { + bool reversed; + return GetFilteredEvents(filter, 0, 0, out reversed); + } + + /// + /// Получить отфильтрованные события в указанном диапазоне + /// + public List GetFilteredEvents(EventFilter filter, int lastCount, int startEvNum, out bool reversed) + { + if (filter == null) + throw new ArgumentNullException("filter"); + filter.Check(); + + reversed = false; + List filteredEvents = lastCount > 0 ? new List(lastCount) : new List(); + int startEvInd = Math.Max(0, startEvNum - 1); + int allEventsCnt = allEvents.Count; + + Action addEventAction = delegate(int i) + { + Event ev = allEvents[i]; + if (filter.Satisfied(ev)) + filteredEvents.Add(ev); + }; + + if (lastCount > 0) + { + for (int i = allEventsCnt - 1; i >= startEvInd && filteredEvents.Count < lastCount; i--) + addEventAction(i); + reversed = true; + } + else + { + for (int i = startEvInd; i < allEventsCnt; i++) + addEventAction(i); + } + + return filteredEvents; + } + + /// + /// Получить событие по номеру + /// + public Event GetEventByNum(int evNum) + { + if (1 <= evNum && evNum <= allEvents.Count) + { + Event ev = allEvents[evNum - 1]; + return ev.Number == evNum ? ev : null; + } + else + { + return null; + } + } } } diff --git a/ScadaData/ScadaData/Data/SrezAdapter.cs b/ScadaData/ScadaData/Data/Tables/SrezAdapter.cs similarity index 86% rename from ScadaData/ScadaData/Data/SrezAdapter.cs rename to ScadaData/ScadaData/Data/Tables/SrezAdapter.cs index b7bf016ab..6227cb764 100644 --- a/ScadaData/ScadaData/Data/SrezAdapter.cs +++ b/ScadaData/ScadaData/Data/Tables/SrezAdapter.cs @@ -38,17 +38,17 @@ */ using System; -using System.IO; using System.Data; +using System.IO; using System.Text; -namespace Scada.Data +namespace Scada.Data.Tables { /// /// Adapter for reading and writing data tables /// Адаптер для чтения и записи таблиц срезов /// - public class SrezAdapter + public class SrezAdapter : Adapter { /// /// Имя таблицы текущего среза @@ -59,119 +59,13 @@ public class SrezAdapter /// protected static readonly byte[] EmptyCnlNumsBuf = new byte[] { 0x00, 0x00, 0x01, 0x00 }; - /// - /// Директория таблицы срезов - /// - protected string directory; - /// - /// Входной и выходной поток - /// - protected Stream ioStream; - /// - /// Имя файла таблицы срезов - /// - protected string tableName; - /// - /// Полное имя файла таблицы срезов - /// - protected string fileName; - /// - /// Доступ к данным выполняется через файл на диске - /// - protected bool fileMode; - /// /// Конструктор /// public SrezAdapter() + : base() { - directory = ""; - ioStream = null; - tableName = ""; - fileName = ""; - fileMode = true; - } - - - /// - /// Получить или установить директорию таблицы срезов - /// - public string Directory - { - get - { - return directory; - } - set - { - ioStream = null; - fileMode = true; - if (directory != value) - { - directory = value; - fileName = directory + tableName; - } - } - } - - /// - /// Получить или установить входной и выходной поток (вместо директории) - /// - public Stream Stream - { - get - { - return ioStream; - } - set - { - directory = ""; - ioStream = value; - fileName = tableName; - fileMode = false; - } - } - - /// - /// Получить или установить имя файла таблицы срезов - /// - public string TableName - { - get - { - return tableName; - } - set - { - if (tableName != value) - { - tableName = value; - fileName = directory + tableName; - } - } - } - - /// - /// Получить или установить полное имя файла таблицы срезов - /// - public string FileName - { - get - { - return fileName; - } - set - { - if (fileName != value) - { - directory = Path.GetDirectoryName(value); - ioStream = null; - tableName = Path.GetFileName(value); - fileName = value; - fileMode = true; - } - } } @@ -291,7 +185,7 @@ protected void FillObj(object dest) ioStream; reader = new BinaryReader(stream); - DateTime date = Arithmetic.ExtractDate(tableName); // определение даты срезов + DateTime date = ExtractDate(tableName); // определение даты срезов SrezTable.SrezDescr srezDescr = null; // описание среза int[] cnlNums = null; // ссылка на номера входных каналов из описания среза while (stream.Position < stream.Length) @@ -339,7 +233,7 @@ protected void FillObj(object dest) long srezPos = stream.Position; double time = reader.ReadDouble(); int hour, min, sec; - Arithmetic.DecodeTime(time, out hour, out min, out sec); + ScadaUtils.DecodeTime(time, out hour, out min, out sec); DateTime srezDT = new DateTime(date.Year, date.Month, date.Day, hour, min, sec); // инициализация нового среза @@ -525,7 +419,7 @@ public void Create(SrezTable.Srez srez, DateTime srezDT) writer = new BinaryWriter(stream); writer.Write(GetSrezDescrBuf(srez.SrezDescr)); - writer.Write(Arithmetic.EncodeDateTime(srezDT)); + writer.Write(ScadaUtils.EncodeDateTime(srezDT)); writer.Write(GetCnlDataBuf(srez.CnlData)); stream.SetLength(stream.Position); } @@ -605,7 +499,7 @@ public void Update(SrezTable srezTable) // запись данных среза srez.Position = stream.Position; - writer.Write(Arithmetic.EncodeDateTime(srez.DateTime)); + writer.Write(ScadaUtils.EncodeDateTime(srez.DateTime)); writer.Write(GetCnlDataBuf(srez.CnlData)); lastSrez = srez; } diff --git a/ScadaData/ScadaData/Data/SrezTable.cs b/ScadaData/ScadaData/Data/Tables/SrezTable.cs similarity index 99% rename from ScadaData/ScadaData/Data/SrezTable.cs rename to ScadaData/ScadaData/Data/Tables/SrezTable.cs index f2184ef69..ebf18781d 100644 --- a/ScadaData/ScadaData/Data/SrezTable.cs +++ b/ScadaData/ScadaData/Data/Tables/SrezTable.cs @@ -27,7 +27,7 @@ using System.Collections.Generic; using System.Data; -namespace Scada.Data +namespace Scada.Data.Tables { /// /// Snapshot table for read and write data access diff --git a/ScadaData/ScadaData/Data/SrezTableLight.cs b/ScadaData/ScadaData/Data/Tables/SrezTableLight.cs similarity index 91% rename from ScadaData/ScadaData/Data/SrezTableLight.cs rename to ScadaData/ScadaData/Data/Tables/SrezTableLight.cs index 7d387a0c8..b72135338 100644 --- a/ScadaData/ScadaData/Data/SrezTableLight.cs +++ b/ScadaData/ScadaData/Data/Tables/SrezTableLight.cs @@ -26,7 +26,7 @@ using System; using System.Collections.Generic; -namespace Scada.Data +namespace Scada.Data.Tables { /// /// Snapshot table for fast read data access @@ -131,6 +131,26 @@ public bool GetCnlData(int cnlNum, out CnlData cnlData) } } /// + /// Получить данные входного канала по номеру + /// + public CnlData GetCnlData(int cnlNum) + { + CnlData cnlData; + GetCnlData(cnlNum, out cnlData); + return cnlData; + } + /// + /// Получить значение и статус входного канала по номеру + /// + public bool GetCnlData(int cnlNum, out double val, out int stat) + { + CnlData cnlData; + bool result = GetCnlData(cnlNum, out cnlData); + val = cnlData.Val; + stat = cnlData.Stat; + return result; + } + /// /// Сравнить текущий объект с другим объектом такого же типа /// public int CompareTo(Srez other) diff --git a/ScadaData/ScadaData/Data/Trend.cs b/ScadaData/ScadaData/Data/Tables/Trend.cs similarity index 99% rename from ScadaData/ScadaData/Data/Trend.cs rename to ScadaData/ScadaData/Data/Tables/Trend.cs index 2304987a1..4ef69995d 100644 --- a/ScadaData/ScadaData/Data/Trend.cs +++ b/ScadaData/ScadaData/Data/Tables/Trend.cs @@ -26,7 +26,7 @@ using System; using System.Collections.Generic; -namespace Scada.Data +namespace Scada.Data.Tables { /// /// Trend for fast reading one input channel data diff --git a/ScadaData/ScadaData/Lang/ScadaData.en-GB.xml b/ScadaData/ScadaData/Lang/ScadaData.en-GB.xml index af5a8b627..7bf4ccae2 100644 --- a/ScadaData/ScadaData/Lang/ScadaData.en-GB.xml +++ b/ScadaData/ScadaData/Lang/ScadaData.en-GB.xml @@ -36,6 +36,8 @@ Incorrect value of XML attribute "{0}". Incorrect value of the parameter "{0}". XML node "{0}" not found within the node "{1}". + Yes + No Command types Command values Channel types @@ -67,4 +69,27 @@ Event check command sent successfully. Command sent successfully. + + String is not hexadecimal. + Computer code contains error record. + Error decoding computer code + Error retrieving registration key info + Registration key length is incorrect. + Registration key info is incorrect. + Registration key is valid + Registration key is valid. Expiration date is {0} + Registration key is not valid + Registration key is expired {0} + Registration key is empty + Registration key contains error record + Registration key is incorrect + Registration key file {0} not found. + Error loading computer code + Error saving computer code + Error loading registration key + Error saving registration key + Check "{0}" registration: + Computer code: {0} + Registration failed. + \ No newline at end of file diff --git a/ScadaData/ScadaData/Lang/ScadaData.ru-RU.xml b/ScadaData/ScadaData/Lang/ScadaData.ru-RU.xml new file mode 100644 index 000000000..214f194fd --- /dev/null +++ b/ScadaData/ScadaData/Lang/ScadaData.ru-RU.xml @@ -0,0 +1,95 @@ + + + + Информация + Вопрос + Ошибка + Предупреждение + Ошибка: + Необработанное исключение + Настройки были изменены. Сохранить изменения? + Не найден файл. + Директория не существует. + Файл {0} не найден. + Директория {0} не существует. + Директория базы конфигурации в формате DAT + Директория базы конфигурации в формате DAT не существует. + Выберите директорию базы конфигурации в формате DAT + Ошибка при загрузке настроек приложения + Ошибка при сохранении настроек приложения + Ошибка при загрузке настроек соединения с сервером + Ошибка при сохранении настроек соединения с сервером + Ошибка при работе с данными + Требуется целое число + Требуется целое число в диапазоне от {0} до {1}. + Требуется вещественное число. + Требуется непустое значение. + Требуется дата и время. + Длина строки должна быть не более {0} символов. + "{0}" не является числом. + Ошибка при загрузке изображения из файла: {0} + Ошибка при загрузке гиперссылки из файла: {0} + Некорректный формат файла. + Нет данных + Недостаточно прав. + Некорректное значение XML-узла "{0}". + Некорректное значение XML-атрибута "{0}". + Некорректное значение параметра "{0}". + XML-узел "{0}" не найден внутри узла "{1}". + Да + Нет + Типы команд + Значения команд + Типы каналов + Линии связи + Каналы управления + Типы событий + Форматы чисел + Формулы + Входные каналы + Интерфейс + КП + Типы КП + Объекты + Величины + Права + Роли + Размерности + Пользователи + ожидание продолжения + пауза + ожидание паузы + работает + ожидание запуска + остановлена + ожидание остановки + не установлена + Данные отправлены успешно. + Событие отправлено успешно. + Команда квитирования события отправлена успешно. + Команда отправлена успешно. + + + Строка не является 16-ричной записью. + Код компьютера содержит запись об ошибке. + Ошибка при расшифровке кода компьютера + Ошибка при извлечении информации регистрационного ключа + Некорректная длина регистрационного ключа. + Некорректная информация регистрационного ключа. + Регистрационный ключ действителен + Регистрационный ключ действителен. Дата окончания {0} + Регистрационный ключ не действителен + Регистрационный ключ истёк {0} + Регистрационный ключ пуст + Регистрационный ключ содержит запись об ошибке + Регистрационный ключ некорректный + Файл регистрационного ключа {0} не найден. + Ошибка при загрузке кода компьютера + Ошибка при сохранении кода компьютера + Ошибка при загрузке регистрационного ключа + Ошибка при сохранении регистрационного ключа + Проверка регистрации "{0}": + Код компьютера: {0} + Ошибка регистрации. + + \ No newline at end of file diff --git a/ScadaData/ScadaData/Localization.cs b/ScadaData/ScadaData/Localization.cs index 7f6551dcb..1043903c3 100644 --- a/ScadaData/ScadaData/Localization.cs +++ b/ScadaData/ScadaData/Localization.cs @@ -89,9 +89,7 @@ public string GetPhrase(string key, string defaultVal) /// public static string GetEmptyPhrase(string key) { - return string.Format(UseRussian ? - "Фраза с ключом {0} не загружена." : - "The phrase with the key {0} is not loaded.", key); + return "[" + key + "]"; } } @@ -102,7 +100,7 @@ public static string GetEmptyPhrase(string key) static Localization() { InitDefaultCulture(); - ReadCulture(); + SetCulture(ReadCulture()); Dictionaries = new Dictionary(); } @@ -163,21 +161,35 @@ private static void InitDefaultCulture() } /// - /// Считать информацию о культуре из реестра + /// Считать наименование культуры из реестра /// - private static void ReadCulture() + private static string ReadCulture() { try { using (RegistryKey key = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64) .OpenSubKey("Software\\SCADA", false)) { - string cultureName = key.GetValue("Culture").ToString(); - Culture = string.IsNullOrEmpty(cultureName) ? - DefaultCulture : CultureInfo.GetCultureInfo(cultureName); + return key.GetValue("Culture").ToString(); } } catch + { + return ""; + } + } + + /// + /// Установить культуру + /// + public static void SetCulture(string cultureName) + { + try + { + Culture = string.IsNullOrEmpty(cultureName) ? + DefaultCulture : CultureInfo.GetCultureInfo(cultureName); + } + catch { Culture = DefaultCulture; } @@ -197,7 +209,17 @@ private static bool CultureIsRussian(CultureInfo cultureInfo) /// - /// Записать информацию о культуре в реестр + /// Изменить культуру + /// + public static void ChangeCulture(string cultureName) + { + if (string.IsNullOrEmpty(cultureName)) + cultureName = ReadCulture(); + SetCulture(cultureName); + } + + /// + /// Записать наименование культуры в реестр /// public static bool WriteCulture(string cultureName, out string errMsg) { @@ -297,9 +319,9 @@ public static bool LoadDictionaries(string fileName, out string errMsg) } catch (Exception ex) { - errMsg = (UseRussian ? - "Ошибка при загрузке словарей: " : - "Error loading dictionaries: ") + ex.Message; + errMsg = string.Format(UseRussian ? + "Ошибка при загрузке словарей из файла {0}: {1}" : + "Error loading dictionaries from file {0}: {1}", fileName, ex.Message); return false; } } @@ -322,6 +344,7 @@ public static bool LoadingRequired(string directory, string fileNamePrefix) return !UseRussian || File.Exists(GetDictionaryFileName(directory, fileNamePrefix)); } + /// /// Преобразовать дату и время в строку в соответствии с культурой SCADA /// @@ -329,5 +352,21 @@ public static string ToLocalizedString(this DateTime dateTime) { return dateTime.ToString("d", Culture) + " " + dateTime.ToString("T", Culture); } + + /// + /// Преобразовать дату в строку в соответствии с культурой SCADA + /// + public static string ToLocalizedDateString(this DateTime dateTime) + { + return dateTime.ToString("d", Culture); + } + + /// + /// Преобразовать время в строку в соответствии с культурой SCADA + /// + public static string ToLocalizedTimeString(this DateTime dateTime) + { + return dateTime.ToString("T", Culture); + } } } \ No newline at end of file diff --git a/ScadaData/ScadaData/Properties/AssemblyInfo.cs b/ScadaData/ScadaData/Properties/AssemblyInfo.cs index f49737cf8..888435def 100644 --- a/ScadaData/ScadaData/Properties/AssemblyInfo.cs +++ b/ScadaData/ScadaData/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Rapid SCADA")] -[assembly: AssemblyCopyright("Copyright © 2005-2014")] +[assembly: AssemblyCopyright("Copyright © 2005-2016")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -31,5 +31,5 @@ // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("4.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] +[assembly: AssemblyVersion("5.0.0.0")] +[assembly: AssemblyFileVersion("5.0.0.0")] diff --git a/ScadaData/ScadaData/ScadaData.csproj b/ScadaData/ScadaData/ScadaData.csproj index 816b626cf..7b3f75cc2 100644 --- a/ScadaData/ScadaData/ScadaData.csproj +++ b/ScadaData/ScadaData/ScadaData.csproj @@ -51,34 +51,39 @@ + - + + + + + - + - + - + + - - - + + - - + + - + - - - - + + + + @@ -88,10 +93,14 @@ + + Designer + Designer + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 - - - 17, 56 - - - 17, 17 - - - False - - \ No newline at end of file diff --git a/ScadaServer/ScadaServerSvc/MainLogic.Types.cs b/ScadaServer/ScadaServerSvc/MainLogic.Types.cs index 0c3af874e..5fb434652 100644 --- a/ScadaServer/ScadaServerSvc/MainLogic.Types.cs +++ b/ScadaServer/ScadaServerSvc/MainLogic.Types.cs @@ -25,7 +25,8 @@ using System; using System.IO; -using Scada.Data; +using Scada.Data.Models; +using Scada.Data.Tables; namespace Scada.Server.Svc { diff --git a/ScadaServer/ScadaServerSvc/MainLogic.cs b/ScadaServer/ScadaServerSvc/MainLogic.cs index 7d12c9de7..64fa04f2a 100644 --- a/ScadaServer/ScadaServerSvc/MainLogic.cs +++ b/ScadaServer/ScadaServerSvc/MainLogic.cs @@ -1,5 +1,5 @@ /* - * Copyright 2015 Mikhail Shiryaev + * Copyright 2016 Mikhail Shiryaev * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,9 +20,12 @@ * * Author : Mikhail Shiryaev * Created : 2013 - * Modified : 2015 + * Modified : 2016 */ +using Scada.Data.Models; +using Scada.Data.Tables; +using Scada.Server.Modules; using System; using System.Collections.Generic; using System.Data; @@ -30,9 +33,6 @@ using System.Reflection; using System.Text; using System.Threading; -using Microsoft.Win32; -using Scada.Data; -using Scada.Server.Modules; using Utils; namespace Scada.Server.Svc @@ -84,15 +84,15 @@ static WorkStateNames() /// /// Старший байт номера версии приложения /// - public const byte AppVersionHi = 4; + public const byte AppVersionHi = 5; /// /// Младший байт номера версии приложения /// - public const byte AppVersionLo = 5; + public const byte AppVersionLo = 0; /// /// Строковая запись версии приложения /// - public const string AppVersion = "4.5.0.5"; + public const string AppVersion = "5.0.0.0"; /// /// Имя файла журнала приложения /// diff --git a/ScadaServer/ScadaServerSvc/Properties/AssemblyInfo.cs b/ScadaServer/ScadaServerSvc/Properties/AssemblyInfo.cs index b333b1548..ee66e2c9e 100644 --- a/ScadaServer/ScadaServerSvc/Properties/AssemblyInfo.cs +++ b/ScadaServer/ScadaServerSvc/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Rapid SCADA")] -[assembly: AssemblyCopyright("Copyright © 2004-2014")] +[assembly: AssemblyCopyright("Copyright © 2004-2016")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -32,5 +32,5 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("4.0.0.0")] -[assembly: AssemblyFileVersion("4.0.0.0")] \ No newline at end of file +[assembly: AssemblyVersion("5.0.0.0")] +[assembly: AssemblyFileVersion("5.0.0.0")] \ No newline at end of file diff --git a/ScadaServer/ScadaServerSvc/ScadaServerSvc.csproj b/ScadaServer/ScadaServerSvc/ScadaServerSvc.csproj index 91ed41fa7..39c8530d1 100644 --- a/ScadaServer/ScadaServerSvc/ScadaServerSvc.csproj +++ b/ScadaServer/ScadaServerSvc/ScadaServerSvc.csproj @@ -57,6 +57,9 @@ False ..\..\ScadaData\ScadaData\bin\Release\ScadaData.dll + + ..\..\ScadaData\ScadaData.Svc\bin\Release\ScadaData.Svc.dll + @@ -68,15 +71,12 @@ - - Component - - - InstMain.cs - + + Component + Component @@ -87,9 +87,6 @@ - - InstMain.cs - SvcMain.cs diff --git a/ScadaServer/ScadaServerSvc/InstMain.cs b/ScadaServer/ScadaServerSvc/SvcInstaller.cs similarity index 68% rename from ScadaServer/ScadaServerSvc/InstMain.cs rename to ScadaServer/ScadaServerSvc/SvcInstaller.cs index f4fffe57d..6561cc8a4 100644 --- a/ScadaServer/ScadaServerSvc/InstMain.cs +++ b/ScadaServer/ScadaServerSvc/SvcInstaller.cs @@ -1,5 +1,5 @@ /* - * Copyright 2014 Mikhail Shiryaev + * Copyright 2016 Mikhail Shiryaev * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,11 +19,11 @@ * Summary : Service installer * * Author : Mikhail Shiryaev - * Created : 2013 - * Modified : 2013 + * Created : 2016 + * Modified : 2016 */ -using System; +using Scada.Svc; using System.ComponentModel; namespace Scada.Server.Svc @@ -33,11 +33,16 @@ namespace Scada.Server.Svc /// Инсталлятор службы /// [RunInstaller(true)] - public partial class InstMain : System.Configuration.Install.Installer + public class SvcInstaller : BaseSvcInstaller { - public InstMain() + /// + /// Конструктор + /// + public SvcInstaller() { - InitializeComponent(); + Init( + "ScadaServerService", + "SCADA-Server manages the archive database, performs mathematical calculations and provides information"); } } } diff --git a/ScadaWeb/OpenPlugins/OpenPlugins.sln b/ScadaWeb/OpenPlugins/OpenPlugins.sln index 5215742bb..6182c1fed 100644 --- a/ScadaWeb/OpenPlugins/OpenPlugins.sln +++ b/ScadaWeb/OpenPlugins/OpenPlugins.sln @@ -9,6 +9,16 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlgSchemeCommon", "PlgSchem EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlgConfig", "PlgConfig\PlgConfig.csproj", "{BA16503A-0E79-42C1-94C7-F690A0DA145E}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlgTable", "PlgTable\PlgTable.csproj", "{7AE30E67-D10E-44B9-AC7A-81E21C8839E8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlgChart", "PlgChart\PlgChart.csproj", "{72AFAED2-953D-4939-A828-8EC8A63AB19F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlgWebPage", "PlgWebPage\PlgWebPage.csproj", "{CC838844-D847-4230-B0B8-1747F4CA080B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlgChartCommon", "PlgChartCommon\PlgChartCommon.csproj", "{827AC062-9C5E-429F-ABBF-368E84CEC9B7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlgMonitor", "PlgMonitor\PlgMonitor.csproj", "{3A48FD69-7F01-4562-B9D7-E4984DA03BA9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -27,6 +37,26 @@ Global {BA16503A-0E79-42C1-94C7-F690A0DA145E}.Debug|Any CPU.Build.0 = Debug|Any CPU {BA16503A-0E79-42C1-94C7-F690A0DA145E}.Release|Any CPU.ActiveCfg = Release|Any CPU {BA16503A-0E79-42C1-94C7-F690A0DA145E}.Release|Any CPU.Build.0 = Release|Any CPU + {7AE30E67-D10E-44B9-AC7A-81E21C8839E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7AE30E67-D10E-44B9-AC7A-81E21C8839E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7AE30E67-D10E-44B9-AC7A-81E21C8839E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7AE30E67-D10E-44B9-AC7A-81E21C8839E8}.Release|Any CPU.Build.0 = Release|Any CPU + {72AFAED2-953D-4939-A828-8EC8A63AB19F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {72AFAED2-953D-4939-A828-8EC8A63AB19F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {72AFAED2-953D-4939-A828-8EC8A63AB19F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {72AFAED2-953D-4939-A828-8EC8A63AB19F}.Release|Any CPU.Build.0 = Release|Any CPU + {CC838844-D847-4230-B0B8-1747F4CA080B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CC838844-D847-4230-B0B8-1747F4CA080B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC838844-D847-4230-B0B8-1747F4CA080B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CC838844-D847-4230-B0B8-1747F4CA080B}.Release|Any CPU.Build.0 = Release|Any CPU + {827AC062-9C5E-429F-ABBF-368E84CEC9B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {827AC062-9C5E-429F-ABBF-368E84CEC9B7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {827AC062-9C5E-429F-ABBF-368E84CEC9B7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {827AC062-9C5E-429F-ABBF-368E84CEC9B7}.Release|Any CPU.Build.0 = Release|Any CPU + {3A48FD69-7F01-4562-B9D7-E4984DA03BA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3A48FD69-7F01-4562-B9D7-E4984DA03BA9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3A48FD69-7F01-4562-B9D7-E4984DA03BA9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3A48FD69-7F01-4562-B9D7-E4984DA03BA9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ScadaWeb/OpenPlugins/PlgChart/AppCode/Chart/MinDataRepBuilder.cs b/ScadaWeb/OpenPlugins/PlgChart/AppCode/Chart/MinDataRepBuilder.cs new file mode 100644 index 000000000..48948c7df --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/AppCode/Chart/MinDataRepBuilder.cs @@ -0,0 +1,421 @@ +/* + * Copyright 2016 Mikhail Shiryaev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Product : Rapid SCADA + * Module : PlgChart + * Summary : Minute data report builder + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + */ + +using Scada.Client; +using Scada.Data.Models; +using Scada.Data.Tables; +using System; +using Utils.Report; + +namespace Scada.Web.Plugins.Chart +{ + /// + /// Minute data report builder + /// Формирует отчёт по минутным данным + /// + internal class MinDataRepBuilder : ExcelRepBuilder + { + private readonly DataAccess dataAccess; // объект для доступа к данным + + private int[] cnlNums; // номера каналов, по которым строится отчёт + private DateTime startDate; // начальная дата данных отчёта + private int period; // период отображаемых данных + private string viewTitle; // заголовок представления + + private Row cnlRowTemplate; // строка-шаблон списка входных каналов + private Row dataHdrRow; // строка шапки таблицы минутных данных + private Row dateRowTemplate; // строка-шаблон даты в таблице минутных данных + private Row dataRowTemplate; // строка-шаблон данных в таблице минутных данных + private Cell valColCellTemplate; // ячейка-шаблон шапки столбца минутных данных + private Cell valCellTemplate; // ячейка-шаблон столбца минутных данных + + private bool procCnlRow; // производится обработка строки входного канала + private InCnlProps[] cnlPropsArr; // массив свойств входных каналов отчёта + private InCnlProps curCnlProps; // свойства текущего обрабатываемого входного канала + + /// + /// Конструктор, ограничивающий создание объекта без параметров + /// + protected MinDataRepBuilder() + { + } + + /// + /// Конструктор + /// + public MinDataRepBuilder(DataAccess dataAccess) + : base() + { + if (dataAccess == null) + throw new ArgumentNullException("dataAccess"); + + this.dataAccess = dataAccess; + + ClearRepParams(); + ClearXmlObjects(); + ClearDataObjects(); + } + + + /// + /// Очистить параметры отчёта + /// + private void ClearRepParams() + { + startDate = DateTime.MinValue; + period = 1; + cnlNums = null; + viewTitle = ""; + } + + /// + /// Очистить XML-объекты строк и ячеек + /// + private void ClearXmlObjects() + { + cnlRowTemplate = null; + dataHdrRow = null; + dateRowTemplate = null; + dataRowTemplate = null; + valColCellTemplate = null; + valCellTemplate = null; + } + + /// + /// Очистить объекты данных + /// + private void ClearDataObjects() + { + procCnlRow = false; + cnlPropsArr = null; + curCnlProps = null; + } + + /// + /// Создать и заполнить массив свойств входных каналов отчёта + /// + private void CreateCnlPropsArr() + { + int cnlCnt = cnlNums.Length; + cnlPropsArr = new InCnlProps[cnlCnt]; + + for (int i = 0; i < cnlCnt; i++) + { + int cnlNum = cnlNums[i]; + InCnlProps cnlProps = dataAccess.GetCnlProps(cnlNum); + cnlPropsArr[i] = cnlProps == null ? new InCnlProps() { CnlNum = cnlNum } : cnlProps; + } + } + + /// + /// Вывести в отчёт список входных каналов + /// + private void WriteCnlList(Table table, int cnlRowIndex) + { + try + { + procCnlRow = true; + + foreach (InCnlProps cnlProps in cnlPropsArr) + { + curCnlProps = cnlProps; + Row cnlRow = cnlRowTemplate.Clone(); + ExcelProc(cnlRow); + table.InsertRow(cnlRowIndex, cnlRow); + cnlRowIndex++; + } + } + finally + { + procCnlRow = false; + } + } + + /// + /// Добавить столбцы в таблицу минутных данных + /// + private void AddMinTableColumns(Table table) + { + Column colTemplate = table.Columns[1]; + int colIndex = colTemplate.Index + 1; + + for (int i = 1, cnlCnt = cnlNums.Length; i < cnlCnt; i++) + { + Column col = colTemplate.Clone(); + table.InsertColumn(colIndex, col); + colIndex++; + + Cell valColCell = valColCellTemplate.Clone(); + valColCell.DataNode.InnerText = string.Format(MinDataRepPhrases.ValColTitle, cnlNums[i]); + dataHdrRow.AppendCell(valColCell); + + Cell emptyCell = valCellTemplate.Clone(); + dateRowTemplate.AppendCell(emptyCell); + + Cell valCell = valCellTemplate.Clone(); + dataRowTemplate.AppendCell(valCell); + } + } + + /// + /// Вывести в отчёт минутные данные + /// + private void WriteMinData(Table table) + { + // получение трендов + int cnlCnt = cnlNums.Length; + Trend[] trends = new Trend[cnlCnt]; + Trend trend; + + for (int i = 0; i < cnlCnt; i++) + { + int cnlNum = cnlNums[i]; + + if (period == 1) + { + trend = dataAccess.DataCache.GetMinTrend(startDate, cnlNum); + } + else + { + trend = new Trend(cnlNum); + for (int d = 0; d < period; d++) + { + Trend dailyTrend = dataAccess.DataCache.GetMinTrend(startDate.AddDays(d), cnlNum); + trend.Points.AddRange(dailyTrend.Points); + } + } + + trends[i] = trend; + } + + // создание связки трендов + TrendBundle trendBundle = new TrendBundle(); + trendBundle.Init(trends); + + // вывод в отчёт + DataFormatter dataFormatter = new DataFormatter(); + DateTime prevDate = period > 1 ? DateTime.MinValue : DateTime.MaxValue /*не выводить даты*/; + + foreach (TrendBundle.Point point in trendBundle.Series) + { + DateTime pointDT = point.DateTime; + + // вывод строки с новой датой + if (prevDate < pointDT.Date) + { + prevDate = pointDT.Date; + Row dateRow = dateRowTemplate.Clone(); + dateRow.Cells[0].DataNode.InnerText = pointDT.ToLocalizedDateString(); + table.AppendRow(dateRow); + } + + // вывод строки с минутными данными + Row dataRow = dataRowTemplate.Clone(); + dataRow.Cells[0].DataNode.InnerText = pointDT.ToLocalizedTimeString(); + + for (int i = 0; i < cnlCnt; i++) + { + SrezTableLight.CnlData cnlData = point.CnlData[i]; + + string text; + string textWithUnit; + bool textIsNumber; + dataFormatter.FormatCnlVal(cnlData.Val, cnlData.Stat, cnlPropsArr[i], ".", "", + out text, out textWithUnit, out textIsNumber); + + Cell cell = dataRow.Cells[i + 1]; + cell.DataNode.InnerText = text; + if (textIsNumber) + cell.SetNumberType(); + } + + table.AppendRow(dataRow); + } + } + + + /// + /// Получить имя отчёта + /// + public override string RepName + { + get + { + return Localization.UseRussian ? + "Минутные данные" : + "Minute data"; + } + } + + /// + /// Получить имя файла шаблона + /// + public override string TemplateFileName + { + get + { + return "MinDataRep.xml"; + } + } + + + /// + /// Установить параметры отчёта. + /// repParams[0] - номера каналов, по которым строится отчёт, int[], + /// repParams[1] - начальная дата данных отчёта, DateTime, + /// repParams[2] - период отображаемых данных, int, + /// repParams[3] - заголовок представления, string + /// + public override void SetParams(params object[] repParams) + { + cnlNums = (int[])repParams[0]; + startDate = (DateTime)repParams[1]; + period = (int)repParams[2]; + viewTitle = (string)repParams[3]; + + RepUtils.NormalizeTimeRange(ref startDate, ref period); + } + + /// + /// Предварительно обработать дерево XML-документа + /// + protected override void StartXmlDocProc() + { + ClearXmlObjects(); + ClearDataObjects(); + } + + /// + /// Окончательно обработать дерево XML-документа + /// + protected override void FinalXmlDocProc() + { + // проверка шаблона + Table table = cnlRowTemplate == null ? null : cnlRowTemplate.ParentTable; + if (workbook.Worksheets.Count == 0 || + cnlRowTemplate == null || + dataHdrRow == null || dataHdrRow.Cells.Count < 2 || + dataRowTemplate == null || dataRowTemplate.Cells.Count < 2 || + table.Columns.Count < 2) + { + throw new Exception(WebPhrases.IncorrectRepTemplate); + } + + // перевод наименования листа + workbook.Worksheets[0].Name = MinDataRepPhrases.MinDataWorksheet; + + // удаление лишних атрибутов таблицы + table.RemoveTableNodeAttrs(); + + // удаление строк-шаблонов из таблицы + int cnlRowIndex = table.Rows.IndexOf(cnlRowTemplate); + table.RemoveRow(cnlRowIndex); + int dateRowIndex = table.Rows.IndexOf(dateRowTemplate); + table.RemoveRow(dateRowIndex); + int dataRowIndex = table.Rows.IndexOf(dataRowTemplate); + table.RemoveRow(dataRowIndex); + + // формирование отчёта + CreateCnlPropsArr(); + WriteCnlList(table, cnlRowIndex); + AddMinTableColumns(table); + WriteMinData(table); + workbook.Worksheets[0].SplitHorizontal(dataRowIndex + cnlNums.Length); + } + + /// + /// Обработать директиву, связанную со значением ячейки + /// + protected override void ProcVal(Cell cell, string valName) + { + string nodeText = null; + + if (valName == "Title") + { + string periodStr = startDate.ToLocalizedDateString() + + (period > 1 ? " - " + startDate.AddDays(period - 1).ToLocalizedDateString() : ""); + nodeText = string.IsNullOrEmpty(viewTitle) ? + periodStr : viewTitle + ", " + periodStr; + } + else if (valName == "Gen") + { + nodeText = MinDataRepPhrases.MinDataGen + DateTime.Now.ToLocalizedString(); + } + else if (valName == "CnlsCaption") + { + nodeText = MinDataRepPhrases.CnlsCaption; + } + else if (procCnlRow) + { + if (valName == "CnlNum") + { + nodeText = curCnlProps.CnlNum.ToString(); + cell.SetNumberType(); + } + else if (valName == "CnlName") + { + nodeText = curCnlProps.CnlName + + (curCnlProps.ShowNumber && curCnlProps.UnitArr != null && curCnlProps.UnitArr.Length == 1 ? + ", " + curCnlProps.UnitArr[0] : ""); + } + } + else if (valName == "TimeCol") + { + nodeText = MinDataRepPhrases.TimeColTitle; + dataHdrRow = cell.ParentRow; + } + else if (valName == "ValCol") + { + valColCellTemplate = cell; + nodeText = string.Format(MinDataRepPhrases.ValColTitle, cnlNums[0]); + } + else if (valName == "Date") + { + dateRowTemplate = cell.ParentRow; + } + else if (valName == "Time") + { + dataRowTemplate = cell.ParentRow; + } + else if (valName == "Val") + { + valCellTemplate = cell; + nodeText = ""; + } + + if (nodeText != null) + cell.DataNode.InnerText = nodeText; + } + + /// + /// Обработать директиву, связанную со строкой таблицы + /// + protected override void ProcRow(Cell cell, string rowName) + { + if (rowName == "CnlRow") + cnlRowTemplate = cell.ParentRow; + } + } +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/AppCode/Chart/MinDataRepPhrases.cs b/ScadaWeb/OpenPlugins/PlgChart/AppCode/Chart/MinDataRepPhrases.cs new file mode 100644 index 000000000..1cb7599a1 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/AppCode/Chart/MinDataRepPhrases.cs @@ -0,0 +1,70 @@ +/* + * Copyright 2016 Mikhail Shiryaev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Product : Rapid SCADA + * Module : PlgChart + * Summary : The phrases used by the minute data report + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + */ + +#pragma warning disable 1591 // отключение warning CS1591: Missing XML comment for publicly visible type or member + +namespace Scada.Web.Plugins.Chart +{ + /// + /// The phrases used by the minute data report + /// Фразы, используемые отчётом по минутным данным + /// + internal static class MinDataRepPhrases + { + static MinDataRepPhrases() + { + SetToDefault(); + } + + // Словарь Scada.Web.Plugins.Chart.MinDataRepBuilder + public static string MinDataWorksheet { get; private set; } + public static string MinDataGen { get; private set; } + public static string CnlsCaption { get; private set; } + public static string TimeColTitle { get; private set; } + public static string ValColTitle { get; private set; } + + private static void SetToDefault() + { + MinDataWorksheet = "MinDataWorksheet"; // GetEmptyPhrase() не удовлетворяет ограничениям Excel + MinDataGen = Localization.Dict.GetEmptyPhrase("MinDataGen"); + CnlsCaption = Localization.Dict.GetEmptyPhrase("CnlsCaption"); + TimeColTitle = Localization.Dict.GetEmptyPhrase("TimeColTitle"); + ValColTitle = Localization.Dict.GetEmptyPhrase("ValColTitle"); + } + + public static void Init() + { + Localization.Dict dict; + if (Localization.Dictionaries.TryGetValue("Scada.Web.Plugins.Chart.MinDataRepBuilder", out dict)) + { + MinDataWorksheet = dict.GetPhrase("MinDataWorksheet", MinDataWorksheet); + MinDataGen = dict.GetPhrase("MinDataGen", MinDataGen); + CnlsCaption = dict.GetPhrase("CnlsCaption", CnlsCaption); + TimeColTitle = dict.GetPhrase("TimeColTitle", TimeColTitle); + ValColTitle = dict.GetPhrase("ValColTitle", ValColTitle); + } + } + } +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/AppCode/Chart/MinDataRepSpec.cs b/ScadaWeb/OpenPlugins/PlgChart/AppCode/Chart/MinDataRepSpec.cs new file mode 100644 index 000000000..5446995b8 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/AppCode/Chart/MinDataRepSpec.cs @@ -0,0 +1,78 @@ +/* + * Copyright 2016 Mikhail Shiryaev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Product : Rapid SCADA + * Module : PlgChart + * Summary : Minute data report specification + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + */ + +namespace Scada.Web.Plugins.Chart +{ + /// + /// Minute data report specification + /// Спецификация отчёта по минутным данным + /// + public class MinDataRepSpec : ReportSpec + { + /// + /// Получить код типа отчёта + /// + public override string TypeCode + { + get + { + return "MinDataRep"; + } + } + + /// + /// Получить наименование отчёта + /// + public override string Name + { + get + { + return Localization.UseRussian ? + "Отчёт по минутным данным" : + "Minute data report"; + } + } + + /// + /// Получить признак, что отчёт доступен всем ролям и не требует назначения прав + /// + public override bool ForEveryone + { + get + { + return true; + } + } + + + /// + /// Получить ссылку на страницу отчёта + /// + public override string GetUrl(int reportID) + { + return "~/plugins/Chart/MinDataRep.aspx"; + } + } +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/AppCode/PlgChartSpec.cs b/ScadaWeb/OpenPlugins/PlgChart/AppCode/PlgChartSpec.cs new file mode 100644 index 000000000..32b22bf4f --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/AppCode/PlgChartSpec.cs @@ -0,0 +1,130 @@ +/* + * Copyright 2016 Mikhail Shiryaev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Product : Rapid SCADA + * Module : PlgChart + * Summary : Chart plugin specification + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + */ + +using Scada.Web.Plugins.Chart; +using Scada.Web.Shell; +using System.Collections.Generic; +using System.IO; + +namespace Scada.Web.Plugins +{ + /// + /// Chart plugin specification + /// Спецификация плагина графиков + /// + public class PlgChartSpec : PluginSpec + { + private DictUpdater dictUpdater; // объект для обновления словаря плагина + + + /// + /// Конструктор + /// + public PlgChartSpec() + : base() + { + dictUpdater = null; + } + + + /// + /// Получить наименование плагина + /// + public override string Name + { + get + { + return Localization.UseRussian ? + "Графики" : + "Chart"; + } + } + + /// + /// Получить описание плагина + /// + public override string Descr + { + get + { + return Localization.UseRussian ? + "Плагин обеспечивает отображение графиков." : + "The plugin provides displaying charts."; + } + } + + /// + /// Получить версию плагина + /// + public override string Version + { + get + { + return "1.0.0.0"; + } + } + + /// + /// Получить спецификации отчётов, которые реализуются плагином + /// + public override List ReportSpecs + { + get + { + return new List() { new MinDataRepSpec() }; + } + } + + /// + /// Получить пути к дополнительным скриптам, которые реализуются плагином + /// + public override ScriptPaths ScriptPaths + { + get + { + return new ScriptPaths() { ChartScriptPath = "~/plugins/Chart/js/chartdialog.js" }; + } + } + + + /// + /// Инициализировать плагин + /// + public override void Init() + { + dictUpdater = new DictUpdater( + string.Format("{0}Chart{1}lang{1}", AppDirs.PluginsDir, Path.DirectorySeparatorChar), + "PlgChart", () => { ChartPhrases.Init(); MinDataRepPhrases.Init(); }, Log); + } + + /// + /// Выполнить действия после успешного входа пользователя в систему + /// + public override void OnUserLogin(UserData userData) + { + dictUpdater.Update(); + } + } +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/MasterMain.Master b/ScadaWeb/OpenPlugins/PlgChart/MasterMain.Master new file mode 100644 index 000000000..56eafea94 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/MasterMain.Master @@ -0,0 +1,19 @@ +<%@ Master Language="C#" AutoEventWireup="true" %> + + + + + + Test Master Page + + + + + + + + + + + + diff --git a/ScadaWeb/OpenPlugins/PlgChart/PlgChart.csproj b/ScadaWeb/OpenPlugins/PlgChart/PlgChart.csproj new file mode 100644 index 000000000..5c76a3d82 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/PlgChart.csproj @@ -0,0 +1,213 @@ + + + + + Debug + AnyCPU + + + 2.0 + {72AFAED2-953D-4939-A828-8EC8A63AB19F} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + Scada.Web + PlgChart + v4.0 + true + + + + + + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + ..\..\..\Log\bin\Release\Log.dll + + + False + ..\..\..\Report\RepBuilder\bin\Release\RepBuilder.dll + + + ..\..\..\ScadaData\ScadaData\bin\Release\ScadaData.dll + + + ..\..\ScadaWebCommon5\bin\Release\ScadaWebCommon5.dll + + + + + + + + + + + + + + + + + + chart.less + + + chart.css + + + chartform.less + + + chartform.css + + + cnllist.less + + + cnllist.css + + + mindatarep.less + + + mindatarep.css + + + selectcnls.less + + + selectcnls.css + + + + + + + + + + + + + + + + + + + + + + Chart.aspx + ASPXCodeBehind + + + Chart.aspx + + + MinDataRep.aspx + ASPXCodeBehind + + + MinDataRep.aspx + + + MinDataRepOut.aspx + ASPXCodeBehind + + + MinDataRepOut.aspx + + + SelectCnls.aspx + ASPXCodeBehind + + + SelectCnls.aspx + + + + + + + + compilerconfig.json + + + + + + + + + + + + Web.config + + + Web.config + + + + + + + + + {827ac062-9c5e-429f-abbf-368e84cec9b7} + PlgChartCommon + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + + + + + True + True + 54466 + / + http://localhost:54466/ + False + False + + + False + + + + + + \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/Properties/AssemblyInfo.cs b/ScadaWeb/OpenPlugins/PlgChart/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..8299c2450 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PlgChart")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Rapid SCADA")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("72afaed2-953d-4939-a828-8ec8a63ab19f")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ScadaWeb/ScadaWebShell5Beta/Web.Debug.config b/ScadaWeb/OpenPlugins/PlgChart/Web.Debug.config similarity index 100% rename from ScadaWeb/ScadaWebShell5Beta/Web.Debug.config rename to ScadaWeb/OpenPlugins/PlgChart/Web.Debug.config diff --git a/ScadaWeb/ScadaWebShell5Beta/Web.Release.config b/ScadaWeb/OpenPlugins/PlgChart/Web.Release.config similarity index 100% rename from ScadaWeb/ScadaWebShell5Beta/Web.Release.config rename to ScadaWeb/OpenPlugins/PlgChart/Web.Release.config diff --git a/ScadaWeb/OpenPlugins/PlgChart/Web.config b/ScadaWeb/OpenPlugins/PlgChart/Web.config new file mode 100644 index 000000000..c72873f34 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/Web.config @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/ScadaWeb/OpenPlugins/PlgChart/compilerconfig.json b/ScadaWeb/OpenPlugins/PlgChart/compilerconfig.json new file mode 100644 index 000000000..85df34043 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/compilerconfig.json @@ -0,0 +1,22 @@ +[ + { + "outputFile": "plugins/Chart/css/chart.css", + "inputFile": "plugins/Chart/css/chart.less" + }, + { + "outputFile": "plugins/Chart/css/chartform.css", + "inputFile": "plugins/Chart/css/chartform.less" + }, + { + "outputFile": "plugins/Chart/css/mindatarep.css", + "inputFile": "plugins/Chart/css/mindatarep.less" + }, + { + "outputFile": "plugins/Chart/css/cnllist.css", + "inputFile": "plugins/Chart/css/cnllist.less" + }, + { + "outputFile": "plugins/Chart/css/selectcnls.css", + "inputFile": "plugins/Chart/css/selectcnls.less" + } +] \ No newline at end of file diff --git a/ScadaWeb/ScadaWebShell5Beta/compilerconfig.json.defaults b/ScadaWeb/OpenPlugins/PlgChart/compilerconfig.json.defaults similarity index 100% rename from ScadaWeb/ScadaWebShell5Beta/compilerconfig.json.defaults rename to ScadaWeb/OpenPlugins/PlgChart/compilerconfig.json.defaults diff --git a/ScadaWeb/ScadaWebShell5Beta/config/CommSettings.xml b/ScadaWeb/OpenPlugins/PlgChart/config/CommSettings.xml similarity index 100% rename from ScadaWeb/ScadaWebShell5Beta/config/CommSettings.xml rename to ScadaWeb/OpenPlugins/PlgChart/config/CommSettings.xml diff --git a/ScadaWeb/OpenPlugins/PlgChart/css/common/contentform.less b/ScadaWeb/OpenPlugins/PlgChart/css/common/contentform.less new file mode 100644 index 000000000..0463c8fd9 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/css/common/contentform.less @@ -0,0 +1,45 @@ +@import "globalvar.less"; + +.main-content { + padding: 20px; + font-size: 14px; +} + +h1 { + font-size: 23px; +} + +h2 { + font-size: 19px; +} + +h3 { + font-size: 15px; +} + +h1, h2, h3 { + font-weight: 500; + margin: 20px 0 10px; +} + +h1:first-of-type { + margin-top: 0; +} + +a, +a:active, +a:hover, +a:focus, +a:visited { + color: @link-fore-color; + outline: 0; + text-decoration: none; +} + +a:hover { + color: @link-hover-fore-color; +} + +label { + font-weight: 600; +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/css/common/globalvar.less b/ScadaWeb/OpenPlugins/PlgChart/css/common/globalvar.less new file mode 100644 index 000000000..4b1e60099 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/css/common/globalvar.less @@ -0,0 +1,29 @@ +@content-back-color: #f1f1f1; +@content-fore-color: #333; +@link-fore-color: #0073aa; +@link-hover-fore-color: #00a0d2; +@panel-border-color: #e5e5e5; +@popup-back-color: white; +@default-font-family: 'Open Sans', sans-serif; +@secondary-font-family: Arial, Helvetica, sans-serif; +@form-font-size: 13px; +@data-font-size: 12px; +@popup-min-width: 250px; + +@menu-back-color: #23282d; +@menu-back-color-air: white; +@menu-fore-color: #eee; +@menu-fore-color-dark: #9ca1a6; +@menu-fore-color-air: #444; +@menu-fore-color-air-light: #8f8f8f; +@menu-item-hover-back-color: #32373c; +@menu-item-hover-back-color-dark: #191e23; +@menu-item-hover-fore-color: #00b9eb; +@menu-item-selected-back-color: #0073aa; +@menu-item-selected-fore-color: white; +@menu-item-disabled-fore-color: @menu-fore-color-dark; +@menu-item-disabled-fore-color-air: #ccc; + +@splitter-color: #ddd; +@splitter-active-color: #ccc; +@splitter-width: 9px; \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/css/common/modalform.less b/ScadaWeb/OpenPlugins/PlgChart/css/common/modalform.less new file mode 100644 index 000000000..c34091cc0 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/css/common/modalform.less @@ -0,0 +1,20 @@ +@import "globalvar.less"; + +body { + margin: 0; + padding: 0; + background-color: @popup-back-color; + color: @content-fore-color; + font-family: @default-font-family; + font-size: @form-font-size; + min-width: @popup-min-width; + overflow: hidden; +} + +table { + border-collapse: collapse; +} + +label { + font-weight: 600; +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/js/api/clientapi.js b/ScadaWeb/OpenPlugins/PlgChart/js/api/clientapi.js new file mode 100644 index 000000000..2b23bf92e --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/js/api/clientapi.js @@ -0,0 +1,251 @@ +/* + * Rapid SCADA client API for access data and sending commands + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + * + * Requires: + * - jquery + * - utils.js + */ + +// Rapid SCADA namespace +var scada = scada || {}; + +/********** Input Channel Data **********/ + +// Input channel data type. +// Note: Casing is caused by C# naming rules +scada.CnlData = function () { + this.Val = 0.0; + this.Stat = 0; +}; + +// Extended input channel data type +scada.CnlDataExt = function () { + scada.CnlData.call(this); + + this.CnlNum = 0; + this.Text = ""; + this.TextWithUnit = ""; + this.Color = ""; +}; + +scada.CnlDataExt.prototype = Object.create(scada.CnlData.prototype); +scada.CnlDataExt.constructor = scada.CnlDataExt; + +// Hourly input channel data type +scada.HourCnlData = function () { + this.Hour = NaN; + this.Modified = false; + this.CnlDataExtArr = []; +} + +/********** Event **********/ + +// Event type +scada.Event = function () { + this.Num = 0; + this.Time = ""; + this.Obj = ""; + this.KP = ""; + this.Cnl = ""; + this.Text = ""; + this.Ack = ""; + this.Color = ""; + this.Sound = false; +}; + +/********** Auxiliary Request Parameters **********/ + +// Input channel filter type +scada.CnlFilter = function () { + // Filter by the explicitly specified input channel numbers. No other filtering is applied + this.cnlNums = []; + // Filter by input channels included in the view + this.viewID = 0; +}; + +// Convert the input channel filter to a query string +scada.CnlFilter.prototype.toQueryString = function () { + return "cnlNums=" + scada.utils.arrayToQueryParam(this.cnlNums) + + "&viewID=" + (this.viewID ? this.viewID : 0); +}; + +// Time period in hours type +scada.HourPeriod = function () { + // Date is a reference point of the period + this.date = 0; + // Start hour relative to the date. May be negative + this.startHour = 0; + // End hour relative to the date + this.endHour = 0; +}; + +// Convert the time period to a query string +scada.HourPeriod.prototype.toQueryString = function () { + return "year=" + this.date.getFullYear() + + "&month=" + (this.date.getMonth() + 1) + + "&day=" + this.date.getDate() + + "&startHour=" + this.startHour + + "&endHour=" + this.endHour; +}; + +// Hourly data selection modes enumeration +scada.HourDataModes = { + // Select data for integer hours even if a snapshot doesn't exist + INTEGER_HOURS: false, + // Select existing hourly snapshots + EXISTING: true +}; + +/********** Client API **********/ + +// Client API object +scada.clientAPI = { + // Empty input channel data + _EMPTY_CNL_DATA: Object.freeze(new scada.CnlData()), + + // Empty extended input channel data + _EMPTY_CNL_DATA_EXT: Object.freeze(new scada.CnlDataExt()), + + // Web service root path + rootPath: "", + + // Execute an AJAX request + _request: function (operation, queryString, callback, errorResult) { + $.ajax({ + url: this.rootPath + operation + queryString, + method: "GET", + dataType: "json", + cache: false + }) + .done(function (data, textStatus, jqXHR) { + try { + var parsedData = $.parseJSON(data.d); + if (parsedData.Success) { + scada.utils.logSuccessfulRequest(operation/*, data*/); + if (typeof parsedData.DataAge === "undefined") { + callback(true, parsedData.Data); + } else { + callback(true, parsedData.Data, parsedData.DataAge); + } + } else { + scada.utils.logServiceError(operation, parsedData.ErrorMessage); + callback(false, errorResult); + } + } + catch (ex) { + scada.utils.logProcessingError(operation, ex.message); + if (typeof callback === "function") { + callback(false, errorResult); + } + } + }) + .fail(function (jqXHR, textStatus, errorThrown) { + scada.utils.logFailedRequest(operation, jqXHR); + if (typeof callback === "function") { + callback(false, errorResult); + } + }); + }, + + // Perform user login. + // callback is a function (success, loggedOn) + // URL example: http://webserver/scada/ClientApiSvc.svc/Login?username=admin&password=12345 + login: function (username, password, callback) { + this._request("ClientApiSvc.svc/Login", + "?username=" + encodeURIComponent(username) + "&password=" + encodeURIComponent(password), + callback, false); + }, + + // Check that a user is logged on. + // callback is a function (success, loggedOn) + // URL example: http://webserver/scada/ClientApiSvc.svc/CheckLoggedOn + checkLoggedOn: function (callback) { + this._request("ClientApiSvc.svc/CheckLoggedOn", "", callback, false); + }, + + // Get current data of the input channel. + // callback is a function (success, cnlData) + // URL example: http://webserver/scada/ClientApiSvc.svc/GetCurCnlData?cnlNum=1 + getCurCnlData: function (cnlNum, callback) { + this._request("ClientApiSvc.svc/GetCurCnlData", "?cnlNum=" + cnlNum, callback, this._EMPTY_CNL_DATA); + }, + + // Get extended current data by the specified filter. + // callback is a function (success, cnlDataExtArr) + // URL example: http://webserver/scada/ClientApiSvc.svc/GetCurCnlDataExt?cnlNums=&viewID=1 + getCurCnlDataExt: function (cnlFilter, callback) { + this._request("ClientApiSvc.svc/GetCurCnlDataExt", "?" + cnlFilter.toQueryString(), callback, []); + }, + + // Get hourly data by the specified filter. + // dataAge is an array of dates in milliseconds, + // callback is a function (success, hourCnlDataArr, dataAge) + // URL example: http://webserver/scada/ClientApiSvc.svc/GetHourCnlData?year=2016&month=1&day=1&startHour=0&endHour=23&cnlNums=&viewID=1&existing=true&dataAge= + getHourCnlData: function (hourPeriod, cnlFilter, selectMode, dataAge, callback) { + this._request("ClientApiSvc.svc/GetHourCnlData", + "?" + hourPeriod.toQueryString() + "&" + cnlFilter.toQueryString() + "&existing=" + selectMode + + "&dataAge=" + scada.utils.arrayToQueryParam(dataAge), + callback, []); + }, + + // Get events by the specified filter. + // callback is a function (success, eventArr, dataAge) + // URL example: http://webserver/scada/ClientApiSvc.svc/GetEvents?year=2016&month=1&day=1&cnlNums=&viewID=1&lastCount=100&startEvNum=0&dataAge=0 + getEvents: function (date, cnlFilter, lastCount, startEvNum, dataAge, callback) { + this._request("ClientApiSvc.svc/GetEvents", + "?" + scada.utils.dateToQueryString(date) + "&" + cnlFilter.toQueryString() + + "&lastCount=" + lastCount + "&startEvNum=" + startEvNum + "&dataAge=" + dataAge, + callback, []); + }, + + // Get the stamp of the view from the cache. + // callback is a function (success, stamp) + // URL example: http://webserver/scada/ClientApiSvc.svc/GetViewStamp?viewID=1 + getViewStamp: function (viewID, callback) { + this._request("ClientApiSvc.svc/GetViewStamp", "?viewID=" + viewID, callback, 0); + }, + + // Parse date and time using the application culture + // callback is a function (success, value), + // value is the number of milliseconds or null in case of any error + // URL example: http://webserver/scada/ClientApiSvc.svc/ParseDateTime?s=01%20January%202016 + parseDateTime: function (s, callback) { + this._request("ClientApiSvc.svc/ParseDateTime", "?s=" + encodeURIComponent(s), callback, null); + }, + + // Create map of extended input channel data to access by channel number + createCnlDataExtMap: function (cnlDataExtArr) { + try { + var map = new Map(); + for (var cnlDataExt of cnlDataExtArr) { + map.set(cnlDataExt.CnlNum, cnlDataExt); + } + return map; + } + catch (ex) { + console.error(scada.utils.getCurTime() + " Error creating map of extended input channel data:", + ex.message); + return new Map(); + } + }, + + // Create map of hourly input channel data to access by hour + createHourCnlDataMap: function (hourCnlDataArr) { + try { + var map = new Map(); + for (var hourCnlData of hourCnlDataArr) { + map.set(hourCnlData.Hour, hourCnlData); + } + return map; + } + catch (ex) { + console.error(scada.utils.getCurTime() + " Error creating map of hourly input channel data:", + ex.message); + return new Map(); + } + } +}; diff --git a/ScadaWeb/OpenPlugins/PlgChart/js/api/utils.js b/ScadaWeb/OpenPlugins/PlgChart/js/api/utils.js new file mode 100644 index 000000000..c51af7b9e --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/js/api/utils.js @@ -0,0 +1,253 @@ +/* + * JavaScript utilities + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + * + * No dependencies + */ + +// Rapid SCADA namespace +var scada = scada || {}; + +// JavaScript utilities object +scada.utils = { + // Prospective browser scrollbar width + _SCROLLBAR_WIDTH: 20, + + // z-index that moves element to the front + FRONT_ZINDEX: 10000, + + // Default cookie expiration period in days + COOKIE_EXPIRATION: 7, + + // Window width that is considered a small + SMALL_WND_WIDTH: 800, + + // Get cookie + getCookie: function (name) { + var cookie = " " + document.cookie; + var search = " " + name + "="; + var offset = cookie.indexOf(search); + + if (offset >= 0) { + offset += search.length; + var end = cookie.indexOf(";", offset) + + if (end < 0) + end = cookie.length; + + return decodeURIComponent(cookie.substring(offset, end)); + } else { + return null; + } + }, + + // Set cookie + setCookie: function (name, value, opt_expDays) { + var expDays = opt_expDays ? opt_expDays : this.COOKIE_EXPIRATION; + var expires = new Date(); + expires.setDate(expires.getDate() + expDays); + document.cookie = name + "=" + encodeURIComponent(value) + "; expires=" + expires.toUTCString(); + }, + + // Get the query string parameter value + getQueryParam: function (paramName, opt_url) { + if (paramName) { + var url = opt_url ? opt_url : decodeURIComponent(window.location); + var begInd = url.indexOf("?"); + + if (begInd > 0) { + url = "&" + url.substring(begInd + 1); + } + + paramName = "&" + paramName + "="; + begInd = url.indexOf(paramName); + + if (begInd >= 0) { + begInd += paramName.length; + var endInd = url.indexOf("&", begInd); + return endInd >= 0 ? url.substring(begInd, endInd) : url.substring(begInd); + } + } + + return ""; + }, + + // Set or add the query string parameter value. + // The method returns a new string + setQueryParam: function (paramName, paramVal, opt_url) { + if (paramName) { + var url = opt_url ? opt_url : decodeURIComponent(window.location); + var searchName = "?" + paramName + "="; + var nameBegInd = url.indexOf(searchName); + + if (nameBegInd < 0) { + searchName = "&" + paramName + "="; + nameBegInd = url.indexOf(searchName); + } + + if (nameBegInd >= 0) { + // replace parameter value + var valBegInd = nameBegInd + searchName.length; + var valEndInd = url.indexOf("&", valBegInd); + var newUrl = url.substring(0, valBegInd) + encodeURIComponent(paramVal); + return valEndInd > 0 ? + newUrl + url.substring(valEndInd) : + newUrl; + } else { + // add parameter + var mark = url.indexOf("?") >= 0 ? "&" : "?"; + return url + mark + paramName + "=" + encodeURIComponent(paramVal); + } + } else { + return ""; + } + }, + + // Convert array to a query string parameter by joining array elements with a comma + arrayToQueryParam: function (arr) { + var queryParam = arr ? (Array.isArray(arr) ? arr.join(",") : arr) : ""; + // space instead of empty string is required by Mono WCF implementation + return encodeURIComponent(queryParam ? queryParam : " "); + }, + + // Extract year, month and day from the date, and join them into a query string + dateToQueryString: function (date) { + return "year=" + date.getFullYear() + + "&month=" + (date.getMonth() + 1) + + "&day=" + date.getDate(); + }, + + // Returns the current time string + getCurTime: function () { + return new Date().toLocaleTimeString("en-GB"); + }, + + // Write information about the successful request to console + logSuccessfulRequest: function (operation, opt_data) { + console.log(this.getCurTime() + " Request '" + operation + "' successful"); + if (opt_data) { + console.log(opt_data.d); + } + }, + + // Write information about the failed request to console + logFailedRequest: function (operation, jqXHR) { + console.error(this.getCurTime() + " Request '" + operation + "' failed: " + + jqXHR.status + " (" + jqXHR.statusText + ")"); + }, + + // Write information about the internal service error to console + logServiceError: function (operation, opt_message) { + console.error(this.getCurTime() + " Request '" + operation + "' reports internal service error" + + (opt_message ? ": " + opt_message : "")); + }, + + // Write information about the request processing error to console + logProcessingError: function (operation, opt_message) { + console.error(this.getCurTime() + " Error processing request '" + operation + "'" + + (opt_message ? ": " + opt_message : "")); + }, + + // Check if browser is in fullscreen mode + // See https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API + isFullscreen: function() { + return document.fullscreenElement || document.mozFullScreenElement || + document.webkitFullscreenElement || document.msFullscreenElement; + }, + + // Switch browser to fullscreen mode + requestFullscreen: function () { + if (document.documentElement.requestFullscreen) { + document.documentElement.requestFullscreen(); + } else if (document.documentElement.msRequestFullscreen) { + document.documentElement.msRequestFullscreen(); + } else if (document.documentElement.mozRequestFullScreen) { + document.documentElement.mozRequestFullScreen(); + } else if (document.documentElement.webkitRequestFullscreen) { + document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); + } + }, + + // Exit browser fullscreen mode + exitFullscreen: function () { + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.msExitFullscreen) { + document.msExitFullscreen(); + } else if (document.mozCancelFullScreen) { + document.mozCancelFullScreen(); + } else if (document.webkitExitFullscreen) { + document.webkitExitFullscreen(); + } + }, + + // Switch browser to full screen mode and back to normal view + toggleFullscreen: function () { + if (this.isFullscreen()) { + this.exitFullscreen(); + } else { + this.requestFullscreen(); + } + }, + + // Check if a browser window is small sized + isSmallScreen() { + return top.innerWidth <= this.SMALL_WND_WIDTH; + }, + + // Get browser scrollbar width + getScrollbarWidth: function () { + return this._SCROLLBAR_WIDTH; + }, + + // Click hyperlink programmatically + clickLink: function (jqLink) { + var href = jqLink.attr("href"); + if (href) { + if (href.startsWith("javascript:")) { + // execute script + var script = href.substr(11); + eval(script); + } else { + // open web page + location.href = href; + } + } + }, + + // Scroll the first specified element to make the second element visible if it exists + scrollTo: function (jqScrolledElem, jqTargetElem) { + if (jqTargetElem.length > 0) { + var targetTop = jqTargetElem.offset().top; + + if (jqScrolledElem.scrollTop() > targetTop) { + jqScrolledElem.scrollTop(targetTop); + } + } + }, + + // Detect if iOS is used + iOS: function () { + return /iPad|iPhone|iPod/.test(navigator.platform); + }, + + // Apply additional css styles to a container element in case of using iOS + styleIOS: function (jqElem, opt_resetSize) { + if (this.iOS()) { + jqElem.css({ + "overflow": "scroll", + "-webkit-overflow-scrolling": "touch" + }); + + if (opt_resetSize) { + jqElem.css({ + "width": 0, + "height": 0 + }); + } + } + } +}; diff --git a/ScadaWeb/ScadaWebShell5Beta/lib/jquery/jquery.min.js b/ScadaWeb/OpenPlugins/PlgChart/lib/jquery/jquery.min.js similarity index 100% rename from ScadaWeb/ScadaWebShell5Beta/lib/jquery/jquery.min.js rename to ScadaWeb/OpenPlugins/PlgChart/lib/jquery/jquery.min.js diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/Chart.aspx b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/Chart.aspx new file mode 100644 index 000000000..cdd3d3ce7 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/Chart.aspx @@ -0,0 +1,37 @@ +<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Chart.aspx.cs" Inherits="Scada.Web.Plugins.Chart.WFrmChart" EnableViewState="false" %> + + + + + + Chart - Rapid SCADA + + + + + + + + + + + + + + + + + + + + + + + Upgrade the browser to display chart. + + + + diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/Chart.aspx.cs b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/Chart.aspx.cs new file mode 100644 index 000000000..f649d25c6 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/Chart.aspx.cs @@ -0,0 +1,92 @@ +/* + * Copyright 2016 Mikhail Shiryaev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Product : Rapid SCADA + * Module : PlgChart + * Summary : Chart web form + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + */ + +using Scada.Client; +using Scada.Data.Models; +using Scada.Data.Tables; +using Scada.UI; +using System; +using System.Globalization; +using System.Text; +using System.Web; + +namespace Scada.Web.Plugins.Chart +{ + /// + /// Chart web form + /// Веб-форма графика + /// + public partial class WFrmChart : System.Web.UI.Page + { + protected ChartDataBuilder chartDataBuilder; // объект, задающий данные графика + + protected void Page_Load(object sender, EventArgs e) + { + AppData appData = AppData.GetAppData(); + UserData userData = UserData.GetUserData(); + +#if DEBUG + userData.LoginForDebug(); +#endif + + // перевод веб-страницы + Translator.TranslatePage(Page, "Scada.Web.Plugins.Chart.WFrmChart"); + + // получение параметров запроса + int cnlNum = Request.QueryString.GetParamAsInt("cnlNum"); + int viewID = Request.QueryString.GetParamAsInt("viewID"); + DateTime startDate = Request.QueryString.GetParamAsDate(); + + // проверка входа в систему и прав + if (!userData.LoggedOn) + throw new ScadaException(WebPhrases.NotLoggedOn); + + if (!userData.UserRights.GetUiObjRights(viewID).ViewRight) + throw new ScadaException(CommonPhrases.NoRights); + +#if !DEBUG + // в режиме отладки невозможно получить тип представления, т.к. плагины не загружены + Type viewType = userData.UserViews.GetViewType(viewID); + BaseView view = appData.ViewCache.GetView(viewType, viewID, true); + + if (!view.ContainsCnl(cnlNum)) + throw new ScadaException(CommonPhrases.NoRights); + + // вывод заголовков + Title = cnlNum + " - " + Title; + lblTitle.Text = view.Title; +#endif + + // вывод дополнительной информации + lblStartDate.Text = (string.IsNullOrEmpty(lblTitle.Text) ? "" : ", ") + startDate.ToLocalizedDateString(); + lblGenDT.Text = DateTime.Now.ToLocalizedString(); + + // подготовка данных графика + chartDataBuilder = new ChartDataBuilder( + new int[] { cnlNum }, startDate, 1, userData.WebSettings.ChartGap, appData.DataAccess); + chartDataBuilder.FillData(); + } + } +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/Chart.aspx.designer.cs b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/Chart.aspx.designer.cs new file mode 100644 index 000000000..57e6d0e74 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/Chart.aspx.designer.cs @@ -0,0 +1,60 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Scada.Web.Plugins.Chart { + + + public partial class WFrmChart { + + /// + /// frmChart control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.HtmlControls.HtmlForm frmChart; + + /// + /// lblTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblTitle; + + /// + /// lblStartDate control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblStartDate; + + /// + /// lblGenerated control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblGenerated; + + /// + /// lblGenDT control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblGenDT; + } +} diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/MinDataRep.aspx b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/MinDataRep.aspx new file mode 100644 index 000000000..e4313366a --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/MinDataRep.aspx @@ -0,0 +1,82 @@ +<%@ Page Title="Minute Data Report - Rapid SCADA" Language="C#" MasterPageFile="~/MasterMain.Master" AutoEventWireup="true" CodeBehind="MinDataRep.aspx.cs" Inherits="Scada.Web.Plugins.Chart.WFrmMinDataRep" %> +<%@ Import Namespace="Scada.Web.Plugins.Chart" %> + + + + + + + + + + + × + + + + × + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [<%# Eval("CnlNum") %>] <%# HttpUtility.HtmlEncode(Eval("CnlName")) %> + "><%# ChartPhrases.CnlInfoBtn %> + + + + + + + + + + + + + + + + + + + + diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/MinDataRep.aspx.cs b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/MinDataRep.aspx.cs new file mode 100644 index 000000000..d91c59a4d --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/MinDataRep.aspx.cs @@ -0,0 +1,167 @@ +/* + * Copyright 2016 Mikhail Shiryaev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Product : Rapid SCADA + * Module : PlgChart + * Summary : Minute data report parameters web form + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + */ + +using Scada.UI; +using System; +using System.Collections.Generic; +using System.Web.UI; +using System.Web.UI.WebControls; + +namespace Scada.Web.Plugins.Chart +{ + /// + /// Minute data report parameters web form + /// Веб-форма параметров отчёта по минутным данным + /// + public partial class WFrmMinDataRep : System.Web.UI.Page + { + private AppData appData; // общие данные веб-приложения + private UserData userData; // данные пользователя приложения + private List selCnls; // выбранные каналы + + + /// + /// Отобразить выбранные каналы + /// + private void ShowSelCnls() + { + repSelCnls.DataSource = selCnls; + repSelCnls.DataBind(); + btnGenReport.Enabled = selCnls.Count > 0; + lblNoSelCnls.Visible = !btnGenReport.Enabled; + + if (selCnls.Count > ChartUtils.NormalChartCnt) + pnlWarnMsg.ShowAlert(ChartPhrases.PerfWarning); + } + + + protected void Page_Load(object sender, EventArgs e) + { + appData = AppData.GetAppData(); + userData = UserData.GetUserData(); + + // скрытие всех сообщений + pnlErrMsg.HideAlert(); + pnlWarnMsg.HideAlert(); + + if (IsPostBack) + { + // восстановление заголовка при работе с AJAX + Title = (string)ViewState["Title"]; + + // получение выбранных каналов + selCnls = (List)ViewState["SelCnls"]; + } + else + { + // перевод веб-страницы + Translator.TranslatePage(Page, "Scada.Web.Plugins.Chart.WFrmMinDataRep"); + ViewState["Title"] = Title; + + // установка периода по умолчанию + txtDateFrom.Text = txtDateTo.Text = DateTime.Today.ToLocalizedDateString(); + + // создание списка выбранных каналов + selCnls = new List(); + ViewState.Add("SelCnls", selCnls); + + // отображение выбранных каналов + ShowSelCnls(); + } + } + + protected void btnApplyAddedCnls_Click(object sender, EventArgs e) + { + // добавление каналов + if (hidAddedCnlNums.Value != "") + { + int[] addedCnls = WebUtils.QueryParamToIntArray(hidAddedCnlNums.Value); + int[] addedViewIDs = WebUtils.QueryParamToIntArray(hidAddedViewIDs.Value); + ChartUtils.CheckArrays(addedCnls, addedViewIDs); + HashSet selCnlSet = ChartUtils.GetCnlSet(selCnls); + + for (int i = 0, cnt = addedCnls.Length; i < cnt; i++) + { + int cnlNum = addedCnls[i]; + if (!selCnlSet.Contains(cnlNum)) + { + CnlViewPair pair = new CnlViewPair(cnlNum, addedViewIDs[i]); + pair.FillInfo(appData.DataAccess.GetCnlProps(cnlNum), userData.UserViews); + selCnls.Add(pair); + } + } + + ViewState.Add("SelCnls", selCnls); + ShowSelCnls(); + + hidAddedCnlNums.Value = ""; + hidAddedViewIDs.Value = ""; + } + } + + protected void repSelCnls_ItemCommand(object source, RepeaterCommandEventArgs e) + { + // удаление выбранного канала из списка + if (e.CommandName == "RemoveCnl") + { + int index = int.Parse((string)e.CommandArgument); + if (index < selCnls.Count) + { + selCnls.RemoveAt(index); + ViewState.Add("SelCnls", selCnls); + ShowSelCnls(); + } + } + } + + protected void btnGenReport_Click(object sender, EventArgs e) + { + // проверка параметров отчёта и добавление скрипта генерации отчёта + if (selCnls.Count > 0) + { + DateTime dateFrom; + DateTime dateTo; + int period; + string errMsg; + + if (RepUtils.ParseDates(txtDateFrom.Text, txtDateTo.Text, out dateFrom, out dateTo, out errMsg) && + RepUtils.CheckDayPeriod(dateFrom, dateTo, out period, out errMsg)) + { + string cnlNums; + string viewIDs; + ChartUtils.GetSelection(selCnls, out cnlNums, out viewIDs); + + ScriptManager.RegisterClientScriptBlock(this, GetType(), "GenerateReportScript", + string.Format("generateReport('{0}', '{1}', {2}, {3}, {4}, {5});", + cnlNums, viewIDs, dateFrom.Year, dateFrom.Month, dateFrom.Day, period), true); + } + else + { + pnlErrMsg.ShowAlert(errMsg); + } + } + } + } +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/MinDataRep.aspx.designer.cs b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/MinDataRep.aspx.designer.cs new file mode 100644 index 000000000..c5cece5d2 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/MinDataRep.aspx.designer.cs @@ -0,0 +1,222 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Scada.Web.Plugins.Chart { + + + public partial class WFrmMinDataRep { + + /// + /// ScriptMan control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.ScriptManager ScriptMan; + + /// + /// upnlMessage control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.UpdatePanel upnlMessage; + + /// + /// pnlErrMsg control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Panel pnlErrMsg; + + /// + /// lblErrMsg control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblErrMsg; + + /// + /// pnlWarnMsg control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Panel pnlWarnMsg; + + /// + /// lblWarnMsg control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblWarnMsg; + + /// + /// lblTitle control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblTitle; + + /// + /// lblDateFrom control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblDateFrom; + + /// + /// txtDateFrom control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.TextBox txtDateFrom; + + /// + /// lblDateTo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblDateTo; + + /// + /// txtDateTo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.TextBox txtDateTo; + + /// + /// upnlCnls control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.UpdatePanel upnlCnls; + + /// + /// btnAddCnls control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnAddCnls; + + /// + /// btnApplyAddedCnls control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnApplyAddedCnls; + + /// + /// hidAddedCnlNums control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.HiddenField hidAddedCnlNums; + + /// + /// hidAddedViewIDs control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.HiddenField hidAddedViewIDs; + + /// + /// lblSelCnls control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblSelCnls; + + /// + /// pnlSelCnls control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Panel pnlSelCnls; + + /// + /// repSelCnls control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Repeater repSelCnls; + + /// + /// lblNoSelCnls control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblNoSelCnls; + + /// + /// upnlGenReport control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.UpdatePanel upnlGenReport; + + /// + /// btnGenReport control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnGenReport; + + /// + /// lblGenStarted control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblGenStarted; + } +} diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/MinDataRepOut.aspx b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/MinDataRepOut.aspx new file mode 100644 index 000000000..2c99f8b75 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/MinDataRepOut.aspx @@ -0,0 +1 @@ +<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="MinDataRepOut.aspx.cs" Inherits="Scada.Web.Plugins.Chart.WFrmMinDataRepOut" %> \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/MinDataRepOut.aspx.cs b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/MinDataRepOut.aspx.cs new file mode 100644 index 000000000..f33ba27aa --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/MinDataRepOut.aspx.cs @@ -0,0 +1,74 @@ +/* + * Copyright 2016 Mikhail Shiryaev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Product : Rapid SCADA + * Module : PlgChart + * Summary : Minute data report output web form + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + */ + +using Scada.Client; +using System; +using Utils.Report; + +namespace Scada.Web.Plugins.Chart +{ + /// + /// Minute data report output web form + /// Выходная веб-форма отчёта по минутным данным + /// + /// + /// URL example: + /// http://webserver/scada/plugins/Chart/MinDataRepOut.aspx?cnlNums=1,2&viewIDs=1,1&year=2016&month=1&day=1&period=2 + /// + public partial class WFrmMinDataRepOut : System.Web.UI.Page + { + protected void Page_Load(object sender, EventArgs e) + { + AppData appData = AppData.GetAppData(); + UserData userData = UserData.GetUserData(); + + // проверка входа в систему + if (!userData.LoggedOn) + throw new ScadaException(WebPhrases.NotLoggedOn); + + // получение параметров запроса + int[] cnlNums = Request.QueryString.GetParamAsIntArray("cnlNums"); + int[] viewIDs = Request.QueryString.GetParamAsIntArray("viewIDs"); + DateTime startDate = Request.QueryString.GetParamAsDate(); + int period = Request.QueryString.GetParamAsInt("period"); + + // проверка прав и получение представления, если оно единственное + RightsChecker rightsChecker = new RightsChecker(appData.ViewCache); + BaseView singleView; + rightsChecker.CheckRights(userData, cnlNums, viewIDs, out singleView); + string viewTitle = singleView == null ? "" : singleView.Title; + + // генерация отчёта + RepBuilder repBuilder = new MinDataRepBuilder(appData.DataAccess); + RepUtils.WriteGenerationAction(appData.Log, repBuilder, userData); + RepUtils.GenerateReport( + repBuilder, + new object[] { cnlNums, startDate, period, viewTitle }, + Server.MapPath("~/plugins/Chart/templates/"), + RepUtils.BuildFileName("MinData", "xml"), + Response); + } + } +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/MinDataRepOut.aspx.designer.cs b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/MinDataRepOut.aspx.designer.cs new file mode 100644 index 000000000..4b3d658ac --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/MinDataRepOut.aspx.designer.cs @@ -0,0 +1,15 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Scada.Web.Plugins.Chart { + + + public partial class WFrmMinDataRepOut { + } +} diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/SelectCnls.aspx b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/SelectCnls.aspx new file mode 100644 index 000000000..b4740faf3 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/SelectCnls.aspx @@ -0,0 +1,53 @@ +<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="SelectCnls.aspx.cs" Inherits="Scada.Web.Plugins.Chart.WFrmSelectCnls" %> +<%@ Import Namespace="Scada.Web.Plugins.Chart" %> + + + + + + + + + Select Channels - Rapid SCADA + + + + + + + + + + + + + + + + + + + + + + + + + [<%# Eval("CnlNum") %>] <%# HttpUtility.HtmlEncode(Eval("CnlName")) %> + "><%# ChartPhrases.CnlInfoBtn %> + + + + + + + + + + + diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/SelectCnls.aspx.cs b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/SelectCnls.aspx.cs new file mode 100644 index 000000000..9484c025b --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/SelectCnls.aspx.cs @@ -0,0 +1,142 @@ +/* + * Copyright 2016 Mikhail Shiryaev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Product : Rapid SCADA + * Module : PlgChart + * Summary : Select input channels web form + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + */ + +using Scada.Client; +using Scada.UI; +using Scada.Web.Shell; +using System; +using System.Collections.Generic; +using System.Web.UI.WebControls; + +namespace Scada.Web.Plugins.Chart +{ + /// + /// Select input channels web form + /// Веб-форма выбора входных каналов + /// + public partial class WFrmSelectCnls : System.Web.UI.Page + { + private AppData appData; // общие данные веб-приложения + private UserData userData; // данные пользователя приложения + private List selCnls; // выбранные каналы + protected HashSet selCnlSet; // множество выбранных каналов + + + /// + /// Отобразить каналы выбранного представления + /// + private void ShowCnlsByView() + { + int viewID; + int.TryParse(ddlView.SelectedValue, out viewID); + List cnlsByView = ChartUtils.GetCnlViewPairsByView( + viewID, appData.DataAccess, appData.ViewCache, userData.UserViews); + + repCnlsByView.DataSource = cnlsByView; + repCnlsByView.DataBind(); + lblUnableLoadView.Visible = cnlsByView == null; + lblNoCnlsByView.Visible = cnlsByView != null && cnlsByView.Count == 0; + } + + + protected void Page_Load(object sender, EventArgs e) + { + appData = AppData.GetAppData(); + userData = UserData.GetUserData(); + + // проверка входа в систему + if (!userData.LoggedOn) + throw new ScadaException(WebPhrases.NotLoggedOn); + + if (IsPostBack) + { + // получение выбранных каналов + selCnls = (List)ViewState["SelCnls"]; + selCnlSet = ChartUtils.GetCnlSet(selCnls); + } + else + { + // перевод веб-страницы + Translator.TranslatePage(Page, "Scada.Web.Plugins.Chart.WFrmSelectCnls"); + lblPerfWarn.Text = ChartPhrases.PerfWarning; + + // настройка элементов управления + btnSubmit.Enabled = false; + pnlPerfWarn.Visible = false; + + // создание списка выбранных каналов + selCnls = new List(); + ViewState.Add("SelCnls", selCnls); + selCnlSet = ChartUtils.GetCnlSet(selCnls); + + // заполнение выпадающего списка представлений и отображение каналов по представлению + ChartUtils.FillViewList(ddlView, 0, userData.UserViews); + ShowCnlsByView(); + } + } + + protected void btnSubmit_Click(object sender, EventArgs e) + { + // завершить выбор каналов + string cnlNums; + string viewIDs; + selCnls.GetSelection(out cnlNums, out viewIDs); + ClientScript.RegisterStartupScript(GetType(), "CloseModalScript", + string.Format("closeModal('{0}', '{1}');", cnlNums, viewIDs), true); + } + + protected void ddlView_SelectedIndexChanged(object sender, EventArgs e) + { + // выбор каналов по представлению + ShowCnlsByView(); + ChartUtils.AddUpdateModalHeightScript(this); + } + + protected void repCnlsByView_ItemCommand(object source, RepeaterCommandEventArgs e) + { + // добавление выбранного канала в список + if (e.CommandName == "AddCnl") + { + int cnlNum = int.Parse((string)e.CommandArgument); + + if (!selCnlSet.Contains(cnlNum)) + { + int viewID = int.Parse(ddlView.SelectedValue); + CnlViewPair pair = new CnlViewPair(cnlNum, viewID); + pair.FillInfo(appData.DataAccess.GetCnlProps(cnlNum), null); + + selCnls.Add(pair); + ViewState.Add("SelCnls", selCnls); + + btnSubmit.Enabled = true; + pnlPerfWarn.Visible = selCnls.Count > ChartUtils.NormalChartCnt; + } + + Label lblCnlAdded = (Label)e.Item.FindControl("lblCnlAdded"); + lblCnlAdded.Visible = true; + } + } + } +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/SelectCnls.aspx.designer.cs b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/SelectCnls.aspx.designer.cs new file mode 100644 index 000000000..3a51c8bbf --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/SelectCnls.aspx.designer.cs @@ -0,0 +1,105 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Scada.Web.Plugins.Chart { + + + public partial class WFrmSelectCnls { + + /// + /// frmSelectCnls control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.HtmlControls.HtmlForm frmSelectCnls; + + /// + /// btnSubmit control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Button btnSubmit; + + /// + /// pnlPerfWarn control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Panel pnlPerfWarn; + + /// + /// lblPerfWarn control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblPerfWarn; + + /// + /// ddlView control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.DropDownList ddlView; + + /// + /// pnlCnlsByView control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Panel pnlCnlsByView; + + /// + /// repCnlsByView control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Repeater repCnlsByView; + + /// + /// lblLoading control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblLoading; + + /// + /// lblUnableLoadView control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblUnableLoadView; + + /// + /// lblNoCnlsByView control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblNoCnlsByView; + } +} diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/chart.css b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/chart.css new file mode 100644 index 000000000..4c57e957a --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/chart.css @@ -0,0 +1,46 @@ +.chart-wrapper { + position: relative; +} +.chart-canvas { + border: 1px solid gray; + display: block; +} +.chart-time-mark { + background-color: black; + opacity: 0.25; + position: absolute; + width: 1px; + z-index: 1; +} +.chart-trend-hint { + background-color: white; + border: 1px solid #888; + border-radius: 5px; + box-shadow: 2px 2px 5px #aaa; + color: #333; + font-family: Arial, Helvetica, sans-serif; + font-size: 12px; + padding: 5px 10px; + position: absolute; + z-index: 2; +} +.chart-trend-hint table { + border-collapse: collapse; +} +.chart-trend-hint td { + padding: 2px 5px 0 0; + white-space: nowrap; +} +.chart-trend-hint td:last-child { + padding-right: 0; +} +.chart-trend-hint td.color span { + border: 1px solid black; + display: inline-block; + height: 12px; + width: 12px; +} +.chart-time-mark.hidden, +.chart-trend-hint.hidden { + display: none; +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/chart.less b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/chart.less new file mode 100644 index 000000000..af2c65d4c --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/chart.less @@ -0,0 +1,65 @@ +@import "../../../css/common/globalvar.less"; + +@mark-color: black; +@hint-back-color: white; +@hint-fore-color: @content-fore-color; +@hint-border-color: #888; +@hint-shadow-color: #aaa; +@chart-border-color: gray; +@square-border-color: black; +@chart-font-family: Arial, Helvetica, sans-serif; + +.chart-wrapper { + position: relative; +} + +.chart-canvas { + border: 1px solid @chart-border-color; + display: block; +} + +.chart-time-mark { + background-color: @mark-color; + opacity: 0.25; + position: absolute; + width: 1px; + z-index: 1; +} + +.chart-trend-hint { + background-color: @hint-back-color; + border: 1px solid @hint-border-color; + border-radius: 5px; + box-shadow: 2px 2px 5px @hint-shadow-color; + color: @hint-fore-color; + font-family: @chart-font-family; + font-size: 12px; + padding: 5px 10px; + position: absolute; + z-index: 2; +} + +.chart-trend-hint table { + border-collapse: collapse; +} + +.chart-trend-hint td { + padding: 2px 5px 0 0; + white-space: nowrap; +} + +.chart-trend-hint td:last-child { + padding-right: 0; +} + +.chart-trend-hint td.color span { + border: 1px solid @square-border-color; + display: inline-block; + height: 12px; + width: 12px; +} + +.chart-time-mark.hidden, +.chart-trend-hint.hidden { + display: none; +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/chart.min.css b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/chart.min.css new file mode 100644 index 000000000..6ad0a3aa2 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/chart.min.css @@ -0,0 +1 @@ +.chart-wrapper{position:relative;}.chart-canvas{border:1px solid #808080;display:block;}.chart-time-mark{background-color:#000;opacity:.25;position:absolute;width:1px;z-index:1;}.chart-trend-hint{background-color:#fff;border:1px solid #888;border-radius:5px;box-shadow:2px 2px 5px #aaa;color:#333;font-family:Arial,Helvetica,sans-serif;font-size:12px;padding:5px 10px;position:absolute;z-index:2;}.chart-trend-hint table{border-collapse:collapse;}.chart-trend-hint td{padding:2px 5px 0 0;white-space:nowrap;}.chart-trend-hint td:last-child{padding-right:0;}.chart-trend-hint td.color span{border:1px solid #000;display:inline-block;height:12px;width:12px;}.chart-time-mark.hidden,.chart-trend-hint.hidden{display:none;} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/chartform.css b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/chartform.css new file mode 100644 index 000000000..8d036f05f --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/chartform.css @@ -0,0 +1,35 @@ +body { + margin: 0; + padding: 10px; + background-color: white; +} +/* Inclusion font in the body adds unexpected pixel to the document height */ +div { + font-family: 'Open Sans', sans-serif; + font-size: 13px; +} +.chart-header { + overflow: hidden; + /* to expand the container according to a float div */ +} +.chart-title { + color: black; + display: inline-block; + font-size: 17px; + line-height: 20px; + margin-bottom: 10px; +} +.chart-status { + color: #777; + display: inline-block; + font-size: 11px; + float: right; + line-height: 20px; +} +.chart-status.error { + color: red; +} +.chart-canvas { + min-width: 300px; + min-height: 200px; +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/chartform.less b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/chartform.less new file mode 100644 index 000000000..10b21499a --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/chartform.less @@ -0,0 +1,46 @@ +@import "../../../css/common/globalvar.less"; + +@chart-title-fore-color: black; +@chart-status-fore-color: #777; +@chart-status-error-fore-color: red; + +body { + margin: 0; + padding: 10px; + background-color: @popup-back-color; +} + +/* Inclusion font in the body adds unexpected pixel to the document height */ +div { + font-family: @default-font-family; + font-size: @form-font-size; +} + +.chart-header { + overflow: hidden; /* to expand the container according to a float div */ +} + +.chart-title { + color: @chart-title-fore-color; + display: inline-block; + font-size: 17px; + line-height: 20px; + margin-bottom: 10px; +} + +.chart-status { + color: @chart-status-fore-color; + display: inline-block; + font-size: 11px; + float: right; + line-height: 20px; +} + +.chart-status.error { + color: @chart-status-error-fore-color; +} + +.chart-canvas { + min-width: 300px; + min-height: 200px; +} diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/chartform.min.css b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/chartform.min.css new file mode 100644 index 000000000..49dbf5741 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/chartform.min.css @@ -0,0 +1 @@ +body{margin:0;padding:10px;background-color:#fff;}div{font-family:'Open Sans',sans-serif;font-size:13px;}.chart-header{overflow:hidden;}.chart-title{color:#000;display:inline-block;font-size:17px;line-height:20px;margin-bottom:10px;}.chart-status{color:#777;display:inline-block;font-size:11px;float:right;line-height:20px;}.chart-status.error{color:#f00;}.chart-canvas{min-width:300px;min-height:200px;} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/cnllist.css b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/cnllist.css new file mode 100644 index 000000000..249cfc088 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/cnllist.css @@ -0,0 +1,30 @@ +.cnl-list { + display: table; +} +.cnl-list .cnl-item { + display: table-row; +} +.cnl-list .cnl-field { + display: table-cell; + padding: 2px 0; +} +.cnl-list .cnl-name { + padding-left: 15px; + vertical-align: middle; +} +.cnl-list .cnl-btns { + padding-left: 10px; + vertical-align: top; + white-space: nowrap; +} +.cnl-list .cnl-btns input, +.cnl-list .cnl-btns a { + margin-right: 5px; +} +.cnl-list .popover { + font-size: 12px; + line-height: normal; +} +.cnl-list-msg { + margin-left: 15px; +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/cnllist.less b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/cnllist.less new file mode 100644 index 000000000..a4f39203f --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/cnllist.less @@ -0,0 +1,39 @@ +@import "../../../css/common/globalvar.less"; + +.cnl-list { + display: table; +} + +.cnl-list .cnl-item { + display: table-row; +} + +.cnl-list .cnl-field { + display: table-cell; + padding: 2px 0; +} + +.cnl-list .cnl-name { + padding-left: 15px; + vertical-align: middle; +} + +.cnl-list .cnl-btns { + padding-left: 10px; + vertical-align: top; + white-space: nowrap; +} + +.cnl-list .cnl-btns input, +.cnl-list .cnl-btns a { + margin-right: 5px; +} + +.cnl-list .popover { + font-size: @data-font-size; + line-height: normal; +} + +.cnl-list-msg { + margin-left: 15px; +} diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/cnllist.min.css b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/cnllist.min.css new file mode 100644 index 000000000..642310214 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/cnllist.min.css @@ -0,0 +1 @@ +.cnl-list{display:table;}.cnl-list .cnl-item{display:table-row;}.cnl-list .cnl-field{display:table-cell;padding:2px 0;}.cnl-list .cnl-name{padding-left:15px;vertical-align:middle;}.cnl-list .cnl-btns{padding-left:10px;vertical-align:top;white-space:nowrap;}.cnl-list .cnl-btns input,.cnl-list .cnl-btns a{margin-right:5px;}.cnl-list .popover{font-size:12px;line-height:normal;}.cnl-list-msg{margin-left:15px;} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/mindatarep.css b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/mindatarep.css new file mode 100644 index 000000000..8df2ed021 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/mindatarep.css @@ -0,0 +1,77 @@ +.main-content { + padding: 20px; + font-size: 14px; +} +h1 { + font-size: 23px; +} +h2 { + font-size: 17px; +} +h3 { + font-size: 14px; +} +h1, +h2, +h3 { + font-weight: 500; + margin: 20px 0 10px; +} +h1:first-of-type { + margin-top: 0; +} +a, +a:active, +a:hover, +a:focus, +a:visited { + color: #0073aa; + outline: 0; + text-decoration: none; +} +a:hover { + color: #00a0d2; +} +label { + font-weight: 600; +} +.cnl-list { + display: table; +} +.cnl-list .cnl-item { + display: table-row; +} +.cnl-list .cnl-field { + display: table-cell; + padding: 2px 0; +} +.cnl-list .cnl-name { + padding-left: 15px; + vertical-align: middle; +} +.cnl-list .cnl-btns { + padding-left: 10px; + vertical-align: top; + white-space: nowrap; +} +.cnl-list .cnl-btns input, +.cnl-list .cnl-btns a { + margin-right: 5px; +} +.cnl-list .popover { + font-size: 12px; + line-height: normal; +} +.cnl-list-msg { + margin-left: 15px; +} +.form-group.period { + max-width: 300px; +} +.popovers-container { + position: relative; +} +#lblGenStarted { + display: block; + margin-top: 5px; +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/mindatarep.less b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/mindatarep.less new file mode 100644 index 000000000..5cbd3421c --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/mindatarep.less @@ -0,0 +1,15 @@ +@import "../../../css/common/contentform.less"; +@import "cnllist.less"; + +.form-group.period { + max-width: 300px; +} + +.popovers-container { + position: relative; +} + +#lblGenStarted { + display: block; + margin-top: 5px; +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/mindatarep.min.css b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/mindatarep.min.css new file mode 100644 index 000000000..78c16b2c8 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/mindatarep.min.css @@ -0,0 +1 @@ +.main-content{padding:20px;font-size:14px;}h1{font-size:23px;}h2{font-size:17px;}h3{font-size:14px;}h1,h2,h3{font-weight:500;margin:20px 0 10px;}h1:first-of-type{margin-top:0;}a,a:active,a:hover,a:focus,a:visited{color:#0073aa;outline:0;text-decoration:none;}a:hover{color:#00a0d2;}label{font-weight:600;}.cnl-list{display:table;}.cnl-list .cnl-item{display:table-row;}.cnl-list .cnl-field{display:table-cell;padding:2px 0;}.cnl-list .cnl-name{padding-left:15px;vertical-align:middle;}.cnl-list .cnl-btns{padding-left:10px;vertical-align:top;white-space:nowrap;}.cnl-list .cnl-btns input,.cnl-list .cnl-btns a{margin-right:5px;}.cnl-list .popover{font-size:12px;line-height:normal;}.cnl-list-msg{margin-left:15px;}.form-group.period{max-width:300px;}.popovers-container{position:relative;}#lblGenStarted{display:block;margin-top:5px;} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/selectcnls.css b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/selectcnls.css new file mode 100644 index 000000000..b4e54621c --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/selectcnls.css @@ -0,0 +1,46 @@ +body { + margin: 0; + padding: 0; + background-color: white; + color: #333; + font-family: 'Open Sans', sans-serif; + font-size: 13px; + min-width: 250px; + overflow: hidden; +} +table { + border-collapse: collapse; +} +label { + font-weight: 600; +} +.cnl-list { + display: table; +} +.cnl-list .cnl-item { + display: table-row; +} +.cnl-list .cnl-field { + display: table-cell; + padding: 2px 0; +} +.cnl-list .cnl-name { + padding-left: 15px; + vertical-align: middle; +} +.cnl-list .cnl-btns { + padding-left: 10px; + vertical-align: top; + white-space: nowrap; +} +.cnl-list .cnl-btns input, +.cnl-list .cnl-btns a { + margin-right: 5px; +} +.cnl-list .popover { + font-size: 12px; + line-height: normal; +} +.cnl-list-msg { + margin-left: 15px; +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/selectcnls.less b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/selectcnls.less new file mode 100644 index 000000000..917970f58 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/selectcnls.less @@ -0,0 +1,2 @@ +@import "../../../css/common/modalform.less"; +@import "cnllist.less"; diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/selectcnls.min.css b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/selectcnls.min.css new file mode 100644 index 000000000..9744bf085 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/css/selectcnls.min.css @@ -0,0 +1 @@ +body{margin:0;padding:0;background-color:#fff;color:#333;font-family:'Open Sans',sans-serif;font-size:13px;min-width:250px;overflow:hidden;}table{border-collapse:collapse;}label{font-weight:600;}.cnl-list{display:table;}.cnl-list .cnl-item{display:table-row;}.cnl-list .cnl-field{display:table-cell;padding:2px 0;}.cnl-list .cnl-name{padding-left:15px;vertical-align:middle;}.cnl-list .cnl-btns{padding-left:10px;vertical-align:top;white-space:nowrap;}.cnl-list .cnl-btns input,.cnl-list .cnl-btns a{margin-right:5px;}.cnl-list .popover{font-size:12px;line-height:normal;}.cnl-list-msg{margin-left:15px;} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/js/chart.js b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/js/chart.js new file mode 100644 index 000000000..d00be3771 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/js/chart.js @@ -0,0 +1,1029 @@ +/* + * Chart control + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + * + * Requires: + * - jquery + * - utils.js + */ + +// Rapid SCADA namespace +var scada = scada || {}; +// Chart namespace +scada.chart = scada.chart || {}; + +/********** Constants **********/ + +// Constants object +scada.chart.const = { + // Seconds per day + SEC_PER_DAY: 86400, + // Milliseconds per day + MS_PER_DAY: 86400 * 1000 +}; + +/********** Display Settings **********/ + +// Display settings type +scada.chart.DisplaySettings = function () { + // Application culture name + this.locale = "en-GB"; + // Distance between chart points to make a gap + this.chartGap = 90 / scada.chart.const.SEC_PER_DAY; // 90 seconds +}; + +/********** Time Range **********/ + +// Time range type +scada.chart.TimeRange = function () { + // Date of the beginning of the range in milliseconds + this.startDate = 0; + // Left edge of the range, where 0 is 00:00 and 1 is 24:00 + this.startTime = 0; + // Right edge of the range + this.endTime = 1; +} + +/********** Extended Trend **********/ + +// Extended trend type +// Note: Casing is caused by C# naming rules +scada.chart.TrendExt = function () { + // Input channel number + this.cnlNum = 0; + // Input channel name + this.cnlName = ""; + // Trend points where each point is array [value, "text", "text with unit", "color"] + this.trendPoints = []; +} + +/********** Trend Point Indexes **********/ + +// Trend point indexes enumeration +scada.chart.TrendPointIndexes = { + VAL_IND: 0, + TEXT_IND: 1, + TEXT_WITH_UNIT_IND: 2, + COLOR_IND: 3 +}; + +/********** Chart Data **********/ + +// Chart data type +scada.chart.ChartData = function () { + // Time points which number is matched with the number of trend points. Array of numbers + this.timePoints = []; + // Trends to display. Array of TrendExt + this.trends = []; + // Name of input channel quantity (and unit) + this.quantityName = ""; +} + +/********** Chart Layout **********/ + +// Chart layout type +scada.chart.ChartLayout = function () { + // Desirable number of horizontal grid lines + this._GRID_HOR_LINE_CNT = 10; + + // Chart left padding + this.LEFT_PADDING = 10; + // Chart right padding + this.RIGHT_PADDING = 20; + // Chart top padding + this.TOP_PADDING = 20; + // Chart bottom padding + this.BOTTOM_PADDING = 10; + // Tick mark size + this.TICK_SIZE = 3; + // Data label left and right margins + this.LBL_LR_MARGIN = 10; + // Data label top and bottom margins + this.LBL_TB_MARGIN = 5; + // Data labels font + this.LBL_FONT = "12px Arial"; + // Data labels font size the same as specified above + this.LBL_FONT_SIZE = 12; + // Line height of various kinds of texts + this.LINE_HEIGHT = 18; + // Vertical hint offset relative to the cursor + this.HINT_OFFSET = 20; + // Chart back color + this.BACK_COLOR = "#ffffff"; + // Default fore color + this.DEF_COLOR = "#000000"; + // Chart frame color + this.FRAME_COLOR = "#808080"; + // Grid lines color + this.GRID_COLOR = "#e0e0e0"; + // Tick marks color + this.TICK_COLOR = "#808080"; + // Data labels color + this.LBL_COLOR = "#000000"; + + // Chart width + this.width = 0; + // Chart height + this.height = 0; + + // Grid step for the x-axis + this.gridXStep = 0; + // Start grid value for the y-axis + this.gridYStart = 0; + // Grid step for the y-axis + this.gridYStep = 0; + // Number of decimal places to use in labels for the y-axis + this.gridYDecDig = 0; + // Max data label width for the y-axis + this.maxYLblWidth = 0; + + // Left coordinate of the drawing area + this.plotAreaLeft = 0; + // Right coordinate of the drawing area + this.plotAreaRight = 0; + // Top coordinate of the drawing area + this.plotAreaTop = 0; + // Bottom coordinate of the drawing area + this.plotAreaBottom = 0; + // Drawing area width + this.plotAreaWidth = 0; + // Drawing area height + this.plotAreaHeight = 0; + + // Width of the canvas left border + this.canvasLeftBorder = 0; + // Width of the canvas top border + this.canvasTopBorder = 0; + // Absolute left coordinate of the canvas relative to the document + this.absCanvasLeft = 0; + // Absolute top coordinate of the canvas relative to the document + this.absCanvasTop = 0; + // Absolute left coordinate of the drawing area relative to the document + this.absPlotAreaLeft = 0; + // Absolute right coordinate of the drawing area relative to the document + this.absPlotAreaRight = 0; + // Absolute top coordinate of the drawing area relative to the document + this.absPlotAreaTop = 0; + // Absolute bottom coordinate of the drawing area relative to the document + this.absPlotAreaBottom = 0; +} + +// Calculate grid parameters for the x-axis +scada.chart.ChartLayout.prototype._calcGridX = function (minX, maxX) { + var cnt = 8; + var ranges = [16, 8, 4, 2, 1, 1 / 2, 1 / 4, 1 / 12]; // displayed ranges, days + var steps = [24, 12, 6, 3, 2, 1, 1 / 2, 1 / 4]; // grid steps according to the ranges, hours + var minStep = 1 / 12; // 5 minutes + var range = maxX - minX; + + for (var i = 0; i < cnt; i++) { + if (range > ranges[i]) { + minStep = steps[i]; + break; + } + } + + this.gridXStep = 1 / 24 * minStep; +}; + +// Calculate grid parameters for the y-axis +scada.chart.ChartLayout.prototype._calcGridY = function (context, minY, maxY) { + this.gridYStep = (maxY - minY) / this._GRID_HOR_LINE_CNT; + this.gridYDecDig = 0; + var n = 1; + + if (this.gridYStep >= 1) { + while (this.gridYStep > 10) { + this.gridYStep /= 10; + n *= 10; + } + } else { + while (this.gridYStep < 1) { + this.gridYStep *= 10; + n /= 10; + this.gridYDecDig++ + } + } + + this.gridYStep = Math.floor(this.gridYStep); + + // the first significant digit of the grid step is 1, 2 or 5 + if (3 <= this.gridYStep && this.gridYStep <= 4) { + this.gridYStep = 2; + } + else if (6 <= this.gridYStep && this.gridYStep <= 9) { + this.gridYStep = 5; + } + + this.gridYStep *= n; + this.gridYStart = Math.floor(minY / this.gridYStep) * this.gridYStep + this.gridYStep; + + // measure max data label width + var maxWidth = 0; + for (var y = this.gridYStart; y < maxY; y += this.gridYStep) { + var w = context.measureText(y.toFixed(this.gridYDecDig)).width; + if (maxWidth < w) + maxWidth = w; + } + this.maxYLblWidth = maxWidth; +} + +// Calculate coordinates of the drawing area +scada.chart.ChartLayout.prototype._calcPlotArea = function (canvasJqObj, trendCnt, showDates) { + this.plotAreaLeft = this.LEFT_PADDING + this.LINE_HEIGHT /*y-axis title*/ + + this.maxYLblWidth + this.LBL_LR_MARGIN * 2; + this.plotAreaRight = this.width - this.RIGHT_PADDING; + this.plotAreaTop = this.TOP_PADDING; + this.plotAreaBottom = this.height - this.BOTTOM_PADDING - this.LBL_TB_MARGIN - this.LINE_HEIGHT /*time labels*/ - + (showDates ? this.LINE_HEIGHT : 0) - this.LBL_TB_MARGIN - trendCnt * this.LINE_HEIGHT; + this.plotAreaWidth = this.plotAreaRight - this.plotAreaLeft + 1; + this.plotAreaHeight = this.plotAreaBottom - this.plotAreaTop + 1; + + this.canvasLeftBorder = parseInt(canvasJqObj.css("border-left-width")); + this.canvasTopBorder = parseInt(canvasJqObj.css("border-top-width")); + this.updateAbsCoordinates(canvasJqObj); +} + +// Calculate chart layout +scada.chart.ChartLayout.prototype.calculate = function (canvasJqObj, context, + minX, maxX, minY, maxY, trendCnt, showDates) { + + this.width = canvasJqObj.width(); + this.height = canvasJqObj.height(); + + this._calcGridX(minX, maxX); + this._calcGridY(context, minY, maxY); + this._calcPlotArea(canvasJqObj, trendCnt, showDates); +} + +// Update absolute coordinates those depends on canvas offset +scada.chart.ChartLayout.prototype.updateAbsCoordinates = function (canvasJqObj) { + var offset = canvasJqObj.offset(); + this.absCanvasLeft = offset.left; + this.absCanvasTop = offset.top; + this.absPlotAreaLeft = this.absCanvasLeft + this.canvasLeftBorder + this.plotAreaLeft; + this.absPlotAreaRight = this.absCanvasLeft + this.canvasLeftBorder + this.plotAreaRight; + this.absPlotAreaTop = this.absCanvasTop + this.canvasTopBorder + this.plotAreaTop; + this.absPlotAreaBottom = this.absCanvasTop + this.canvasTopBorder + this.plotAreaBottom; +} + +// Check if the specified point is located within the chart area +scada.chart.ChartLayout.prototype.pointInPlotArea = function (pageX, pageY) { + return this.absPlotAreaLeft <= pageX && pageX <= this.absPlotAreaRight && + this.absPlotAreaTop <= pageY && pageY <= this.absPlotAreaBottom; +} + +/********** Chart Control **********/ + +// Chart type +scada.chart.Chart = function (canvasJqObj) { + // Date format options + this._DATE_OPTIONS = { month: "short", day: "2-digit", timeZone: "UTC" }; + // Time format options + this._TIME_OPTIONS = { hour: "2-digit", minute: "2-digit", timeZone: "UTC" }; + // Date and time format options + this._DATE_TIME_OPTIONS = $.extend({}, this._DATE_OPTIONS, this._TIME_OPTIONS); + // Colors assigned to trends + this._TREND_COLORS = + ["#ff0000" /*Red*/, "#0000ff" /*Blue*/, "#008000" /*Green*/, "#ff00ff" /*Fuchsia*/, "#ffa500" /*Orange*/, + "#00ffff" /*Aqua*/, "#00ff00" /*Lime*/, "#4b0082" /*Indigo*/, "#ff1493" /*DeepPink*/, "#8b4513" /*SaddleBrown*/]; + + // Canvas jQuery object + this._canvasJqObj = canvasJqObj; + // Canvas where the chart is drawn + this._canvas = canvasJqObj.length ? canvasJqObj[0] : null; + // Canvas is supported and ready for drawing + this._canvasOK = this._canvas && this._canvas.getContext; + // Canvas drawing context + this._context = null; + // Layout of the chart + this._chartLayout = new scada.chart.ChartLayout(); + // Time mark jQuery object + this._timeMark = null; + // Trend hint jQuery object + this._trendHint = null; + // Enable or disable trend hint + this._hintEnabled = true; + + // Left edge of the displayed range + this._minX = 0; + // Right edge of the displayed range + this._maxX = 0; + // Bottom edge of the displayed range + this._minY = 0; + // Top edge of the displayed range + this._maxY = 0; + // Transformation coefficient of the x-coordinate + this._coefX = 1; + // Transformation coefficient of the y-coordinate + this._coefY = 1; + // Show date labels below the x-axis + this._showDates = false; + // Zoom mode affects calculation of the vertical range + this._zoomMode = false; + + // Display settings + this.displaySettings = new scada.chart.DisplaySettings(); + // Time range + this.timeRange = new scada.chart.TimeRange(); + // Chart data + this.chartData = null; +}; + +// Initialize displayed range according to the chart time range and data +scada.chart.Chart.prototype._initRange = function (opt_reinit) { + if (this._minX == this._maxX /*not initialized yet*/ || opt_reinit) { + this._minX = Math.min(this.timeRange.startTime, 0); + this._maxX = Math.max(this.timeRange.endTime, 1); + this._zoomMode = false; + this._calcYRange(); + this._showDates = this._maxX - this._minX > 1; + } +} + +// Claculate top and bottom edges of the displayed range +scada.chart.Chart.prototype._calcYRange = function (opt_startPtInd) { + // find min and max trend value + var minY = NaN; + var maxY = NaN; + var minX = this._minX - this.displaySettings.chartGap; + var maxX = this._maxX + this.displaySettings.chartGap; + + var timePoints = this.chartData.timePoints; + var startPtInd = opt_startPtInd ? opt_startPtInd : 0; + var ptCnt = timePoints.length; + var VAL_IND = scada.chart.TrendPointIndexes.VAL_IND; + + for (var trend of this.chartData.trends) { + var trendPoints = trend.trendPoints; + + for (var ptInd = startPtInd; ptInd < ptCnt; ptInd++) { + var x = timePoints[ptInd]; + + if (minX <= x && x <= maxX) { + var y = trendPoints[ptInd][VAL_IND]; + if (isNaN(minY) || minY > y) { + minY = y; + } + if (isNaN(maxY) || maxY < y) { + maxY = y; + } + } + } + } + + if (isNaN(minY)) { + minY = -1; + maxY = 1; + } else { + // calculate extra space + var extraSpace = minY == maxY ? 1 : (maxY - minY) * 0.05; + + // include zero if zoom is off + var origMinY = minY; + var origMaxY = maxY; + + if (!this._zoomMode) { + if (minY > 0 && maxY > 0) { + minY = 0; + } + else if (minY < 0 && maxY < 0) { + maxY = 0; + } + extraSpace = Math.max(extraSpace, (maxY - minY) * 0.05); + } + + // add extra space + if (origMinY - minY < extraSpace) { + minY -= extraSpace; + } + if (maxY - origMaxY < extraSpace) { + maxY += extraSpace; + } + } + + this._minY = minY; + this._maxY = maxY; +}; + +// Check if top and bottom edges are outdated because of new data +scada.chart.Chart.prototype._yRangeIsOutdated = function (startPtInd) { + var oldMinY = this._minY; + var oldMaxY = this._maxY; + this._calcYRange(startPtInd); + var outdated = this._minY < oldMinY || this._maxY > oldMaxY; + + // restore the range + this._minY = oldMinY; + this._maxY = oldMaxY; + + return outdated; +} + +// Convert trend x-coordinate to the chart x-coordinate +scada.chart.Chart.prototype._trendXToChartX = function (x) { + return Math.round((x - this._minX) * this._coefX + this._chartLayout.plotAreaLeft); +}; + +// Convert trend y-coordinate to the chart y-coordinate +scada.chart.Chart.prototype._trendYToChartY = function (y) { + return Math.round((this._maxY - y) * this._coefY + this._chartLayout.plotAreaTop); +}; + +// Convert trend x-coordinate to the page x-coordinate +scada.chart.Chart.prototype._trendXToPageX = function (x) { + return Math.round((x - this._minX) * this._coefX + this._chartLayout.absPlotAreaLeft); +}; + +// Convert chart x-coordinate to the trend x-coordinate +scada.chart.Chart.prototype._pageXToTrendX = function (pageX) { + return (pageX - this._chartLayout.absPlotAreaLeft) / this._coefX + this._minX; +}, + +// Convert trend x-coordinate to the date object +scada.chart.Chart.prototype._trendXToDate = function (x) { + return new Date(this.timeRange.startDate + Math.round(x * scada.chart.const.MS_PER_DAY)); +} + +// Get index of the point nearest to the specified page x-coordinate +scada.chart.Chart.prototype._getPointIndex = function (pageX) { + var timePoints = this.chartData.timePoints; + var ptCnt = timePoints.length; + + if (ptCnt < 1) { + return -1; + } else { + var x = this._pageXToTrendX(pageX); + var ptInd = 0; + + if (ptCnt == 1) { + ptInd = 0; + } else { + // binary search + var iL = 0; + var iR = ptCnt - 1; + + if (x < timePoints[iL] || x > timePoints[iR]) + return -1; + + while (iR - iL > 1) { + var iM = Math.floor((iR + iL) / 2); + var xM = timePoints[iM]; + + if (xM == x) + return iM; + else if (xM < x) + iL = iM; + else + iR = iM; + } + + ptInd = x - timePoints[iL] < timePoints[iR] - x ? iL : iR; + } + + return Math.abs(x - timePoints[ptInd]) <= this.displaySettings.chartGap ? ptInd : -1; + } +}; + +// Correct left and right edges of the displayed range to align to the grid +scada.chart.Chart.prototype._alignToGridX = function () { + var gridXStep = this._chartLayout.gridXStep; + this._minX = Math.floor(this._minX / gridXStep) * gridXStep; + this._maxX = Math.ceil(this._maxX / gridXStep) * gridXStep; +} + +// Convert x-coordinate that means time into a date and time string +scada.chart.Chart.prototype._dateTimeToStr = function (t) { + var dateTime = this._trendXToDate(t); + if (scada.utils.iOS()) { + var date = new Date(dateTime.getTime()); + date.setUTCMinutes(date.getUTCMinutes() + date.getTimezoneOffset()); + return date.toLocaleDateString(this.displaySettings.locale, this._DATE_OPTIONS) + ", " + + this._simpleTimeToStr(dateTime); + } else { + return dateTime.toLocaleString(this.displaySettings.locale, this._DATE_TIME_OPTIONS); + } +}; + +// Convert time to a string using manual transformations +scada.chart.Chart.prototype._simpleTimeToStr = function (time, opt_showSeconds) { + var min = time.getUTCMinutes(); + var timeStr = time.getUTCHours() + ":" + (min < 10 ? "0" + min : min); + + if (opt_showSeconds) { + var sec = time.getUTCSeconds(); + timeStr += ":" + (sec < 10 ? "0" + sec : sec); + } + + return timeStr; +} + +// Convert x-coordinate that means time into a time string +scada.chart.Chart.prototype._timeToStr = function (t) { + var time = new Date(Math.round(t * scada.chart.const.MS_PER_DAY)); + return scada.utils.iOS() ? // iOS requires manual time formatting + this._simpleTimeToStr(time) : + time.toLocaleTimeString(this.displaySettings.locale, this._TIME_OPTIONS); +}; + +// Draw pixel on the chart +scada.chart.Chart.prototype._drawPixel = function (x, y, opt_checkBounds) { + if (opt_checkBounds) { + // check if the given coordinates are located within the drawing area + var layout = this._chartLayout; + if (layout.plotAreaLeft <= x && x <= layout.plotAreaRight && + layout.plotAreaTop <= y && y <= layout.plotAreaBottom) { + this._context.fillRect(x, y, 1, 1); + } + } else { + // just draw a pixel + this._context.fillRect(x, y, 1, 1); + } +}, + +// Draw line on the chart +scada.chart.Chart.prototype._drawLine = function (x1, y1, x2, y2, opt_checkBounds) { + if (opt_checkBounds) { + var layout = this._chartLayout; + var minX = Math.min(x1, x2); + var maxX = Math.max(x1, x2); + var minY = Math.min(y1, y2); + var maxY = Math.max(y1, y2); + + if (layout.plotAreaLeft <= minX && maxX <= layout.plotAreaRight && + layout.plotAreaTop <= minY && maxY <= layout.plotAreaBottom) { + opt_checkBounds = false; // the line is fully inside the drawing area + } else if (layout.plotAreaLeft > maxX || minX > layout.plotAreaRight || + layout.plotAreaTop > maxY || minY > layout.plotAreaBottom) { + return; // the line is outside the drawing area + } + } + + var dx = x2 - x1; + var dy = y2 - y1; + + if (dx != 0 || dy != 0) { + if (Math.abs(dx) > Math.abs(dy)) { + var a = dy / dx; + var b = -a * x1 + y1; + + if (dx < 0) { + var x0 = x1; + x1 = x2; + x2 = x0; + } + + for (var x = x1; x <= x2; x++) { + var y = Math.round(a * x + b); + this._drawPixel(x, y, opt_checkBounds); + } + } else { + var a = dx / dy; + var b = -a * y1 + x1; + + if (dy < 0) { + var y0 = y1; + y1 = y2; + y2 = y0; + } + + for (var y = y1; y <= y2; y++) { + var x = Math.round(a * y + b); + this._drawPixel(x, y, opt_checkBounds); + } + } + } +}; + +// Clear the specified rectangle by filling it with the background color +scada.chart.Chart.prototype._clearRect = function (x, y, width, height) { + this._setColor(this._chartLayout.BACK_COLOR); + this._context.fillRect(x, y, width, height); +} + +// Set current drawing color +scada.chart.Chart.prototype._setColor = function (color) { + this._context.fillStyle = this._context.strokeStyle = + color ? color : this._chartLayout.DEF_COLOR; +} + +// Get color of the trend with the specified index +scada.chart.Chart.prototype._getColorByTrend = function (trendInd) { + return this._TREND_COLORS[trendInd % this._TREND_COLORS.length]; +} + +// Draw the chart frame +scada.chart.Chart.prototype._drawFrame = function () { + var layout = this._chartLayout; + var frameL = layout.plotAreaLeft - 1; + var frameR = layout.plotAreaRight + 1; + var frameT = layout.plotAreaTop - 1; + var frameB = layout.plotAreaBottom + 1; + + this._setColor(layout.FRAME_COLOR); + this._drawLine(frameL, frameT, frameL, frameB); + this._drawLine(frameR, frameT, frameR, frameB); + this._drawLine(frameL, frameT, frameR, frameT); + this._drawLine(frameL, frameB, frameR, frameB); +} + +// Draw chart grid of the x-axis +scada.chart.Chart.prototype._drawGridX = function () { + var layout = this._chartLayout; + this._context.textAlign = "center"; + this._context.textBaseline = "middle"; + + var prevLblX = NaN; + var prevLblHalfW = NaN; + var frameB = layout.plotAreaBottom + 1; + var tickT = frameB + 1; + var tickB = frameB + layout.TICK_SIZE; + var lblY = layout.plotAreaBottom + layout.LBL_TB_MARGIN + layout.LINE_HEIGHT / 2; + var lblDateY = lblY + layout.LINE_HEIGHT; + var dayBegTimeText = this._timeToStr(0); + + for (var x = this._minX; x <= this._maxX; x += layout.gridXStep) { + var ptX = this._trendXToChartX(x); + + // vertical grid line + this._setColor(layout.GRID_COLOR); + this._drawLine(ptX, layout.plotAreaTop, ptX, layout.plotAreaBottom); + + // tick + this._setColor(layout.TICK_COLOR); + this._drawLine(ptX, tickT, ptX, tickB); + + // label + this._setColor(layout.LBL_COLOR); + var lblX = ptX; + var timeText = this._timeToStr(x); + var lblHalfW = this._context.measureText(timeText).width / 2; + + if (isNaN(prevLblX) || lblX - lblHalfW > prevLblX + prevLblHalfW + layout.LBL_LR_MARGIN) { + this._context.fillText(timeText, lblX, lblY); + if (this._showDates && timeText == dayBegTimeText) { + this._context.fillText(this.dateToStr(x), lblX, lblDateY); + } + prevLblX = lblX; + prevLblHalfW = lblHalfW; + } + } +}; + +// Draw chart grid of the y-axis +scada.chart.Chart.prototype._drawGridY = function () { + var layout = this._chartLayout; + this._context.textAlign = "right"; + this._context.textBaseline = "middle"; + + var prevLblY = NaN; + var frameL = layout.plotAreaLeft - 1; + var tickL = frameL - layout.TICK_SIZE; + var tickR = frameL - 1; + var lblX = frameL - layout.LBL_LR_MARGIN; + + for (var y = layout.gridYStart; y < this._maxY; y += layout.gridYStep) { + var ptY = this._trendYToChartY(y); + + // horizontal grid line + this._setColor(layout.GRID_COLOR); + this._drawLine(layout.plotAreaLeft, ptY, layout.plotAreaRight, ptY); + + // tick + this._setColor(layout.TICK_COLOR); + this._drawLine(tickL, ptY, tickR, ptY); + + // label + this._setColor(layout.LBL_COLOR); + var lblY = ptY; + if (isNaN(prevLblY) || prevLblY - lblY > layout.LBL_FONT_SIZE) { + this._context.fillText(y.toFixed(layout.gridYDecDig), lblX, lblY); + prevLblY = lblY; + } + } +}; + +// Draw the y-axis title +scada.chart.Chart.prototype._drawYAxisTitle = function () { + if (this.chartData.quantityName) { + var ctx = this._context; + var layout = this._chartLayout; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.save(); + ctx.translate(0, layout.plotAreaBottom); + ctx.rotate(-Math.PI / 2); + ctx.fillText(this.chartData.quantityName, layout.plotAreaHeight / 2, + layout.LEFT_PADDING + layout.LINE_HEIGHT / 2, layout.plotAreaHeight); + ctx.restore(); + } +}; + +// Draw lagand that is the input channel names +scada.chart.Chart.prototype._drawLegend = function () { + var layout = this._chartLayout; + this._context.textAlign = "left"; + this._context.textBaseline = "middle"; + + var lblX = layout.plotAreaLeft + layout.LBL_FONT_SIZE + layout.LBL_LR_MARGIN; + var lblY = layout.plotAreaBottom + layout.LBL_TB_MARGIN + layout.LINE_HEIGHT /*time labels*/ + + (this._showDates ? layout.LINE_HEIGHT : 0) + layout.LBL_TB_MARGIN + layout.LINE_HEIGHT / 2; + var rectSize = layout.LBL_FONT_SIZE; + var rectX = layout.plotAreaLeft - 0.5; + var rectY = lblY - rectSize / 2 - 0.5; + var trendCnt = this.chartData.trends.length; + + for (var trendInd = 0; trendInd < trendCnt; trendInd++) { + var trend = this.chartData.trends[trendInd]; + var legendText = "[" + trend.cnlNum + "] " + trend.cnlName; + + this._setColor(this._getColorByTrend(trendInd)); + this._context.fillRect(rectX, rectY, rectSize, rectSize); + this._setColor(layout.LBL_COLOR); + this._context.strokeRect(rectX, rectY, rectSize, rectSize); + this._context.fillText(legendText, lblX, lblY); + + lblY += layout.LINE_HEIGHT; + rectY += layout.LINE_HEIGHT; + } +} + +// Draw all the trends +scada.chart.Chart.prototype._drawTrends = function (opt_startPtInd) { + var trendCnt = this.chartData.trends.length; + for (var trendInd = 0; trendInd < trendCnt; trendInd++) { + this._drawTrend(this.chartData.timePoints, this.chartData.trends[trendInd], + this._getColorByTrend(trendInd), opt_startPtInd); + } +} + +// Draw the specified trend +scada.chart.Chart.prototype._drawTrend = function (timePoints, trend, color, opt_startPtInd) { + var trendPoints = trend.trendPoints; + var chartGap = this.displaySettings.chartGap; + var VAL_IND = scada.chart.TrendPointIndexes.VAL_IND; + + this._setColor(color); + + var prevX = NaN; + var prevPtX = NaN; + var prevPtY = NaN; + var startPtInd = opt_startPtInd ? opt_startPtInd : 0; + var ptCnt = timePoints.length; + + for (var ptInd = startPtInd; ptInd < ptCnt; ptInd++) { + var pt = trendPoints[ptInd]; + var y = pt[VAL_IND]; + + if (!isNaN(y)) { + var x = timePoints[ptInd]; + var ptX = this._trendXToChartX(x); + var ptY = this._trendYToChartY(y); + + if (isNaN(prevX)) { + } + else if (x - prevX > chartGap) { + this._drawPixel(prevPtX, prevPtY, true); + this._drawPixel(ptX, ptY, true); + } else if (prevPtX != ptX || prevPtY != ptY) { + this._drawLine(prevPtX, prevPtY, ptX, ptY, true); + } + + prevX = x; + prevPtX = ptX; + prevPtY = ptY; + } + } + + if (!isNaN(prevPtX)) + this._drawPixel(prevPtX, prevPtY, true); +} + +// Create a time mark if it doesn't exist +scada.chart.Chart.prototype._initTimeMark = function () { + if (this._timeMark) { + this._timeMark.addClass("hidden"); + } else { + this._timeMark = $(""); + this._canvasJqObj.after(this._timeMark); + } +} + +// Create a trend hint if it doesn't exist +scada.chart.Chart.prototype._initTrendHint = function () { + if (this._trendHint) { + this._trendHint.addClass("hidden"); + } else { + var trendCnt = this.chartData.trends.length; + if (trendCnt > 0) { + this._trendHint = $(""); + var table = this._trendHint.children("table"); + + for (var trendInd = 0; trendInd < trendCnt; trendInd++) { + var trend = this.chartData.trends[trendInd]; + var row = $("" + + ":"); + row.find("td.color span").css("background-color", this._getColorByTrend(trendInd)); + row.children("td.text").text("[" + trend.cnlNum + "] " + trend.cnlName); + table.append(row); + } + + this._canvasJqObj.after(this._trendHint); + } else { + this._trendHint = $(); + } + } +} + +// Show hint with the values nearest to the pointer +scada.chart.Chart.prototype._showHint = function (pageX, pageY, opt_touch) { + var layout = this._chartLayout; + var hideHint = true; + layout.updateAbsCoordinates(this._canvasJqObj); + + if (this._hintEnabled && layout.pointInPlotArea(pageX, pageY)) { + var ptInd = this._getPointIndex(pageX); + + if (ptInd >= 0) { + var x = this.chartData.timePoints[ptInd]; + var ptPageX = this._trendXToPageX(x); + + if (layout.absPlotAreaLeft <= ptPageX && ptPageX <= layout.absPlotAreaRight) { + hideHint = false; + + // set position and show the time mark + this._timeMark + .removeClass("hidden") + .css({ + "left": ptPageX - layout.absCanvasLeft, + "top": layout.canvasTopBorder + layout.plotAreaTop, + "height": layout.plotAreaHeight, + }); + + // set text, position and show the trend hint + this._trendHint.find("div.time").text(this._showDates ? this._dateTimeToStr(x) : this._timeToStr(x)); + var trendCnt = this.chartData.trends.length; + var hintValCells = this._trendHint.find("td.val"); + + for (var trendInd = 0; trendInd < trendCnt; trendInd++) { + var trend = this.chartData.trends[trendInd]; + var trendPoint = trend.trendPoints[ptInd]; + hintValCells.eq(trendInd) + .text(trendPoint[scada.chart.TrendPointIndexes.TEXT_WITH_UNIT_IND]) + .css("color", trendPoint[scada.chart.TrendPointIndexes.COLOR_IND]); + } + + // allow measuring the hint size + this._trendHint + .css({ + "left": 0, + "top": 0, + "visibility": "hidden" + }) + .removeClass("hidden"); + + var hintWidth = this._trendHint.outerWidth(); + var hintHeight = this._trendHint.outerHeight(); + var winScrollLeft = $(window).scrollLeft(); + var winRight = winScrollLeft + $(window).width(); + var chartRight = winScrollLeft + layout.absCanvasLeft + layout.canvasLeftBorder + layout.width; + var maxRight = Math.min(winRight, chartRight); + var absHintLeft = pageX + hintWidth < maxRight ? pageX : Math.max(maxRight - hintWidth, 0); + + this._trendHint.css({ + "left": absHintLeft - layout.absCanvasLeft, + "top": pageY - layout.absCanvasTop - hintHeight - + (opt_touch ? layout.HINT_OFFSET /*above a finger*/ : 0), + "visibility": "" + }); + } + } + } + + if (hideHint) { + this._timeMark.addClass("hidden"); + this._trendHint.addClass("hidden"); + } +}; + +// Draw the chart +scada.chart.Chart.prototype.draw = function () { + if (this._canvasOK && this.displaySettings && this.timeRange && this.chartData) { + // initialize displayed range on the first using + this._initRange(); + + // prepare canvas + var layout = this._chartLayout; + this._canvas.width = this._canvasJqObj.width(); + this._canvas.height = this._canvasJqObj.height(); + this._context = this._canvas.getContext("2d"); + this._context.font = layout.LBL_FONT; + this._initTimeMark(); + this._initTrendHint(); + + // calculate layout + var trendCnt = this.chartData.trends.length; + layout.calculate(this._canvasJqObj, this._context, + this._minX, this._maxX, this._minY, this._maxY, trendCnt, this._showDates); + + this._alignToGridX(); + this._coefX = (layout.plotAreaWidth - 1) / (this._maxX - this._minX); + this._coefY = (layout.plotAreaHeight - 1) / (this._maxY - this._minY); + + // draw chart + this._clearRect(0, 0, layout.width, layout.height); + this._drawFrame(); + this._drawGridX(); + this._drawGridY(); + this._drawYAxisTitle(); + this._drawLegend(); + this._drawTrends(); + } +}; + +// Resume drawing of the chart +scada.chart.Chart.prototype.resume = function (pointInd) { + if (pointInd < this.chartData.timePoints.length) { + if (this._yRangeIsOutdated(pointInd)) { + this._calcYRange(); + this.draw(); + } else { + this._drawTrends(pointInd ? pointInd - 1 : 0); + } + } +} + +// Set displayed time range +scada.chart.Chart.prototype.setRange = function (startX, endX) { + // swap the range if needed + if (startX > endX) { + var xbuf = startX; + startX = endX; + endX = xbuf; + } + + // correct the range + startX = Math.max(startX, this.timeRange.startTime); + endX = Math.min(endX, this.timeRange.endTime); + + // apply the new range + if (startX != endX) { + this._minX = startX; + this._maxX = endX; + this._zoomMode = this._minX > this.timeRange.startTime || this._maxX < this.timeRange.endTime; + this._calcYRange(); + this.draw(); + } +} + +// Reset displayed time range according to the chart time range +scada.chart.Chart.prototype.resetRange = function () { + this._initRange(true); + this.draw(); +} + +// Convert x-coordinate that means time into a date string +scada.chart.Chart.prototype.dateToStr = function (t) { + var date = this._trendXToDate(t); + if (scada.utils.iOS()) { + date.setUTCMinutes(date.getUTCMinutes() + date.getTimezoneOffset()); + } + return date.toLocaleDateString(this.displaySettings.locale, this._DATE_OPTIONS); +}; + +// Convert x-coordinate that means time into a time string ignoring culture with high performance +scada.chart.Chart.prototype.fastTimeToStr = function (t, opt_showSeconds) { + var time = new Date(Math.round(t * scada.chart.const.MS_PER_DAY)); + return this._simpleTimeToStr(time, opt_showSeconds); +} + +// Bind events to allow hints +scada.chart.Chart.prototype.bindHintEvents = function () { + if (this._canvasJqObj.length) { + var thisObj = this; + + $(this._canvasJqObj.parent()) + .off(".scada.chart.hint") + .on("mousemove.scada.chart.hint touchstart.scada.chart.hint touchmove.scada.chart.hint", function (event) { + var touch = false; + var stopEvent = false; + + if (event.type == "touchstart") { + event = event.originalEvent.touches[0]; + touch = true; + } + else if (event.type == "touchmove") { + $(this).off("mousemove"); + event = event.originalEvent.touches[0]; + touch = true; + stopEvent = true; + } + + thisObj._showHint(event.pageX, event.pageY, touch); + return !stopEvent; + }); + } +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/js/chartdialog.js b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/js/chartdialog.js new file mode 100644 index 000000000..296095d32 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/js/chartdialog.js @@ -0,0 +1,28 @@ +/* + * Chart object that implements displaying charts + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + * + * Requires: + * - utils.js + */ + +// Rapid SCADA namespace +var scada = scada || {}; +// Chart namespace +scada.chart = scada.chart || {}; + +scada.chart.dialog = { + // Get chart URL + getChartUrl: function (cnlNums, viewIDs, date) { + return "plugins/Chart/Chart.aspx?cnlNum=" + cnlNums + "&viewID=" + viewIDs + + "&" + scada.utils.dateToQueryString(date); + }, + + // Open chart in the new tab + show: function (rootPath, cnlNums, viewIDs, date) { + window.open(rootPath + this.getChartUrl(cnlNums, viewIDs, date)); + } +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/js/chartform.js b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/js/chartform.js new file mode 100644 index 000000000..5987513a4 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/js/chartform.js @@ -0,0 +1,24 @@ +// Set the chart width and height +function updateLayout() { + var BODY_PADDING = 20; + $("#cnvChart") + .outerWidth($(window).width() - BODY_PADDING) + .outerHeight($(window).height() - $("#divHeader").outerHeight() - BODY_PADDING); +} + +$(document).ready(function () { + // chart parameters must be defined in Chart.aspx + var chart = new scada.chart.Chart($("#cnvChart")); + chart.displaySettings = displaySettings; + chart.timeRange = timeRange; + chart.chartData = chartData; + + updateLayout(); + chart.draw(); + chart.bindHintEvents(); + + $(window).resize(function () { + updateLayout(); + chart.draw(); + }); +}); \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/js/mindatarep.js b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/js/mindatarep.js new file mode 100644 index 000000000..84c4c63d0 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/js/mindatarep.js @@ -0,0 +1,80 @@ +// Duration of locking the generate report button, ms +var GEN_BTN_LOCK_DURATION = 3000; + +// Select begin or end date using a calendar popup +function selectDate(inputElem, buttonElem) { + scada.dialogs.showCalendar(buttonElem, inputElem.val(), function (dialogResult, extraParams) { + if (dialogResult) { + inputElem.val(extraParams.dateStr); + } + }); +} + +// Show modal dialog to select the channels +function showSelectCnlsModal() { + popup.showModal("SelectCnls.aspx", + new scada.ModalOptions([scada.ModalButtons.OK, scada.ModalButtons.CANCEL]), + function (dialogResult, extraParams) { + if (dialogResult) { + // perform adding channels + if (extraParams.cnlNums && extraParams.viewIDs) { + $("#hidAddedCnlNums").val(extraParams.cnlNums); + $("#hidAddedViewIDs").val(extraParams.viewIDs); + $("#btnApplyAddedCnls").click(); + } + } + }); +} + +// Start report generation +function generateReport(cnlNums, viewIDs, year, month, day, period) { + lockGenerateButton(); + window.location = "MinDataRepOut.aspx?cnlNums=" + cnlNums + "&viewIDs=" + viewIDs + + "&year=" + year + "&month=" + month + "&day=" + day + "&period=" + period; +} + +// Lock the generate report button after click +function lockGenerateButton() { + $("#btnGenReport").prop("disabled", true); + $("#lblGenStarted").removeClass("hidden"); + + setTimeout(function () { + $("#btnGenReport").prop("disabled", false); + $("#lblGenStarted").addClass("hidden"); + }, GEN_BTN_LOCK_DURATION) +} + +// Initialize Bootstrap popovers +function initPopovers() { + $('[data-toggle="popover"]').popover({ html: true }); +} + +// Bind the form events +function bindEvents() { + // open a calendar popup on a calendar button click + $("button.calendar") + .off("click") + .on("click", function () { + selectDate($(this).parent().siblings("input"), $(this)); + }); + + // open a select channels popup on the appropriate button click + $("#btnAddCnls") + .off("click") + .on("click", function () { + showSelectCnlsModal(); + return false; + }); +} + +// Do actions after asynchronous request +function asyncEndRequest() { + initPopovers(); + bindEvents(); + scada.utils.scrollTo($(".main-content:first"), $(".alert")); +} + +$(document).ready(function () { + initPopovers(); + bindEvents(); +}); \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/js/selectcnls.js b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/js/selectcnls.js new file mode 100644 index 000000000..10e26e6a8 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/js/selectcnls.js @@ -0,0 +1,41 @@ +// Popup dialogs manipulation object +var popup = scada.popupLocator.getPopup(); + +// Update the modal dialog height according to a frame height +function updateModalHeight() { + if (popup) { + setTimeout(popup.updateModalHeight, 0, window); + } +} + +// Close the modal with successful result +function closeModal(cnlNums, viewIDs) { + if (popup) { + popup.closeModal(window, true, { cnlNums: cnlNums, viewIDs: viewIDs }); + } +} + +$(document).ready(function () { + // initialize Bootstrap popovers + $('[data-toggle="popover"]').popover({ html: true }); + + // disable OK button according to the submit button state + if (popup) { + var enabled = !$("#btnSubmit").is(":disabled"); + popup.setButtonEnabled(window, scada.ModalButtons.OK, enabled); + } + + // submit the form on OK button click + $(window).on(scada.EventTypes.MODAL_BTN_CLICK, function (event, result) { + if (result == scada.ModalButtons.OK) { + $("#btnSubmit").click(); + } + }); + + // show "loading" message on change a view + $("#ddlView").change(function () { + $(".cnl-list").addClass("hidden"); + $(".cnl-list-msg").addClass("hidden"); + $(".cnl-list-msg.loading").removeClass("hidden"); + }); +}); \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/lang/PlgChart.en-GB.xml b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/lang/PlgChart.en-GB.xml new file mode 100644 index 000000000..785a312ef --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/lang/PlgChart.en-GB.xml @@ -0,0 +1,42 @@ + + + + Channel numbers are not specified. + Mismatch in number of channels and view IDs. + Warning! Too many channels may affect performance. + Add + Remove + Info + Object: + Device: + View: + + + Minute data + Generated: + Input channels: + Time + Channel {0} + + + Chart - Rapid SCADA + Generated + + + Minute Data Report - Rapid SCADA + Minute Data Report + From + To + Add Channels + Selected Channels: + Input channels are not selected + Download Report + Generating the report. Please wait... + + + Select Channels - Rapid SCADA + Loading... + Unable to load view + Input channels not found + + diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/lang/PlgChart.ru-RU.xml b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/lang/PlgChart.ru-RU.xml new file mode 100644 index 000000000..b43906d1d --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/lang/PlgChart.ru-RU.xml @@ -0,0 +1,42 @@ + + + + Номера каналов не заданы. + Несоответствие количества каналов и ид. представлений. + Предупреждение! Слишком много каналов могут снизить производительность. + Добавить + Удалить + Инфо + Объект: + КП: + Представление: + + + Минутные данные + Получено: + Входные каналы: + Время + Канал {0} + + + График - Rapid SCADA + Получено + + + Отчёт по минутным данным - Rapid SCADA + Отчёт по минутным данным + Начало + Окончание + Добавить каналы + Выбранные каналы: + Входные каналы не выбраны + Скачать отчёт + Отчёт формируется. Пожалуйста, подождите... + + + Выбор каналов - Rapid SCADA + Загрузка... + Не удалось загрузить представление + Входные каналы не найдены + + diff --git a/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/templates/MinDataRep.xml b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/templates/MinDataRep.xml new file mode 100644 index 000000000..1be8ae5fb --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/plugins/Chart/templates/MinDataRep.xml @@ -0,0 +1,141 @@ + + + + + Rapid SCADA + Rapid SCADA + 2016-02-19T09:00:44Z + 14.00 + + + + + + 11760 + 15195 + 480 + 120 + False + False + + + + + + + + + + + + + + + repVal=Title + + + repVal=Gen + + + repVal=CnlsCaption + + + repRow=CnlRow repVal=CnlNum + repVal=CnlName + + + + + + repVal=TimeCol + repVal=ValCol + + + repVal=Date + + + + repVal=Time + repVal=Val + + + + + + + + + + + 0 + + 9 + 600 + 600 + + 75 + + + + 6 + 6 + 2 + + + 3 + + + 2 + 0 + + + False + False + + + diff --git a/ScadaWeb/OpenPlugins/PlgChart/test/ChartTest.html b/ScadaWeb/OpenPlugins/PlgChart/test/ChartTest.html new file mode 100644 index 000000000..a35c0055d --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChart/test/ChartTest.html @@ -0,0 +1,71 @@ + + + + Chart Test - Rapid SCADA + + + + + + + + + Upgrade the browser to display chart. + + diff --git a/ScadaWeb/OpenPlugins/PlgChartCommon/ChartDataBuilder.cs b/ScadaWeb/OpenPlugins/PlgChartCommon/ChartDataBuilder.cs new file mode 100644 index 000000000..36f1c945e --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChartCommon/ChartDataBuilder.cs @@ -0,0 +1,405 @@ +/* + * Copyright 2016 Mikhail Shiryaev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Product : Rapid SCADA + * Module : PlgChartCommon + * Summary : Builds JavaScript of chart properties and data + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + */ + +using Scada.Client; +using Scada.Data.Models; +using Scada.Data.Tables; +using System; +using System.Globalization; +using System.Text; +using System.Web; + +namespace Scada.Web.Plugins.Chart +{ + /// + /// Builds JavaScript of chart properties and data + /// Строит JavaScript свойств и данных графика + /// + public class ChartDataBuilder + { + private int cnlCnt; // количество каналов + + + /// + /// Обеспечивает форматирование данных входных каналов + /// + protected readonly DataFormatter dataFormatter; + /// + /// Объект для доступа к данным + /// + protected readonly DataAccess dataAccess; + + /// + /// Номера каналов отображаемого графика + /// + protected int[] cnlNums; + /// + /// Начальная дата отображаемых данных + /// + protected DateTime startDate; + /// + /// Период отображаемых данных, дн. + /// + /// Если период отрицательный, то используется интервал времени влево от начальной даты + protected int period; + /// + /// Расстояние между разделяемыми точками графика, с + /// + protected int chartGap; + + /// + /// Свойства каналов отображаемого графика + /// + protected InCnlProps[] cnlPropsArr; + /// + /// Имена каналов отображаемого графика + /// + protected string[] cnlNames; + /// + /// Имя величины с указанием размерности, общее для всех каналов + /// + protected string quantityName; + /// + /// Одиночный тренд + /// + /// Если количество каналов равно 1 + protected Trend singleTrend; + /// + /// Связка трендов + /// + /// Если количество каналов больше 1 + protected TrendBundle trendBundle; + + + /// + /// Конструктор, ограничивающий создание объекта без параметров + /// + protected ChartDataBuilder() + { + } + + /// + /// Конструктор + /// + public ChartDataBuilder(int[] cnlNums, DateTime startDate, int period, int chartGap, DataAccess dataAccess) + { + dataFormatter = new DataFormatter(); + this.dataAccess = dataAccess; + + this.cnlNums = cnlNums; + this.startDate = startDate; + this.period = period; + this.chartGap = chartGap; + RepUtils.NormalizeTimeRange(ref this.startDate, ref this.period); + + cnlCnt = cnlNums.Length; + cnlPropsArr = new InCnlProps[cnlCnt]; + cnlNames = new string[cnlCnt]; + quantityName = ""; + singleTrend = null; + trendBundle = null; + } + + + /// + /// Получить нормализованную начальную дату отображаемых данных + /// + public DateTime StartDate + { + get + { + return startDate; + } + } + + /// + /// Получить нормализованный период отображаемых данных + /// + public int Period + { + get + { + return period; + } + } + + + /// + /// Получить имя величины с указанием размерности + /// + protected string GetQuantityName(string paramName, string singleUnit) + { + return !string.IsNullOrEmpty(paramName) && !string.IsNullOrEmpty(singleUnit) ? + paramName + ", " + singleUnit : + paramName + singleUnit; + } + + /// + /// Заполнить свойства каналов и определить имя величины + /// + protected void FillCnlProps() + { + quantityName = ""; + bool quantityIsInited = false; + bool quantitiesAreEqual = true; + + for (int i = 0; i < cnlCnt; i++) + { + InCnlProps cnlProps = dataAccess.GetCnlProps(cnlNums[i]); + cnlPropsArr[i] = cnlProps; + + if (cnlProps == null) + { + cnlNames[i] = ""; + } + else + { + cnlNames[i] = cnlProps.CnlName; + + if (quantitiesAreEqual) + { + string qname = GetQuantityName(cnlProps.ParamName, cnlProps.SingleUnit); + + if (!quantityIsInited) + { + quantityName = qname; + quantityIsInited = true; + } + + if (quantityName != qname) + { + quantityName = ""; + quantitiesAreEqual = false; + } + } + } + } + } + + /// + /// Заполнить данные одиночного тренда + /// + protected void FillSingleTrend() + { + singleTrend = cnlCnt > 0 ? dataAccess.DataCache.GetMinTrend(startDate, cnlNums[0]) : null; + trendBundle = null; + } + + /// + /// Заполнить данные связки трендов + /// + protected void FillTrendBundle() + { + singleTrend = null; + trendBundle = new TrendBundle(); + + Trend[] trends = new Trend[cnlCnt]; + for (int i = 0; i < cnlCnt; i++) + trends[i] = dataAccess.DataCache.GetMinTrend(startDate, cnlNums[i]); + + trendBundle.Init(trends); + } + + /// + /// Преобразовать точку тренда в запись JavaScript + /// + protected string TrendPointToJs(double val, int stat, InCnlProps cnlProps) + { + string text; + string textWithUnit; + dataFormatter.FormatCnlVal(val, stat, cnlProps, out text, out textWithUnit); + CnlStatProps cnlStatProps = dataAccess.GetCnlStatProps(stat); + string color = dataFormatter.GetCnlValColor(val, stat, cnlProps, cnlStatProps); + + // для text и textWithUnit было бы корректно использовать метод HttpUtility.JavaScriptStringEncode(), + // но он опускается для повышения скорости + double chartVal = stat > 0 ? val : double.NaN; + return (new StringBuilder("[") + .Append(double.IsNaN(chartVal) ? "NaN" : chartVal.ToString(CultureInfo.InvariantCulture)) + .Append(", \"") + .Append(text) + .Append("\", \"") + .Append(textWithUnit) + .Append("\", \"") + .Append(color) + .Append("\"]")).ToString(); + } + + /// + /// Получить точки одиночного тренда в виде JavaScript + /// + protected string GetTrendPointsJs(Trend trend, InCnlProps cnlProps) + { + StringBuilder sbTrendPoints = new StringBuilder("["); + + if (trend != null) + { + foreach (Trend.Point point in trend.Points) + { + sbTrendPoints + .Append(TrendPointToJs(point.Val, point.Stat, cnlProps)) + .Append(", "); + } + } + + sbTrendPoints.Append("]"); + return sbTrendPoints.ToString(); + } + + /// + /// Получить точки связки трендов в виде JavaScript + /// + protected string GetTrendPointsJs(TrendBundle trendBundle, int trendInd) + { + StringBuilder sbTrendPoints = new StringBuilder("["); + InCnlProps cnlProps = cnlPropsArr[trendInd]; + + if (trendBundle != null) + { + foreach (TrendBundle.Point point in trendBundle.Series) + { + SrezTableLight.CnlData cnlData = point.CnlData[trendInd]; + sbTrendPoints + .Append(TrendPointToJs(cnlData.Val, cnlData.Stat, cnlProps)) + .Append(", "); + } + } + + sbTrendPoints.Append("]"); + return sbTrendPoints.ToString(); + } + + /// + /// Получить метки времени одиночного тренда в виде JavaScript + /// + protected string GetTimePointsJs(Trend trend) + { + + StringBuilder sbTimePoints = new StringBuilder("["); + + if (trend != null) + { + foreach (Trend.Point point in trend.Points) + { + double time = point.DateTime.TimeOfDay.TotalDays; + sbTimePoints.Append(time.ToString(CultureInfo.InvariantCulture)).Append(", "); + } + } + + sbTimePoints.Append("]"); + return sbTimePoints.ToString(); + } + + /// + /// Получить метки времени связки трендов в виде JavaScript + /// + protected string GetTimePointsJs(TrendBundle trendBundle) + { + StringBuilder sbTimePoints = new StringBuilder("["); + + if (trendBundle != null) + { + foreach (TrendBundle.Point point in trendBundle.Series) + { + double time = point.DateTime.TimeOfDay.TotalDays; + sbTimePoints.Append(time.ToString(CultureInfo.InvariantCulture)).Append(", "); + } + } + + sbTimePoints.Append("]"); + return sbTimePoints.ToString(); + } + + + /// + /// Заполнить данные графика только за нормализованную начальную дату + /// + public void FillData() + { + FillCnlProps(); + + if (cnlCnt <= 1) + FillSingleTrend(); + else + FillTrendBundle(); + } + + /// + /// Преобразовать свойства графика в JavaScript + /// + public string ToJs() + { + StringBuilder sbJs = new StringBuilder(); + + // настройки отображения + sbJs + .AppendLine("var displaySettings = new scada.chart.DisplaySettings();") + .Append("displaySettings.locale = '").Append(Localization.Culture.Name).AppendLine("';") + .Append("displaySettings.chartGap = ").Append(chartGap).AppendLine(" / scada.chart.const.SEC_PER_DAY;") + .AppendLine(); + + // интервал времени + sbJs + .AppendLine("var timeRange = new scada.chart.TimeRange();") + .Append("timeRange.startDate = Date.UTC(") + .AppendFormat("{0}, {1}, {2}", startDate.Year, startDate.Month - 1, startDate.Day).AppendLine(");") + .AppendLine("timeRange.startTime = 0;") + .Append("timeRange.endTime = ").Append(period).AppendLine(";") + .AppendLine(); + + // данные графика + StringBuilder sbTrends = new StringBuilder("["); + bool single = singleTrend != null; + + for (int i = 0; i < cnlCnt; i++) + { + string trendName = "trend" + i; + sbTrends.Append(trendName).Append(", "); + + sbJs + .Append("var ").Append(trendName).AppendLine(" = new scada.chart.TrendExt();") + .Append(trendName).Append(".cnlNum = ").Append(cnlNums[i]).AppendLine(";") + .Append(trendName).Append(".cnlName = '") + .Append(HttpUtility.JavaScriptStringEncode(cnlNames[i])).AppendLine("';") + .Append(trendName).Append(".trendPoints = ") + .Append(single ? GetTrendPointsJs(singleTrend, cnlPropsArr[i]) : GetTrendPointsJs(trendBundle, i)) + .AppendLine(";") + .AppendLine(); + } + + sbTrends.Append("];"); + + sbJs + .AppendLine("var chartData = new scada.chart.ChartData();") + .Append("chartData.timePoints = ") + .Append(single ? GetTimePointsJs(singleTrend) : GetTimePointsJs(trendBundle)).AppendLine(";") + .Append("chartData.trends = ").Append(sbTrends).AppendLine() + .Append("chartData.quantityName = '") + .Append(HttpUtility.JavaScriptStringEncode(quantityName)).AppendLine("';"); + + return sbJs.ToString(); + } + } +} diff --git a/ScadaWeb/OpenPlugins/PlgChartCommon/ChartPhrases.cs b/ScadaWeb/OpenPlugins/PlgChartCommon/ChartPhrases.cs new file mode 100644 index 000000000..7fee364d3 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChartCommon/ChartPhrases.cs @@ -0,0 +1,82 @@ +/* + * Copyright 2016 Mikhail Shiryaev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Product : Rapid SCADA + * Module : PlgChartCommon + * Summary : The phrases used by chart plugins + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + */ + +#pragma warning disable 1591 // отключение warning CS1591: Missing XML comment for publicly visible type or member + +namespace Scada.Web.Plugins.Chart +{ + /// + /// The phrases used by chart plugins + /// Фразы, используемые плагинами графиков + /// + public static class ChartPhrases + { + static ChartPhrases() + { + SetToDefault(); + } + + // Словарь Scada.Web.Plugins.Chart + public static string CnlNumsEmptyError { get; private set; } + public static string CountMismatchError { get; private set; } + public static string PerfWarning { get; private set; } + public static string AddCnlBtn { get; private set; } + public static string RemoveCnlBtn { get; private set; } + public static string CnlInfoBtn { get; private set; } + public static string ObjectHint { get; private set; } + public static string DeviceHint { get; private set; } + public static string ViewHint { get; private set; } + + private static void SetToDefault() + { + CnlNumsEmptyError = Localization.Dict.GetEmptyPhrase("CnlNumsEmptyError"); + CountMismatchError = Localization.Dict.GetEmptyPhrase("CountMismatchError"); + PerfWarning = Localization.Dict.GetEmptyPhrase("PerfWarning"); + AddCnlBtn = Localization.Dict.GetEmptyPhrase("AddCnlBtn"); + RemoveCnlBtn = Localization.Dict.GetEmptyPhrase("RemoveCnlBtn"); + CnlInfoBtn = Localization.Dict.GetEmptyPhrase("CnlInfoBtn"); + ObjectHint = Localization.Dict.GetEmptyPhrase("ObjectHint"); + DeviceHint = Localization.Dict.GetEmptyPhrase("DeviceHint"); + ViewHint = Localization.Dict.GetEmptyPhrase("ViewHint"); + } + + public static void Init() + { + Localization.Dict dict; + if (Localization.Dictionaries.TryGetValue("Scada.Web.Plugins.Chart", out dict)) + { + CnlNumsEmptyError = dict.GetPhrase("CnlNumsEmptyError", CnlNumsEmptyError); + CountMismatchError = dict.GetPhrase("CountMismatchError", CountMismatchError); + PerfWarning = dict.GetPhrase("PerfWarning", PerfWarning); + AddCnlBtn = dict.GetPhrase("AddCnlBtn", AddCnlBtn); + RemoveCnlBtn = dict.GetPhrase("RemoveCnlBtn", RemoveCnlBtn); + CnlInfoBtn = dict.GetPhrase("CnlInfoBtn", CnlInfoBtn); + ObjectHint = dict.GetPhrase("ObjectHint", ObjectHint); + DeviceHint = dict.GetPhrase("DeviceHint", DeviceHint); + ViewHint = dict.GetPhrase("ViewHint", ViewHint); + } + } + } +} diff --git a/ScadaWeb/OpenPlugins/PlgChartCommon/ChartUtils.cs b/ScadaWeb/OpenPlugins/PlgChartCommon/ChartUtils.cs new file mode 100644 index 000000000..b4002c5c3 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChartCommon/ChartUtils.cs @@ -0,0 +1,175 @@ +/* + * Copyright 2016 Mikhail Shiryaev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Product : Rapid SCADA + * Module : PlgChartCommon + * Summary : The class contains utility methods for charts + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + */ + +using Scada.Client; +using Scada.Web.Shell; +using System; +using System.Collections.Generic; +using System.Text; +using System.Web.UI; +using System.Web.UI.WebControls; + +namespace Scada.Web.Plugins.Chart +{ + /// + /// The class contains utility methods for charts + /// Класс, содержащий вспомогательные методы для графиков + /// + public static class ChartUtils + { + /// + /// Количество графиков, выше которого могут быть проблемы с производительностью + /// + public const int NormalChartCnt = 10; + + + /// + /// Проверить корректность заданных массивов + /// + public static void CheckArrays(int[] cnlNums, int[] viewIDs) + { + if (cnlNums == null) + throw new ArgumentNullException("cnlNums"); + + if (viewIDs == null) + throw new ArgumentNullException("viewIDs"); + + if (cnlNums.Length == 0) + throw new ArgumentException(ChartPhrases.CnlNumsEmptyError); + + if (cnlNums.Length != viewIDs.Length) + throw new ScadaException(ChartPhrases.CountMismatchError); + } + + /// + /// Получить можество номеров канала из списка пар канал/представление + /// + public static HashSet GetCnlSet(List cnlViewPairs) + { + HashSet cnlSet = new HashSet(); + foreach (CnlViewPair pair in cnlViewPairs) + cnlSet.Add(pair.CnlNum); + return cnlSet; + } + + /// + /// Получить выбранные каналы и соответствующие им представления из списка + /// + public static void GetSelection(this List cnlViewPairs, out string cnlNums, out string viewIDs) + { + StringBuilder sbCnlNums = new StringBuilder(); + StringBuilder sbViewIDs = new StringBuilder(); + + for (int i = 0, lastInd = cnlViewPairs.Count - 1; i <= lastInd; i++) + { + CnlViewPair pair = cnlViewPairs[i]; + sbCnlNums.Append(pair.CnlNum); + sbViewIDs.Append(pair.ViewID); + + if (i < lastInd) + { + sbCnlNums.Append(","); + sbViewIDs.Append(","); + } + } + + cnlNums = sbCnlNums.ToString(); + viewIDs = sbViewIDs.ToString(); + } + + /// + /// Получить список пар канал/представление по ид. представления + /// + public static List GetCnlViewPairsByView( + int viewID, DataAccess dataAccess, ViewCache viewCache, UserViews userViews) + { + BaseView view = null; + + if (viewID > 0) + { + Type viewType = userViews.GetViewType(viewID); + view = viewCache.GetView(viewType, viewID); + } + + if (view == null) + { + return null; + } + else + { + List cnlsByView = new List(); + foreach (int cnlNum in view.CnlList) + { + CnlViewPair pair = new CnlViewPair(cnlNum, 0); + pair.FillInfo(dataAccess.GetCnlProps(cnlNum), null); + cnlsByView.Add(pair); + } + return cnlsByView; + } + } + + /// + /// Заполнить выпадающий список представлений + /// + public static void FillViewList(DropDownList ddlView, int preferableViewID, UserViews userViews) + { + int selInd1 = -1; // индекс выбранного элемента, соответствующего непустому представлению + int selInd2 = -1; // индекс выбранного элемента, соответствующего предпочтительному представлению + List viewNodes = userViews.GetLinearViewNodes(); + int viewNodesCnt = viewNodes.Count; + + // заполнение списка представлений и определение индексов выбранного элемента + ddlView.Items.Clear(); + for (int i = 0; i < viewNodesCnt; i++) + { + ViewNode viewNode = viewNodes[i]; + + if (selInd1 <= 0 && !viewNode.IsEmpty) + selInd1 = i; + if (selInd2 <= 0 && preferableViewID > 0 && viewNode.ViewID == preferableViewID) + selInd2 = i; + + string text = new string('-', viewNode.Level) + " " + viewNode.Text; + ddlView.Items.Add(new ListItem(text, viewNode.ViewID.ToString())); + } + + // установка выбранного элемента + if (selInd2 >= 0) + ddlView.SelectedIndex = selInd2; + else if (selInd1 >= 0) + ddlView.SelectedIndex = selInd1; + } + + /// + /// Добавить на страницу скрипт обновления высоты диалогового окна + /// + public static void AddUpdateModalHeightScript(Page page) + { + if (!page.ClientScript.IsStartupScriptRegistered(page.GetType(), "UpdateModalHeightScript")) + page.ClientScript.RegisterStartupScript( + page.GetType(), "UpdateModalHeightScript", "updateModalHeight();", true); + } + } +} diff --git a/ScadaWeb/OpenPlugins/PlgChartCommon/CnlViewPair.cs b/ScadaWeb/OpenPlugins/PlgChartCommon/CnlViewPair.cs new file mode 100644 index 000000000..0b62ed12b --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChartCommon/CnlViewPair.cs @@ -0,0 +1,114 @@ +/* + * Copyright 2016 Mikhail Shiryaev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Product : Rapid SCADA + * Module : PlgChartCommon + * Summary : Defines a input channel/view pair + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + */ + +using Scada.Data.Models; +using Scada.Web.Shell; +using System; +using System.Text; + +namespace Scada.Web.Plugins.Chart +{ + /// + /// Defines a input channel/view pair + /// Определяет пару входной канал/представление + /// + [Serializable] + public class CnlViewPair + { + /// + /// Конструктор + /// + public CnlViewPair() + : this(0, 0) + { + } + + /// + /// Конструктор + /// + public CnlViewPair(int cnlNum, int viewID) + { + CnlNum = cnlNum; + ViewID = viewID; + CnlName = ""; + Info = ""; + } + + + /// + /// Получить или установить номер входного канала + /// + public int CnlNum { get; set; } + + /// + /// Получить или установить ид. представления + /// + public int ViewID { get; set; } + + + /// + /// Получить или установить наименование входного канала + /// + public string CnlName { get; set; } + + /// + /// Получить или установить информацию о канале и представлении + /// + public string Info { get; set; } + + + /// + /// Заполнить наименование канала и информацию + /// + public void FillInfo(InCnlProps cnlProps, UserViews userViews) + { + StringBuilder sbInfo = new StringBuilder(); + + if (cnlProps == null) + { + CnlName = ""; + } + else + { + CnlName = cnlProps.CnlName; + if (cnlProps.ObjNum > 0) + sbInfo.Append(ChartPhrases.ObjectHint).Append("[").Append(cnlProps.ObjNum).Append("] ") + .AppendLine(cnlProps.ObjName); + if (cnlProps.KPNum > 0) + sbInfo.Append(ChartPhrases.DeviceHint).Append("[").Append(cnlProps.KPNum).Append("] ") + .AppendLine(cnlProps.KPName); + } + + if (ViewID > 0 && userViews != null) + { + ViewNode viewNode = userViews.GetViewNode(ViewID); + if (viewNode != null) + sbInfo.Append(ChartPhrases.ViewHint).Append(viewNode.Text); + } + + Info = sbInfo.ToString().TrimEnd(); + } + } +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChartCommon/PlgChartCommon.csproj b/ScadaWeb/OpenPlugins/PlgChartCommon/PlgChartCommon.csproj new file mode 100644 index 000000000..523ae8c23 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChartCommon/PlgChartCommon.csproj @@ -0,0 +1,65 @@ + + + + + Debug + AnyCPU + {827AC062-9C5E-429F-ABBF-368E84CEC9B7} + Library + Properties + Scada.Web.Plugins.Chart + PlgChartCommon + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + bin\Release\PlgChartCommon.XML + + + + ..\..\..\Log\bin\Release\Log.dll + + + ..\..\..\ScadaData\ScadaData\bin\Release\ScadaData.dll + + + ..\..\ScadaWebCommon5\bin\Release\ScadaWebCommon5.dll + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChartCommon/Properties/AssemblyInfo.cs b/ScadaWeb/OpenPlugins/PlgChartCommon/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..c18306536 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChartCommon/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PlgChartCommon")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("PlgChartCommon")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("827ac062-9c5e-429f-abbf-368e84cec9b7")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ScadaWeb/OpenPlugins/PlgChartCommon/RightsChecker.cs b/ScadaWeb/OpenPlugins/PlgChartCommon/RightsChecker.cs new file mode 100644 index 000000000..2fce73859 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChartCommon/RightsChecker.cs @@ -0,0 +1,203 @@ +/* + * Copyright 2016 Mikhail Shiryaev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Product : Rapid SCADA + * Module : PlgChartCommon + * Summary : Checks access rights for a chart + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + */ + +using Scada.Client; +using System; + +namespace Scada.Web.Plugins.Chart +{ + /// + /// Checks access rights for a chart + /// Проверяет права доступа к графику + /// + public class RightsChecker + { + /// + /// Кэш представлений + /// + protected readonly ViewCache viewCache; + + /// + /// Данные пользователя приложения + /// + protected UserData userData; + /// + /// Права пользователя веб-приложения + /// + protected UserRights userRights; + + + /// + /// Конструктор, ограничивающий создание объекта без параметров + /// + protected RightsChecker() + { + } + + /// + /// Конструктор + /// + public RightsChecker(ViewCache viewCache) + { + if (viewCache == null) + throw new ArgumentNullException("viewCache"); + + this.viewCache = viewCache; + } + + + /// + /// Проверить, что все элементы непустого массива равны + /// + protected bool ElementsEqual(int[] arr) + { + int first = arr[0]; + int len = arr.Length; + for (int i = 1; i < len; i++) + if (arr[i] != first) + return false; + return true; + } + + /// + /// Получить представление по идентификатору + /// + protected BaseView GetView(int viewID) + { +#if DEBUG + return null; +#else + if (userData == null) + { + // получение представления из кеша для WCF-сервиса + return viewCache.GetViewFromCache(viewID, true); + } + else + { + // получение представления из кеша или от сервера для веб-формы + Type viewType = userData.UserViews.GetViewType(viewID); + return viewCache.GetView(viewType, viewID, true); + } +#endif + } + + /// + /// Проверить права на представления и принадлежность каналов + /// + protected void CheckRights(int[] cnlNums, int[] viewIDs, out BaseView singleView) + { + if (ElementsEqual(viewIDs)) + { + CheckRights(cnlNums, viewIDs[0], out singleView); + } + else + { + singleView = null; + int cnlCnt = cnlNums.Length; + for (int i = 0; i < cnlCnt; i++) + CheckRights(cnlNums[i], viewIDs[i]); + } + } + + /// + /// Проверить права на одно представление и принадлежность каналов + /// + protected void CheckRights(int[] cnlNums, int viewID, out BaseView singleView) + { + if (!userRights.GetUiObjRights(viewID).ViewRight) + throw new ScadaException(CommonPhrases.NoRights); + +#if DEBUG + singleView = null; +#else + singleView = GetView(viewID); + if (!singleView.ContainsAllCnls(cnlNums)) + throw new ScadaException(CommonPhrases.NoRights); +#endif + } + + /// + /// Проверить права на одно представление и принадлежность одного канала + /// + protected void CheckRights(int cnlNum, int viewID) + { + if (!userRights.GetUiObjRights(viewID).ViewRight) + throw new ScadaException(CommonPhrases.NoRights); + +#if !DEBUG + BaseView view = GetView(viewID); + if (!view.ContainsCnl(cnlNum)) + throw new ScadaException(CommonPhrases.NoRights); +#endif + } + + + /// + /// Проверить заданные права на возможность просмотра входных каналов, входящих в указанные представления + /// + /// Если проверка не пройдена, вызывается исключение + public void CheckRights(UserRights userRights, int[] cnlNums, int[] viewIDs) + { + if (userRights == null) + throw new ArgumentNullException("userRights"); + + ChartUtils.CheckArrays(cnlNums, viewIDs); + userData = null; + this.userRights = userRights; + + if (!userRights.ViewAllRight) + { + BaseView singleView; + CheckRights(cnlNums, viewIDs, out singleView); + } + } + + /// + /// Проверить права текущего пользователя на просмотр входных каналов, входящих в указанные представления, + /// а также получить представление, если оно единственное + /// + /// Если проверка не пройдена, вызывается исключение + public void CheckRights(UserData userData, int[] cnlNums, int[] viewIDs, out BaseView singleView) + { + if (userData == null) + throw new ArgumentNullException("userData"); + if (!userData.LoggedOn) + throw new ScadaException(WebPhrases.NotLoggedOn); + ChartUtils.CheckArrays(cnlNums, viewIDs); + + this.userData = userData; + userRights = userData.UserRights; + + if (userRights.ViewAllRight) + { + singleView = ElementsEqual(viewIDs) ? GetView(viewIDs[0]) : null; + } + else + { + CheckRights(cnlNums, viewIDs, out singleView); + } + } + } +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgChartCommon/TrendBundle.cs b/ScadaWeb/OpenPlugins/PlgChartCommon/TrendBundle.cs new file mode 100644 index 000000000..25492ebbf --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgChartCommon/TrendBundle.cs @@ -0,0 +1,173 @@ +/* + * Copyright 2016 Mikhail Shiryaev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Product : Rapid SCADA + * Module : PlgChartCommon + * Summary : Trend bundle that has a single timeline + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + */ + +using Scada.Data.Tables; +using System; +using System.Collections.Generic; + +namespace Scada.Web.Plugins.Chart +{ + /// + /// Trend bundle that has a single timeline + /// Связка трендов, имеющая единую шкалу времени + /// + public class TrendBundle + { + /// + /// Точка по всем трендам с отметкой времени + /// + public class Point + { + /// + /// Конструктор, ограничивающий создание объекта без параметров + /// + protected Point() + { + } + /// + /// Конструктор + /// + public Point(DateTime dateTime, int cnlCnt) + { + DateTime = dateTime; + CnlData = new SrezTableLight.CnlData[cnlCnt]; + } + + /// + /// Получить метку времени + /// + public DateTime DateTime { get; private set; } + /// + /// Получить данные + /// + public SrezTableLight.CnlData[] CnlData { get; private set; } + } + + + /// + /// Разность двух меток времени, менее которой они считаются равными, мс + /// + protected const int TimeDifference = 1000; + + + /// + /// Конструктор + /// + public TrendBundle() + { + Series = null; + } + + + /// + /// Получить временной ряд + /// + public List Series { get; protected set; } + + /// + /// Инициализировать связку трендов + /// + public void Init(Trend[] trends) + { + Init(trends, DateTime.MinValue); + } + + /// + /// Инициализировать связку трендов, включив в неё данные с указанного начального времени + /// + public void Init(Trend[] trends, DateTime startDT) + { + // формирование данных графиков + Series = new List(); + + int cnlCnt = trends.Length; + int[] trendPosArr = new int[cnlCnt]; // позиции получения данных из трендов + for (int i = 0; i < cnlCnt; i++) + trendPosArr[i] = 0; + + while (true) + { + // определение минимального времени среди обрабатываемых точек трендов + DateTime minDateTime = DateTime.MaxValue; + bool complete = true; + + for (int i = 0; i < cnlCnt; i++) + { + List trendPoints = trends[i].Points; + int trendPos = trendPosArr[i]; + int pointCnt = trendPoints.Count; + bool pointCompared = false; + + while (trendPos < pointCnt && !pointCompared) + { + DateTime pointDT = trendPoints[trendPos].DateTime; + + if (pointDT < startDT) + { + trendPos++; + trendPosArr[i]++; + } + else + { + pointCompared = true; + if (minDateTime > pointDT) + minDateTime = pointDT; + } + } + + complete = complete && trendPos >= pointCnt; + } + + // выход из цикла, если обработка данных завершена + if (complete) + break; + + // копирование данных из трендов на момент времени minDateTime + Point bundlePoint = new Point(minDateTime, cnlCnt); + + for (int i = 0; i < cnlCnt; i++) + { + SrezTableLight.CnlData cnlData = SrezTableLight.CnlData.Empty; + List trendPoints = trends[i].Points; + int trendPos = trendPosArr[i]; + + if (trendPos < trendPoints.Count) + { + Trend.Point trendPoint = trendPoints[trendPos]; + if ((trendPoint.DateTime - minDateTime).TotalMilliseconds < TimeDifference) + { + cnlData = new SrezTableLight.CnlData(trendPoint.Val, trendPoint.Stat); + trendPosArr[i]++; + } + } + + bundlePoint.CnlData[i] = cnlData; + } + + Series.Add(bundlePoint); + } + } + } +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgConfig/AppCode/Config/PlgPhrases.cs b/ScadaWeb/OpenPlugins/PlgConfig/AppCode/Config/PlgPhrases.cs new file mode 100644 index 000000000..98947d0ac --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgConfig/AppCode/Config/PlgPhrases.cs @@ -0,0 +1,117 @@ +/* + * Copyright 2016 Mikhail Shiryaev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Product : Rapid SCADA + * Module : PlgConfig + * Summary : The phrases used by the plugin + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + */ + +#pragma warning disable 1591 // отключение warning CS1591: Missing XML comment for publicly visible type or member + +namespace Scada.Web.Plugins.Config +{ + /// + /// The phrases used by the plugin + /// Фразы, используемые плагином + /// + public static class PlgPhrases + { + static PlgPhrases() + { + SetToDefault(); + } + + // Словарь Scada.Web.Plugins.Config.WFrmPlugins + public static string NameCol { get; private set; } + public static string DescrCol { get; private set; } + public static string StateCol { get; private set; } + public static string ActivateBtn { get; private set; } + public static string DeactivateBtn { get; private set; } + public static string InactiveState { get; private set; } + public static string ActiveState { get; private set; } + public static string NotLoadedState { get; private set; } + public static string PluginActivated { get; private set; } + public static string PluginDeactivated { get; private set; } + public static string PluginVersion { get; private set; } + + // Словарь Scada.Web.Plugins.Config.WFrmWebConfig + public static string UnknownPlugin { get; private set; } + public static string IncorrectFields { get; private set; } + public static string ConfigSaved { get; private set; } + + // Словарь Scada.Web.Plugins.PlgConfigSpec + public static string WebConfigMenuItem { get; private set; } + public static string PluginsMenuItem { get; private set; } + + private static void SetToDefault() + { + NameCol = Localization.Dict.GetEmptyPhrase("NameCol"); + DescrCol = Localization.Dict.GetEmptyPhrase("DescrCol"); + StateCol = Localization.Dict.GetEmptyPhrase("StateCol"); + ActivateBtn = Localization.Dict.GetEmptyPhrase("ActivateBtn"); + DeactivateBtn = Localization.Dict.GetEmptyPhrase("DeactivateBtn"); + InactiveState = Localization.Dict.GetEmptyPhrase("InactiveState"); + ActiveState = Localization.Dict.GetEmptyPhrase("ActiveState"); + NotLoadedState = Localization.Dict.GetEmptyPhrase("NotLoadedState"); + PluginActivated = Localization.Dict.GetEmptyPhrase("PluginActivated"); + PluginDeactivated = Localization.Dict.GetEmptyPhrase("PluginDeactivated"); + PluginVersion = Localization.Dict.GetEmptyPhrase("PluginVersion"); + + UnknownPlugin = Localization.Dict.GetEmptyPhrase("UnknownPlugin"); + IncorrectFields = Localization.Dict.GetEmptyPhrase("IncorrectFields"); + ConfigSaved = Localization.Dict.GetEmptyPhrase("ConfigSaved"); + + WebConfigMenuItem = Localization.Dict.GetEmptyPhrase("WebConfigMenuItem"); + PluginsMenuItem = Localization.Dict.GetEmptyPhrase("PluginsMenuItem"); + } + + public static void Init() + { + Localization.Dict dict; + if (Localization.Dictionaries.TryGetValue("Scada.Web.Plugins.Config.WFrmPlugins", out dict)) + { + NameCol = dict.GetPhrase("NameCol", NameCol); + DescrCol = dict.GetPhrase("DescrCol", DescrCol); + StateCol = dict.GetPhrase("StateCol", StateCol); + ActivateBtn = dict.GetPhrase("ActivateBtn", ActivateBtn); + DeactivateBtn = dict.GetPhrase("DeactivateBtn", DeactivateBtn); + InactiveState = dict.GetPhrase("InactiveState", InactiveState); + ActiveState = dict.GetPhrase("ActiveState", ActiveState); + NotLoadedState = dict.GetPhrase("NotLoadedState", NotLoadedState); + PluginActivated = dict.GetPhrase("PluginActivated", PluginActivated); + PluginDeactivated = dict.GetPhrase("PluginDeactivated", PluginDeactivated); + PluginVersion = dict.GetPhrase("PluginVersion", PluginVersion); + } + + if (Localization.Dictionaries.TryGetValue("Scada.Web.Plugins.Config.WFrmWebConfig", out dict)) + { + UnknownPlugin = dict.GetPhrase("UnknownPlugin", UnknownPlugin); + IncorrectFields = dict.GetPhrase("IncorrectFields", IncorrectFields); + ConfigSaved = dict.GetPhrase("ConfigSaved", ConfigSaved); + } + + if (Localization.Dictionaries.TryGetValue("Scada.Web.Plugins.PlgConfigSpec", out dict)) + { + WebConfigMenuItem = dict.GetPhrase("WebConfigMenuItem", WebConfigMenuItem); + PluginsMenuItem = dict.GetPhrase("PluginsMenuItem", PluginsMenuItem); + } + } + } +} diff --git a/ScadaWeb/OpenPlugins/PlgConfig/AppCode/PlgConfigSpec.cs b/ScadaWeb/OpenPlugins/PlgConfig/AppCode/PlgConfigSpec.cs index e1ce7bbdc..7f1a63f15 100644 --- a/ScadaWeb/OpenPlugins/PlgConfig/AppCode/PlgConfigSpec.cs +++ b/ScadaWeb/OpenPlugins/PlgConfig/AppCode/PlgConfigSpec.cs @@ -23,8 +23,10 @@ * Modified : 2016 */ +using Scada.Web.Plugins.Config; using Scada.Web.Shell; using System.Collections.Generic; +using System.IO; namespace Scada.Web.Plugins { @@ -34,6 +36,19 @@ namespace Scada.Web.Plugins /// public class PlgConfigSpec : PluginSpec { + private DictUpdater dictUpdater; // объект для обновления словаря плагина + + + /// + /// Конструктор + /// + public PlgConfigSpec() + : base() + { + dictUpdater = null; + } + + /// /// Получить наименование плагина /// @@ -55,8 +70,8 @@ public override string Descr get { return Localization.UseRussian ? - "Плагин позволяет конфигурировать Rapid SCADA через веб-интерфейс." : - "The plugin allows to configure Rapid SCADA using web interface."; + "Плагин позволяет конфигурировать веб-приложение через браузер." : + "The plugin allows to configure the web application using browser."; } } @@ -67,11 +82,29 @@ public override string Version { get { - return "0.0.0.1"; + return "1.0.0.0"; } } + /// + /// Инициализировать плагин + /// + public override void Init() + { + dictUpdater = new DictUpdater( + string.Format("{0}Config{1}lang{1}", AppDirs.PluginsDir, Path.DirectorySeparatorChar), + "PlgConfig", PlgPhrases.Init, Log); + } + + /// + /// Выполнить действия после успешного входа пользователя в систему + /// + public override void OnUserLogin(UserData userData) + { + dictUpdater.Update(); + } + /// /// Получить элементы меню, доступные пользователю /// @@ -80,9 +113,16 @@ public override List GetMenuItems(UserData userData) if (userData.UserRights.ConfigRight) { List menuItems = new List(); + MenuItem configMenuItem = MenuItem.FromStandardMenuItem(StandardMenuItems.Config); - configMenuItem.Subitems.Add(new MenuItem("Web application", "~/plugins/Config/WebConfig.aspx", 100)); + configMenuItem.Subitems.Add(new MenuItem(PlgPhrases.WebConfigMenuItem, + "~/plugins/Config/WebConfig.aspx")); menuItems.Add(configMenuItem); + + MenuItem pluginsMenuItem = new MenuItem(PlgPhrases.PluginsMenuItem, + "~/plugins/Config/Plugins.aspx", configMenuItem.SortOrder + 100); + menuItems.Add(pluginsMenuItem); + return menuItems; } else diff --git a/ScadaWeb/OpenPlugins/PlgConfig/PlgConfig.csproj b/ScadaWeb/OpenPlugins/PlgConfig/PlgConfig.csproj index e10f9f6c7..b8ce44603 100644 --- a/ScadaWeb/OpenPlugins/PlgConfig/PlgConfig.csproj +++ b/ScadaWeb/OpenPlugins/PlgConfig/PlgConfig.csproj @@ -20,6 +20,8 @@ + + true @@ -46,24 +48,47 @@ ..\..\..\ScadaData\ScadaData\bin\Release\ScadaData.dll - - ..\..\ScadaWebShell5Beta\bin\ScadaWebCommon5Beta.dll + + ..\..\ScadaWebShell5\bin\ScadaWebCommon5.dll - + + plugins.less + + + plugins.css + + + webconfig.less + + + webconfig.css + + + + + + + + Plugins.aspx + ASPXCodeBehind + + + Plugins.aspx + WebConfig.aspx ASPXCodeBehind @@ -75,6 +100,15 @@ + + + + compilerconfig.json + + + + + Web.config @@ -83,6 +117,7 @@ Web.config + 10.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) @@ -108,6 +143,13 @@ + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgMonitor/Properties/AssemblyInfo.cs b/ScadaWeb/OpenPlugins/PlgMonitor/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..ff8bcd00c --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgMonitor/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PlgMonitor")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Rapid SCADA")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("3a48fd69-7f01-4562-b9d7-e4984da03ba9")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ScadaWeb/OpenPlugins/PlgMonitor/Web.Debug.config b/ScadaWeb/OpenPlugins/PlgMonitor/Web.Debug.config new file mode 100644 index 000000000..2e302f9f9 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgMonitor/Web.Debug.config @@ -0,0 +1,30 @@ + + + + + + + + + + \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgMonitor/Web.Release.config b/ScadaWeb/OpenPlugins/PlgMonitor/Web.Release.config new file mode 100644 index 000000000..c35844462 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgMonitor/Web.Release.config @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgMonitor/Web.config b/ScadaWeb/OpenPlugins/PlgMonitor/Web.config new file mode 100644 index 000000000..c72873f34 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgMonitor/Web.config @@ -0,0 +1,13 @@ + + + + + + + + + + diff --git a/ScadaWeb/OpenPlugins/PlgMonitor/plugins/Monitor/ActiveUsers.aspx b/ScadaWeb/OpenPlugins/PlgMonitor/plugins/Monitor/ActiveUsers.aspx new file mode 100644 index 000000000..a0663a965 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgMonitor/plugins/Monitor/ActiveUsers.aspx @@ -0,0 +1,29 @@ +<%@ Page Title="Active Users - Rapid SCADA" Language="C#" MasterPageFile="~/MasterMain.Master" AutoEventWireup="true" CodeBehind="ActiveUsers.aspx.cs" Inherits="Scada.Web.Plugins.Monitor.WFrmActiveUsers" EnableViewState="false" %> + + + + + Active Users + + + + + IP Address + Session ID + Username + Logon Time + + + + + <%# Eval("IpAddress") %> + <%# HttpUtility.HtmlEncode(Eval("SessionID")) %> + <%# HttpUtility.HtmlEncode(Eval("UserProps.UserName")) %> + <%# Eval("LogonDT") %> + + + + + + + diff --git a/ScadaWeb/OpenPlugins/PlgMonitor/plugins/Monitor/ActiveUsers.aspx.cs b/ScadaWeb/OpenPlugins/PlgMonitor/plugins/Monitor/ActiveUsers.aspx.cs new file mode 100644 index 000000000..0d40a6121 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgMonitor/plugins/Monitor/ActiveUsers.aspx.cs @@ -0,0 +1,52 @@ +/* + * Copyright 2016 Mikhail Shiryaev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Product : Rapid SCADA + * Module : PlgMonitor + * Summary : Active users web form + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + */ + +using System; + +namespace Scada.Web.Plugins.Monitor +{ + /// + /// Active users web form + /// Веб-форма активных пользователей + /// + public partial class WFrmActiveUsers : System.Web.UI.Page + { + protected void Page_Load(object sender, EventArgs e) + { + AppData appData = AppData.GetAppData(); + UserData userData = UserData.GetUserData(); + + // проверка входа в систему и прав + userData.CheckLoggedOn(true); + + if (!userData.UserRights.ConfigRight) + throw new ScadaException(CommonPhrases.NoRights); + + // вывод данных + repActiveUsers.DataSource = appData.UserMonitor.GetActiveUsers(); + repActiveUsers.DataBind(); + } + } +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgMonitor/plugins/Monitor/ActiveUsers.aspx.designer.cs b/ScadaWeb/OpenPlugins/PlgMonitor/plugins/Monitor/ActiveUsers.aspx.designer.cs new file mode 100644 index 000000000..46a53e767 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgMonitor/plugins/Monitor/ActiveUsers.aspx.designer.cs @@ -0,0 +1,24 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Scada.Web.Plugins.Monitor { + + + public partial class WFrmActiveUsers { + + /// + /// repActiveUsers control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Repeater repActiveUsers; + } +} diff --git a/ScadaWeb/OpenPlugins/PlgMonitor/plugins/Monitor/CacheState.aspx b/ScadaWeb/OpenPlugins/PlgMonitor/plugins/Monitor/CacheState.aspx new file mode 100644 index 000000000..1e61e86ce --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgMonitor/plugins/Monitor/CacheState.aspx @@ -0,0 +1,58 @@ +<%@ Page Title="Cache State - Rapid SCADA" Language="C#" MasterPageFile="~/MasterMain.Master" AutoEventWireup="true" CodeBehind="CacheState.aspx.cs" Inherits="Scada.Web.Plugins.Monitor.WFrmCacheState" EnableViewState="false" %> + + + + + Hour Table Cache + + + + + + Key + Value Age + Value Refresh Time (UTC) + Access Time (UTC) + Snapshot Table + + + + + <%# HttpUtility.HtmlEncode(Eval("Key", "{0:d}")) %> + <%# Eval("ValueAge") %> + <%# Eval("ValueRefrDT") %> + <%# Eval("AccessDT") %> + <%# HttpUtility.HtmlEncode(Eval("Value.TableName")) %> + + + + + + + View Cache + + + + + + Key + Value Age + Value Refresh Time (UTC) + Access Time (UTC) + View + + + + + <%# HttpUtility.HtmlEncode(Eval("Key")) %> + <%# Eval("ValueAge") %> + <%# Eval("ValueRefrDT") %> + <%# Eval("AccessDT") %> + <%# HttpUtility.HtmlEncode(Eval("Value.Title")) %> + + + + + + + diff --git a/ScadaWeb/OpenPlugins/PlgMonitor/plugins/Monitor/CacheState.aspx.cs b/ScadaWeb/OpenPlugins/PlgMonitor/plugins/Monitor/CacheState.aspx.cs new file mode 100644 index 000000000..5e541dca1 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgMonitor/plugins/Monitor/CacheState.aspx.cs @@ -0,0 +1,85 @@ +/* + * Copyright 2016 Mikhail Shiryaev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + * Product : Rapid SCADA + * Module : PlgMonitor + * Summary : Cache state web form + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + */ + +using Scada.Client; +using Scada.Data.Tables; +using System; +using System.Collections; + +namespace Scada.Web.Plugins.Monitor +{ + /// + /// Cache state web form + /// Веб-форма состояния кэша + /// + public partial class WFrmCacheState : System.Web.UI.Page + { + protected void Page_Load(object sender, EventArgs e) + { + AppData appData = AppData.GetAppData(); + UserData userData = UserData.GetUserData(); + + // проверка входа в систему и прав + userData.CheckLoggedOn(true); + + if (!userData.UserRights.ConfigRight) + throw new ScadaException(CommonPhrases.NoRights); + + // вывод состояния кэша таблиц часовых срезов + const string InfoFormat = "Store period: {0}. Count: {1} / {2}. Cleaned up (UTC): {3}"; + + Cache hourTableCache = appData.DataAccess.DataCache.HourTableCache; + IList items = hourTableCache.GetAllItemsForWatching(); + lblHourTableCacheInfo.Text = string.Format(InfoFormat, + hourTableCache.StorePeriod, items.Count, hourTableCache.Capacity, hourTableCache.LastRemoveDT); + + if (items.Count > 0) + { + repHourTableCache.DataSource = items; + repHourTableCache.DataBind(); + } + else + { + repHourTableCache.Visible = false; + } + + // вывод состояния кэша представлений + Cache viewCache = appData.ViewCache.Cache; + items = viewCache.GetAllItemsForWatching(); + lblViewCacheInfo.Text = string.Format(InfoFormat, + viewCache.StorePeriod, items.Count, viewCache.Capacity, viewCache.LastRemoveDT); + + if (items.Count > 0) + { + repViewCache.DataSource = items; + repViewCache.DataBind(); + } + else + { + repViewCache.Visible = false; + } + } + } +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgMonitor/plugins/Monitor/CacheState.aspx.designer.cs b/ScadaWeb/OpenPlugins/PlgMonitor/plugins/Monitor/CacheState.aspx.designer.cs new file mode 100644 index 000000000..1a0ae123c --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgMonitor/plugins/Monitor/CacheState.aspx.designer.cs @@ -0,0 +1,51 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Scada.Web.Plugins.Monitor { + + + public partial class WFrmCacheState { + + /// + /// lblHourTableCacheInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblHourTableCacheInfo; + + /// + /// repHourTableCache control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Repeater repHourTableCache; + + /// + /// lblViewCacheInfo control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Label lblViewCacheInfo; + + /// + /// repViewCache control. + /// + /// + /// Auto-generated field. + /// To modify move field declaration from designer file to code-behind file. + /// + protected global::System.Web.UI.WebControls.Repeater repViewCache; + } +} diff --git a/ScadaWeb/OpenPlugins/PlgScheme/AppCode/PlgSchemeSpec.cs b/ScadaWeb/OpenPlugins/PlgScheme/AppCode/PlgSchemeSpec.cs index 2da0b44d6..30dcde9fc 100644 --- a/ScadaWeb/OpenPlugins/PlgScheme/AppCode/PlgSchemeSpec.cs +++ b/ScadaWeb/OpenPlugins/PlgScheme/AppCode/PlgSchemeSpec.cs @@ -23,6 +23,7 @@ * Modified : 2016 */ +using Scada.Web.Plugins.Scheme; using System.Collections.Generic; using System.IO; @@ -80,7 +81,7 @@ public override string Version { get { - return "0.0.0.1"; + return "1.0.0.0"; } } @@ -91,7 +92,7 @@ public override List ViewSpecs { get { - return new List() { new SchemeSpec() }; + return new List() { new SchemeViewSpec() }; } } diff --git a/ScadaWeb/OpenPlugins/PlgScheme/AppCode/SchemeSpec.cs b/ScadaWeb/OpenPlugins/PlgScheme/AppCode/Scheme/SchemeViewSpec.cs similarity index 69% rename from ScadaWeb/OpenPlugins/PlgScheme/AppCode/SchemeSpec.cs rename to ScadaWeb/OpenPlugins/PlgScheme/AppCode/Scheme/SchemeViewSpec.cs index 3c285a5bd..a1f333d43 100644 --- a/ScadaWeb/OpenPlugins/PlgScheme/AppCode/SchemeSpec.cs +++ b/ScadaWeb/OpenPlugins/PlgScheme/AppCode/Scheme/SchemeViewSpec.cs @@ -23,33 +23,48 @@ * Modified : 2016 */ -namespace Scada.Web.Plugins +using Scada.Scheme; +using System; + +namespace Scada.Web.Plugins.Scheme { /// /// Scheme view specification /// Спецификация представления схем /// - public class SchemeSpec : ViewSpec + public class SchemeViewSpec : ViewSpec { /// /// Получить код типа представления /// - public override string IconUrl + public override string TypeCode { get { - return ""; + // TODO: заменить на SchemeView после добавления поля ViewTypeCode в базу конфигурации + return "sch"; } } /// /// Получить ссылку на иконку типа представлений /// - public override string ViewTypeCode + public override string IconUrl + { + get + { + return "~/plugins/Scheme/images/schemeicon.png"; + } + } + + /// + /// Получить тип представления + /// + public override Type ViewType { get { - return "SchemeView"; + return typeof(SchemeView); } } @@ -57,7 +72,7 @@ public override string ViewTypeCode /// /// Получить ссылку на представление с заданным идентификатором /// - public override string GetViewUrl(int viewID) + public override string GetUrl(int viewID) { return "~/plugins/Scheme/Scheme.aspx?viewID=" + viewID; } diff --git a/ScadaWeb/OpenPlugins/PlgScheme/PlgScheme.csproj b/ScadaWeb/OpenPlugins/PlgScheme/PlgScheme.csproj index b3b6c01ce..6659ced9e 100644 --- a/ScadaWeb/OpenPlugins/PlgScheme/PlgScheme.csproj +++ b/ScadaWeb/OpenPlugins/PlgScheme/PlgScheme.csproj @@ -54,9 +54,9 @@ False ..\..\..\ScadaScheme\ScadaSchemeCommon\bin\Release\ScadaSchemeCommon.dll - + False - ..\..\ScadaWebShell5Beta\bin\ScadaWebCommon5Beta.dll + ..\..\ScadaWebShell5\bin\ScadaWebCommon5.dll @@ -73,10 +73,20 @@ + + notifier.less + + + notifier.css + - + + + + + scheme.less @@ -85,6 +95,7 @@ scheme.css + @@ -99,7 +110,7 @@ - + Scheme.aspx ASPXCodeBehind @@ -118,9 +129,11 @@ compilerconfig.json - - + + + + Web.config @@ -157,12 +170,12 @@ - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - + + \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgTable/Properties/AssemblyInfo.cs b/ScadaWeb/OpenPlugins/PlgTable/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..dc9065086 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgTable/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PlgTable")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Rapid SCADA")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7ae30e67-d10e-44b9-ac7a-81e21c8839e8")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/ScadaWeb/OpenPlugins/PlgTable/Web.Debug.config b/ScadaWeb/OpenPlugins/PlgTable/Web.Debug.config new file mode 100644 index 000000000..2e302f9f9 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgTable/Web.Debug.config @@ -0,0 +1,30 @@ + + + + + + + + + + \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgTable/Web.Release.config b/ScadaWeb/OpenPlugins/PlgTable/Web.Release.config new file mode 100644 index 000000000..c35844462 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgTable/Web.Release.config @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/ScadaWeb/ScadaWebShell5Beta/Web.config b/ScadaWeb/OpenPlugins/PlgTable/Web.config similarity index 87% rename from ScadaWeb/ScadaWebShell5Beta/Web.config rename to ScadaWeb/OpenPlugins/PlgTable/Web.config index 8ef1b35fa..d42573c62 100644 --- a/ScadaWeb/ScadaWebShell5Beta/Web.config +++ b/ScadaWeb/OpenPlugins/PlgTable/Web.config @@ -17,6 +17,12 @@ + + + + + + diff --git a/ScadaWeb/OpenPlugins/PlgTable/compilerconfig.json b/ScadaWeb/OpenPlugins/PlgTable/compilerconfig.json new file mode 100644 index 000000000..2cf1d282f --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgTable/compilerconfig.json @@ -0,0 +1,30 @@ +[ + { + "outputFile": "plugins/table/css/table.css", + "inputFile": "plugins/table/css/table.less" + }, + { + "outputFile": "css/controls/notifier.css", + "inputFile": "css/controls/notifier.less" + }, + { + "outputFile": "plugins/table/css/events.css", + "inputFile": "plugins/table/css/events.less" + }, + { + "outputFile": "plugins/Table/css/tablecommon.css", + "inputFile": "plugins/Table/css/tablecommon.less" + }, + { + "outputFile": "css/controls/tableheader.css", + "inputFile": "css/controls/tableheader.less" + }, + { + "outputFile": "plugins/Table/css/command.css", + "inputFile": "plugins/Table/css/command.less" + }, + { + "outputFile": "plugins/Table/css/eventack.css", + "inputFile": "plugins/Table/css/eventack.less" + } +] \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgTable/compilerconfig.json.defaults b/ScadaWeb/OpenPlugins/PlgTable/compilerconfig.json.defaults new file mode 100644 index 000000000..c75eb7d51 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgTable/compilerconfig.json.defaults @@ -0,0 +1,49 @@ +{ + "compilers": { + "less": { + "autoPrefix": "", + "cssComb": "none", + "ieCompat": true, + "strictMath": false, + "strictUnits": false, + "relativeUrls": true, + "rootPath": "", + "sourceMapRoot": "", + "sourceMapBasePath": "", + "sourceMap": false + }, + "sass": { + "includePath": "", + "indentType": "space", + "indentWidth": 2, + "outputStyle": "nested", + "Precision": 5, + "relativeUrls": true, + "sourceMapRoot": "", + "sourceMap": false + }, + "stylus": { + "sourceMap": false + }, + "babel": { + "sourceMap": false + }, + "coffeescript": { + "bare": false, + "runtimeMode": "node", + "sourceMap": false + } + }, + "minifiers": { + "css": { + "enabled": true, + "termSemicolons": true, + "gzip": false + }, + "javascript": { + "enabled": true, + "termSemicolons": true, + "gzip": false + } + } +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgTable/config/CommSettings.xml b/ScadaWeb/OpenPlugins/PlgTable/config/CommSettings.xml new file mode 100644 index 000000000..85ee9c1a5 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgTable/config/CommSettings.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgTable/config/WebSettings.xml b/ScadaWeb/OpenPlugins/PlgTable/config/WebSettings.xml new file mode 100644 index 000000000..56ae3db32 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgTable/config/WebSettings.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgTable/css/common/globalvar.less b/ScadaWeb/OpenPlugins/PlgTable/css/common/globalvar.less new file mode 100644 index 000000000..4b1e60099 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgTable/css/common/globalvar.less @@ -0,0 +1,29 @@ +@content-back-color: #f1f1f1; +@content-fore-color: #333; +@link-fore-color: #0073aa; +@link-hover-fore-color: #00a0d2; +@panel-border-color: #e5e5e5; +@popup-back-color: white; +@default-font-family: 'Open Sans', sans-serif; +@secondary-font-family: Arial, Helvetica, sans-serif; +@form-font-size: 13px; +@data-font-size: 12px; +@popup-min-width: 250px; + +@menu-back-color: #23282d; +@menu-back-color-air: white; +@menu-fore-color: #eee; +@menu-fore-color-dark: #9ca1a6; +@menu-fore-color-air: #444; +@menu-fore-color-air-light: #8f8f8f; +@menu-item-hover-back-color: #32373c; +@menu-item-hover-back-color-dark: #191e23; +@menu-item-hover-fore-color: #00b9eb; +@menu-item-selected-back-color: #0073aa; +@menu-item-selected-fore-color: white; +@menu-item-disabled-fore-color: @menu-fore-color-dark; +@menu-item-disabled-fore-color-air: #ccc; + +@splitter-color: #ddd; +@splitter-active-color: #ccc; +@splitter-width: 9px; \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgTable/css/common/modalform.less b/ScadaWeb/OpenPlugins/PlgTable/css/common/modalform.less new file mode 100644 index 000000000..c34091cc0 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgTable/css/common/modalform.less @@ -0,0 +1,20 @@ +@import "globalvar.less"; + +body { + margin: 0; + padding: 0; + background-color: @popup-back-color; + color: @content-fore-color; + font-family: @default-font-family; + font-size: @form-font-size; + min-width: @popup-min-width; + overflow: hidden; +} + +table { + border-collapse: collapse; +} + +label { + font-weight: 600; +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgTable/css/controls/notifier.css b/ScadaWeb/OpenPlugins/PlgTable/css/controls/notifier.css new file mode 100644 index 000000000..c435faa98 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgTable/css/controls/notifier.css @@ -0,0 +1,20 @@ +.notifier { + position: fixed; + top: 0; + left: 0; + max-height: 100px; + padding: 10px; + background-color: white; + border-bottom: 1px solid #e5e5e5; + display: none; + /*block*/ + font-family: 'Open Sans', sans-serif; + overflow: auto; + white-space: nowrap; +} +.notifier .message { + color: #0073aa; +} +.notifier .message.error { + color: red; +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgTable/css/controls/notifier.less b/ScadaWeb/OpenPlugins/PlgTable/css/controls/notifier.less new file mode 100644 index 000000000..b570b76d7 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgTable/css/controls/notifier.less @@ -0,0 +1,28 @@ +@import "../common/globalvar.less"; + +@notifier-back-color: white; +@notifier-msg-fore-color: #0073aa; +@notifier-err-fore-color: red; + +.notifier { + position: fixed; + top: 0; + left: 0; + max-height: 100px; + padding: 10px; + background-color: @notifier-back-color; + border-bottom: 1px solid @panel-border-color; + display: none; /*block*/ + font-family: @default-font-family; + font-size: @data-font-size; + overflow: auto; + white-space: nowrap; +} + +.notifier .message { + color: @notifier-msg-fore-color; +} + +.notifier .message.error { + color: @notifier-err-fore-color; +} diff --git a/ScadaWeb/OpenPlugins/PlgTable/css/controls/notifier.min.css b/ScadaWeb/OpenPlugins/PlgTable/css/controls/notifier.min.css new file mode 100644 index 000000000..424c08c79 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgTable/css/controls/notifier.min.css @@ -0,0 +1 @@ +.notifier{position:fixed;top:0;left:0;max-height:100px;padding:10px;background-color:#fff;border-bottom:1px solid #e5e5e5;display:none;font-family:'Open Sans',sans-serif;overflow:auto;white-space:nowrap;}.notifier .message{color:#0073aa;}.notifier .message.error{color:#f00;} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgTable/css/controls/tableheader.css b/ScadaWeb/OpenPlugins/PlgTable/css/controls/tableheader.css new file mode 100644 index 000000000..c4d5bf55c --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgTable/css/controls/tableheader.css @@ -0,0 +1,12 @@ +.table-wrapper { + position: relative; +} +.table-wrapper tr.fixed-table-header { + position: absolute; + top: 0; + left: 0; +} +.table-wrapper tr:first-child td > span, +.table-wrapper tr.fixed-table-header td > span { + display: block; +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgTable/css/controls/tableheader.less b/ScadaWeb/OpenPlugins/PlgTable/css/controls/tableheader.less new file mode 100644 index 000000000..0d8794265 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgTable/css/controls/tableheader.less @@ -0,0 +1,14 @@ +.table-wrapper { + position: relative; +} + +.table-wrapper tr.fixed-table-header { + position: absolute; + top: 0; + left: 0; +} + +.table-wrapper tr:first-child td > span, +.table-wrapper tr.fixed-table-header td > span { + display: block; +} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgTable/css/controls/tableheader.min.css b/ScadaWeb/OpenPlugins/PlgTable/css/controls/tableheader.min.css new file mode 100644 index 000000000..ba72ab399 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgTable/css/controls/tableheader.min.css @@ -0,0 +1 @@ +.table-wrapper{position:relative;}.table-wrapper tr.fixed-table-header{position:absolute;top:0;left:0;}.table-wrapper tr:first-child td>span,.table-wrapper tr.fixed-table-header td>span{display:block;} \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgTable/js/api/clientapi.js b/ScadaWeb/OpenPlugins/PlgTable/js/api/clientapi.js new file mode 100644 index 000000000..2b23bf92e --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgTable/js/api/clientapi.js @@ -0,0 +1,251 @@ +/* + * Rapid SCADA client API for access data and sending commands + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + * + * Requires: + * - jquery + * - utils.js + */ + +// Rapid SCADA namespace +var scada = scada || {}; + +/********** Input Channel Data **********/ + +// Input channel data type. +// Note: Casing is caused by C# naming rules +scada.CnlData = function () { + this.Val = 0.0; + this.Stat = 0; +}; + +// Extended input channel data type +scada.CnlDataExt = function () { + scada.CnlData.call(this); + + this.CnlNum = 0; + this.Text = ""; + this.TextWithUnit = ""; + this.Color = ""; +}; + +scada.CnlDataExt.prototype = Object.create(scada.CnlData.prototype); +scada.CnlDataExt.constructor = scada.CnlDataExt; + +// Hourly input channel data type +scada.HourCnlData = function () { + this.Hour = NaN; + this.Modified = false; + this.CnlDataExtArr = []; +} + +/********** Event **********/ + +// Event type +scada.Event = function () { + this.Num = 0; + this.Time = ""; + this.Obj = ""; + this.KP = ""; + this.Cnl = ""; + this.Text = ""; + this.Ack = ""; + this.Color = ""; + this.Sound = false; +}; + +/********** Auxiliary Request Parameters **********/ + +// Input channel filter type +scada.CnlFilter = function () { + // Filter by the explicitly specified input channel numbers. No other filtering is applied + this.cnlNums = []; + // Filter by input channels included in the view + this.viewID = 0; +}; + +// Convert the input channel filter to a query string +scada.CnlFilter.prototype.toQueryString = function () { + return "cnlNums=" + scada.utils.arrayToQueryParam(this.cnlNums) + + "&viewID=" + (this.viewID ? this.viewID : 0); +}; + +// Time period in hours type +scada.HourPeriod = function () { + // Date is a reference point of the period + this.date = 0; + // Start hour relative to the date. May be negative + this.startHour = 0; + // End hour relative to the date + this.endHour = 0; +}; + +// Convert the time period to a query string +scada.HourPeriod.prototype.toQueryString = function () { + return "year=" + this.date.getFullYear() + + "&month=" + (this.date.getMonth() + 1) + + "&day=" + this.date.getDate() + + "&startHour=" + this.startHour + + "&endHour=" + this.endHour; +}; + +// Hourly data selection modes enumeration +scada.HourDataModes = { + // Select data for integer hours even if a snapshot doesn't exist + INTEGER_HOURS: false, + // Select existing hourly snapshots + EXISTING: true +}; + +/********** Client API **********/ + +// Client API object +scada.clientAPI = { + // Empty input channel data + _EMPTY_CNL_DATA: Object.freeze(new scada.CnlData()), + + // Empty extended input channel data + _EMPTY_CNL_DATA_EXT: Object.freeze(new scada.CnlDataExt()), + + // Web service root path + rootPath: "", + + // Execute an AJAX request + _request: function (operation, queryString, callback, errorResult) { + $.ajax({ + url: this.rootPath + operation + queryString, + method: "GET", + dataType: "json", + cache: false + }) + .done(function (data, textStatus, jqXHR) { + try { + var parsedData = $.parseJSON(data.d); + if (parsedData.Success) { + scada.utils.logSuccessfulRequest(operation/*, data*/); + if (typeof parsedData.DataAge === "undefined") { + callback(true, parsedData.Data); + } else { + callback(true, parsedData.Data, parsedData.DataAge); + } + } else { + scada.utils.logServiceError(operation, parsedData.ErrorMessage); + callback(false, errorResult); + } + } + catch (ex) { + scada.utils.logProcessingError(operation, ex.message); + if (typeof callback === "function") { + callback(false, errorResult); + } + } + }) + .fail(function (jqXHR, textStatus, errorThrown) { + scada.utils.logFailedRequest(operation, jqXHR); + if (typeof callback === "function") { + callback(false, errorResult); + } + }); + }, + + // Perform user login. + // callback is a function (success, loggedOn) + // URL example: http://webserver/scada/ClientApiSvc.svc/Login?username=admin&password=12345 + login: function (username, password, callback) { + this._request("ClientApiSvc.svc/Login", + "?username=" + encodeURIComponent(username) + "&password=" + encodeURIComponent(password), + callback, false); + }, + + // Check that a user is logged on. + // callback is a function (success, loggedOn) + // URL example: http://webserver/scada/ClientApiSvc.svc/CheckLoggedOn + checkLoggedOn: function (callback) { + this._request("ClientApiSvc.svc/CheckLoggedOn", "", callback, false); + }, + + // Get current data of the input channel. + // callback is a function (success, cnlData) + // URL example: http://webserver/scada/ClientApiSvc.svc/GetCurCnlData?cnlNum=1 + getCurCnlData: function (cnlNum, callback) { + this._request("ClientApiSvc.svc/GetCurCnlData", "?cnlNum=" + cnlNum, callback, this._EMPTY_CNL_DATA); + }, + + // Get extended current data by the specified filter. + // callback is a function (success, cnlDataExtArr) + // URL example: http://webserver/scada/ClientApiSvc.svc/GetCurCnlDataExt?cnlNums=&viewID=1 + getCurCnlDataExt: function (cnlFilter, callback) { + this._request("ClientApiSvc.svc/GetCurCnlDataExt", "?" + cnlFilter.toQueryString(), callback, []); + }, + + // Get hourly data by the specified filter. + // dataAge is an array of dates in milliseconds, + // callback is a function (success, hourCnlDataArr, dataAge) + // URL example: http://webserver/scada/ClientApiSvc.svc/GetHourCnlData?year=2016&month=1&day=1&startHour=0&endHour=23&cnlNums=&viewID=1&existing=true&dataAge= + getHourCnlData: function (hourPeriod, cnlFilter, selectMode, dataAge, callback) { + this._request("ClientApiSvc.svc/GetHourCnlData", + "?" + hourPeriod.toQueryString() + "&" + cnlFilter.toQueryString() + "&existing=" + selectMode + + "&dataAge=" + scada.utils.arrayToQueryParam(dataAge), + callback, []); + }, + + // Get events by the specified filter. + // callback is a function (success, eventArr, dataAge) + // URL example: http://webserver/scada/ClientApiSvc.svc/GetEvents?year=2016&month=1&day=1&cnlNums=&viewID=1&lastCount=100&startEvNum=0&dataAge=0 + getEvents: function (date, cnlFilter, lastCount, startEvNum, dataAge, callback) { + this._request("ClientApiSvc.svc/GetEvents", + "?" + scada.utils.dateToQueryString(date) + "&" + cnlFilter.toQueryString() + + "&lastCount=" + lastCount + "&startEvNum=" + startEvNum + "&dataAge=" + dataAge, + callback, []); + }, + + // Get the stamp of the view from the cache. + // callback is a function (success, stamp) + // URL example: http://webserver/scada/ClientApiSvc.svc/GetViewStamp?viewID=1 + getViewStamp: function (viewID, callback) { + this._request("ClientApiSvc.svc/GetViewStamp", "?viewID=" + viewID, callback, 0); + }, + + // Parse date and time using the application culture + // callback is a function (success, value), + // value is the number of milliseconds or null in case of any error + // URL example: http://webserver/scada/ClientApiSvc.svc/ParseDateTime?s=01%20January%202016 + parseDateTime: function (s, callback) { + this._request("ClientApiSvc.svc/ParseDateTime", "?s=" + encodeURIComponent(s), callback, null); + }, + + // Create map of extended input channel data to access by channel number + createCnlDataExtMap: function (cnlDataExtArr) { + try { + var map = new Map(); + for (var cnlDataExt of cnlDataExtArr) { + map.set(cnlDataExt.CnlNum, cnlDataExt); + } + return map; + } + catch (ex) { + console.error(scada.utils.getCurTime() + " Error creating map of extended input channel data:", + ex.message); + return new Map(); + } + }, + + // Create map of hourly input channel data to access by hour + createHourCnlDataMap: function (hourCnlDataArr) { + try { + var map = new Map(); + for (var hourCnlData of hourCnlDataArr) { + map.set(hourCnlData.Hour, hourCnlData); + } + return map; + } + catch (ex) { + console.error(scada.utils.getCurTime() + " Error creating map of hourly input channel data:", + ex.message); + return new Map(); + } + } +}; diff --git a/ScadaWeb/OpenPlugins/PlgTable/js/api/eventtypes.js b/ScadaWeb/OpenPlugins/PlgTable/js/api/eventtypes.js new file mode 100644 index 000000000..d7b9d1354 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgTable/js/api/eventtypes.js @@ -0,0 +1,34 @@ +/* + * JavaScript event types used by the shell + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + * + * No dependencies + */ + +// Rapid SCADA namespace +var scada = scada || {}; + +// JavaScript event types enumeration +scada.EventTypes = { + // Page layout should be updated + UPDATE_LAYOUT: "scada:updateLayout", + + // View title has been changed. + // Event parameter: title + VIEW_TITLE_CHANGED: "scada:viewTitleChanged", + + // Before navigate to another view + // Event parameter: view id + VIEW_NAVIGATE: "scada:viewNavigate", + + // Current view date has been changed + // Event parameter: date + VIEW_DATE_CHANGED: "scada:viewDateChanged", + + // Modal dialog button is clicked + // Event parameter: dialog result + MODAL_BTN_CLICK: "scada:modalBtnClick" +}; diff --git a/ScadaWeb/OpenPlugins/PlgTable/js/api/utils.js b/ScadaWeb/OpenPlugins/PlgTable/js/api/utils.js new file mode 100644 index 000000000..c51af7b9e --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgTable/js/api/utils.js @@ -0,0 +1,253 @@ +/* + * JavaScript utilities + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + * + * No dependencies + */ + +// Rapid SCADA namespace +var scada = scada || {}; + +// JavaScript utilities object +scada.utils = { + // Prospective browser scrollbar width + _SCROLLBAR_WIDTH: 20, + + // z-index that moves element to the front + FRONT_ZINDEX: 10000, + + // Default cookie expiration period in days + COOKIE_EXPIRATION: 7, + + // Window width that is considered a small + SMALL_WND_WIDTH: 800, + + // Get cookie + getCookie: function (name) { + var cookie = " " + document.cookie; + var search = " " + name + "="; + var offset = cookie.indexOf(search); + + if (offset >= 0) { + offset += search.length; + var end = cookie.indexOf(";", offset) + + if (end < 0) + end = cookie.length; + + return decodeURIComponent(cookie.substring(offset, end)); + } else { + return null; + } + }, + + // Set cookie + setCookie: function (name, value, opt_expDays) { + var expDays = opt_expDays ? opt_expDays : this.COOKIE_EXPIRATION; + var expires = new Date(); + expires.setDate(expires.getDate() + expDays); + document.cookie = name + "=" + encodeURIComponent(value) + "; expires=" + expires.toUTCString(); + }, + + // Get the query string parameter value + getQueryParam: function (paramName, opt_url) { + if (paramName) { + var url = opt_url ? opt_url : decodeURIComponent(window.location); + var begInd = url.indexOf("?"); + + if (begInd > 0) { + url = "&" + url.substring(begInd + 1); + } + + paramName = "&" + paramName + "="; + begInd = url.indexOf(paramName); + + if (begInd >= 0) { + begInd += paramName.length; + var endInd = url.indexOf("&", begInd); + return endInd >= 0 ? url.substring(begInd, endInd) : url.substring(begInd); + } + } + + return ""; + }, + + // Set or add the query string parameter value. + // The method returns a new string + setQueryParam: function (paramName, paramVal, opt_url) { + if (paramName) { + var url = opt_url ? opt_url : decodeURIComponent(window.location); + var searchName = "?" + paramName + "="; + var nameBegInd = url.indexOf(searchName); + + if (nameBegInd < 0) { + searchName = "&" + paramName + "="; + nameBegInd = url.indexOf(searchName); + } + + if (nameBegInd >= 0) { + // replace parameter value + var valBegInd = nameBegInd + searchName.length; + var valEndInd = url.indexOf("&", valBegInd); + var newUrl = url.substring(0, valBegInd) + encodeURIComponent(paramVal); + return valEndInd > 0 ? + newUrl + url.substring(valEndInd) : + newUrl; + } else { + // add parameter + var mark = url.indexOf("?") >= 0 ? "&" : "?"; + return url + mark + paramName + "=" + encodeURIComponent(paramVal); + } + } else { + return ""; + } + }, + + // Convert array to a query string parameter by joining array elements with a comma + arrayToQueryParam: function (arr) { + var queryParam = arr ? (Array.isArray(arr) ? arr.join(",") : arr) : ""; + // space instead of empty string is required by Mono WCF implementation + return encodeURIComponent(queryParam ? queryParam : " "); + }, + + // Extract year, month and day from the date, and join them into a query string + dateToQueryString: function (date) { + return "year=" + date.getFullYear() + + "&month=" + (date.getMonth() + 1) + + "&day=" + date.getDate(); + }, + + // Returns the current time string + getCurTime: function () { + return new Date().toLocaleTimeString("en-GB"); + }, + + // Write information about the successful request to console + logSuccessfulRequest: function (operation, opt_data) { + console.log(this.getCurTime() + " Request '" + operation + "' successful"); + if (opt_data) { + console.log(opt_data.d); + } + }, + + // Write information about the failed request to console + logFailedRequest: function (operation, jqXHR) { + console.error(this.getCurTime() + " Request '" + operation + "' failed: " + + jqXHR.status + " (" + jqXHR.statusText + ")"); + }, + + // Write information about the internal service error to console + logServiceError: function (operation, opt_message) { + console.error(this.getCurTime() + " Request '" + operation + "' reports internal service error" + + (opt_message ? ": " + opt_message : "")); + }, + + // Write information about the request processing error to console + logProcessingError: function (operation, opt_message) { + console.error(this.getCurTime() + " Error processing request '" + operation + "'" + + (opt_message ? ": " + opt_message : "")); + }, + + // Check if browser is in fullscreen mode + // See https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API + isFullscreen: function() { + return document.fullscreenElement || document.mozFullScreenElement || + document.webkitFullscreenElement || document.msFullscreenElement; + }, + + // Switch browser to fullscreen mode + requestFullscreen: function () { + if (document.documentElement.requestFullscreen) { + document.documentElement.requestFullscreen(); + } else if (document.documentElement.msRequestFullscreen) { + document.documentElement.msRequestFullscreen(); + } else if (document.documentElement.mozRequestFullScreen) { + document.documentElement.mozRequestFullScreen(); + } else if (document.documentElement.webkitRequestFullscreen) { + document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT); + } + }, + + // Exit browser fullscreen mode + exitFullscreen: function () { + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.msExitFullscreen) { + document.msExitFullscreen(); + } else if (document.mozCancelFullScreen) { + document.mozCancelFullScreen(); + } else if (document.webkitExitFullscreen) { + document.webkitExitFullscreen(); + } + }, + + // Switch browser to full screen mode and back to normal view + toggleFullscreen: function () { + if (this.isFullscreen()) { + this.exitFullscreen(); + } else { + this.requestFullscreen(); + } + }, + + // Check if a browser window is small sized + isSmallScreen() { + return top.innerWidth <= this.SMALL_WND_WIDTH; + }, + + // Get browser scrollbar width + getScrollbarWidth: function () { + return this._SCROLLBAR_WIDTH; + }, + + // Click hyperlink programmatically + clickLink: function (jqLink) { + var href = jqLink.attr("href"); + if (href) { + if (href.startsWith("javascript:")) { + // execute script + var script = href.substr(11); + eval(script); + } else { + // open web page + location.href = href; + } + } + }, + + // Scroll the first specified element to make the second element visible if it exists + scrollTo: function (jqScrolledElem, jqTargetElem) { + if (jqTargetElem.length > 0) { + var targetTop = jqTargetElem.offset().top; + + if (jqScrolledElem.scrollTop() > targetTop) { + jqScrolledElem.scrollTop(targetTop); + } + } + }, + + // Detect if iOS is used + iOS: function () { + return /iPad|iPhone|iPod/.test(navigator.platform); + }, + + // Apply additional css styles to a container element in case of using iOS + styleIOS: function (jqElem, opt_resetSize) { + if (this.iOS()) { + jqElem.css({ + "overflow": "scroll", + "-webkit-overflow-scrolling": "touch" + }); + + if (opt_resetSize) { + jqElem.css({ + "width": 0, + "height": 0 + }); + } + } + } +}; diff --git a/ScadaWeb/OpenPlugins/PlgTable/js/api/viewhub.js b/ScadaWeb/OpenPlugins/PlgTable/js/api/viewhub.js new file mode 100644 index 000000000..ab19d0fbe --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgTable/js/api/viewhub.js @@ -0,0 +1,114 @@ +/* + * View hub provides data exchange between a view, data windows and the shell + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + * + * Requires: + * - jquery + * - eventtypes.js + * + * Optional: + * - dialogs.js + */ + +// Rapid SCADA namespace +var scada = scada || {}; + +// View hub type +scada.ViewHub = function (mainWindow) { + // Current view ID + this.curViewID = 0; + + // Current view date for displaying data (in milliseconds) + this.curViewDateMs = 0; + + // Main window object that manages a view and data windows + this.mainWindow = mainWindow; + + // View window object + this.viewWindow = null; + + // Data window object + this.dataWindow = null; + + // Reference to a dialogs object + this.dialogs = scada.dialogs; +}; + +// Add the specified view to the hub. +// The method is called by the code that manages windows +scada.ViewHub.prototype.addView = function (wnd) { + this.viewWindow = wnd; +}; + +// Add the specified data window to the hub. +// The method is called by the code that manages windows +scada.ViewHub.prototype.addDataWindow = function (wnd) { + this.dataWindow = wnd; +}; + +// Remove the data window reference. +// The method is called by the code that manages windows +scada.ViewHub.prototype.removeDataWindow = function () { + this.dataWindow = null; +}; + +// Send notification to a view or data window. +// The method is called by a child window +scada.ViewHub.prototype.notify = function (eventType, senderWnd, opt_extraParams) { + var handled = false; + var senderIsView = senderWnd == this.viewWindow; + + // preprocess events + if (eventType == scada.EventTypes.VIEW_NAVIGATE) { + if (senderIsView) { + this.curViewID = opt_extraParams; + } else { + handled = true; // cancel notification + } + } + + if (eventType == scada.EventTypes.VIEW_DATE_CHANGED) { + this.curViewDateMs = opt_extraParams.getTime(); + } + + // pass the notification to the main window + if (!handled && this.mainWindow && this.mainWindow != senderWnd) { + var jq = this.mainWindow.$; + if (jq) { + jq(this.mainWindow).trigger(eventType, [senderWnd, opt_extraParams]); + } + } + + // pass the notification to the view window + if (!handled && this.viewWindow && this.viewWindow != senderWnd) { + var jq = this.viewWindow.$; + if (jq) { + jq(this.viewWindow).trigger(eventType, [senderWnd, opt_extraParams]); + } + } + + // pass the notification to the data window + if (!handled && this.dataWindow && this.dataWindow != senderWnd) { + var jq = this.dataWindow.$; + if (jq) { + jq(this.dataWindow).trigger(eventType, [senderWnd, opt_extraParams]); + } + } +}; + +// View hub locator object +scada.viewHubLocator = { + // Find and return an existing view hub object + getViewHub: function () { + var wnd = window; + while (wnd) { + if (wnd.viewHub) { + return wnd.viewHub; + } + wnd = wnd == window.top ? null : window.parent; + } + return null; + } +}; \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgTable/js/controls/notifier.js b/ScadaWeb/OpenPlugins/PlgTable/js/controls/notifier.js new file mode 100644 index 000000000..f1f9fb66a --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgTable/js/controls/notifier.js @@ -0,0 +1,99 @@ +/* + * Notifier control + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + * + * Requires: + * - jquery + * - eventtypes.js + */ + +// Rapid SCADA namespace +var scada = scada || {}; + +// Notifier type +scada.Notifier = function (selector) { + // jQuery object of the notification area + this._notifier = $(selector); + + // Default notification message lifetime, ms + this.DEF_NOTIF_LIFETIME = 10000; + + // Infinite notification message lifetime + this.INFINITE_NOTIF_LIFETIME = 0; + + // Clearing outdated notifications rate + this.CLEAR_RATE = 1000; +}; + +// Add notification to the notification area +scada.Notifier.prototype.addNotification = function (messageHtml, error, lifetime) { + // remove the previous message if it is equal the new + var divPrevMessage = this._notifier.children(".message:last"); + + if (divPrevMessage.html() == messageHtml) { + divPrevMessage.remove(); + } + + // add the new message + var divMessage = $("").html(messageHtml); + + if (error) { + divMessage.addClass("error"); + } + + if (lifetime) { + divMessage.attr("data-expires", Date.now() + lifetime); + } + + this._notifier + .css("display", "block") + .append(divMessage) + .scrollTop(this._notifier.prop("scrollHeight")); + + $(window).trigger(scada.EventTypes.UPDATE_LAYOUT); +}; + +// Clear the notifications which lifetime is expired +scada.Notifier.prototype.clearOutdatedNotifications = function () { + var messages = this._notifier.find(".message"); + + if (messages.length > 0) { + var nowMs = Date.now(); + var removed = false; + + $.each(messages, function () { + var expires = $(this).attr("data-expires"); + if (expires < nowMs) { + $(this).remove(); + removed = true; + } + }); + + if (removed) { + if (this._notifier.find(".message").length == 0) { + this._notifier.css("display", "none"); + } + + $(window).trigger(scada.EventTypes.UPDATE_LAYOUT); + } + } +}; + +// Clear all the notifications +scada.Notifier.prototype.clearAllNotifications = function () { + var messages = this._notifier.find(".message"); + + if (messages.length > 0) { + messages.remove(); + $(window).trigger(scada.EventTypes.UPDATE_LAYOUT); + } +}; + +// Start outdated notifications clearing process +scada.Notifier.prototype.startClearingNotifications = function () { + var thisNotifier = this; + setInterval(function () { thisNotifier.clearOutdatedNotifications(); }, this.CLEAR_RATE); +} diff --git a/ScadaWeb/OpenPlugins/PlgTable/js/controls/popup.js b/ScadaWeb/OpenPlugins/PlgTable/js/controls/popup.js new file mode 100644 index 000000000..9a24ca7af --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgTable/js/controls/popup.js @@ -0,0 +1,438 @@ +/* + * Popup dialogs manipulation + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + * + * Requires: + * - jquery + * - utils.js + * + * Requires for modal dialogs: + * - bootstrap + * - eventtypes.js + * - scada.modalButtonCaptions object + */ + +// Rapid SCADA namespace +var scada = scada || {}; + +/********** Modal Dialog Buttons **********/ + +// Modal dialog buttons enumeration +scada.ModalButtons = { + OK: "ok", + YES: "yes", + NO: "no", + EXEC: "execute", + CANCEL: "cancel", + CLOSE: "close" +}; + +/********** Modal Dialog Sizes **********/ + +// Modal dialog sizes enumeration +scada.ModalSizes = { + NORMAL: 0, + SMALL: 1, + LARGE: 2 +}; + +/********** Modal Dialog Options **********/ + +// Modal dialog options class +scada.ModalOptions = function (buttons, opt_size) { + this.buttons = buttons; + this.size = opt_size ? opt_size : scada.ModalSizes.NORMAL; +} + +/********** Popup **********/ + +// Popup dialogs manipulation type +scada.Popup = function () { + // Window that holds popups + this._holderWindow = window; +}; + +// Close the dropdown popup and execute a callback with a cancel result +scada.Popup.prototype._cancelDropdown = function (popupElem) { + var callback = popupElem.data("popup-callback"); + popupElem.remove(); + + if (callback) { + callback(false); + } +}; + +// Get coodinates of the specified element relative to the holder window +scada.Popup.prototype._getOffset = function (elem) { + // validate the element + var defaultOffset = { left: 0, top: 0 }; + if (!(elem && elem.length)) { + return defaultOffset; + } + + // get coodinates within a window that contains the element + var wnd = elem[0].ownerDocument.defaultView; + var offset = elem.offset(); + var left = offset.left + $(wnd).scrollLeft(); + var top = offset.top + $(wnd).scrollTop(); + + // add coordinates of the parent frames + do { + var parentWnd = wnd.parent; + if (wnd != parentWnd) { + if (parentWnd.$) { + var frame = parentWnd.$(wnd.frameElement); + if (frame.length > 0) { + offset = frame.offset(); + left += offset.left + $(parentWnd).scrollLeft(); + top += offset.top + $(parentWnd).scrollTop(); + } + } else { + console.warn("Unable to get offset, because jQuery is not found"); + return defaultOffset; + } + wnd = parentWnd; + } + } while (wnd != this._holderWindow && wnd != wnd.parent); + + return { left: left, top: top }; +}; + +// Get caption for the specified modal dialog button +scada.Popup.prototype._getModalButtonCaption = function (btn) { + var btnCaption = scada.modalButtonCaptions ? scada.modalButtonCaptions[btn] : null; + if (!btnCaption) { + btnCaption = btn; + } + return btnCaption; +} + +// Get html markup of a modal dialog footer buttons +scada.Popup.prototype._genModalButtonsHtml = function (buttons) { + var html = ""; + + for (var btn of buttons) { + var btnCaption = this._getModalButtonCaption(btn); + var subclass = btn == scada.ModalButtons.OK || btn == scada.ModalButtons.YES ? "btn-primary" : + (btn == scada.ModalButtons.EXEC ? "btn-danger" : "btn-default"); + var dismiss = btn == scada.ModalButtons.CANCEL || btn == scada.ModalButtons.CLOSE ? + " data-dismiss='modal'" : ""; + + html += "" + btnCaption + ""; + } + + return html; +} + +// Find modal button by result +scada.Popup.prototype._findModalButton = function (modalWnd, btn) { + var frame = $(modalWnd.frameElement); + var modalElem = frame.closest(".modal"); + return modalElem.find(".modal-footer button[data-result='" + btn + "']"); +} + +// Show popup with the specified url as a dropdown menu below the anchorElem. +// opt_callback is a function (dialogResult, extraParams) +scada.Popup.prototype.showDropdown = function (url, anchorElem, opt_callback) { + var thisObj = this; + var popupElem = $("" + + ""); + + if (opt_callback) { + popupElem.data("popup-callback", opt_callback); + } + + $("body").append(popupElem); + + var overlay = popupElem.find(".popup-overlay"); + var wrapper = popupElem.find(".popup-wrapper"); + var frame = popupElem.find(".popup-frame"); + + // setup overlay + overlay + .css("z-index", scada.utils.FRONT_ZINDEX) + .click(function () { + thisObj._cancelDropdown(popupElem); + }); + + // setup wrapper + wrapper.css({ + "z-index": scada.utils.FRONT_ZINDEX + 1, // above the overlay + "opacity": 0.0 // hide the popup while it's loading + }); + + // remove the popup on press Escape key in the parent window + var removePopupOnEscapeFunc = function (event) { + if (event.which == 27 /*Escape*/) { + thisObj._cancelDropdown(popupElem); + } + } + + $(document) + .off("keydown.scada.dropdown", removePopupOnEscapeFunc) + .on("keydown.scada.dropdown", removePopupOnEscapeFunc); + + // load the frame + frame + .on("load", function () { + // remove the popup on press Escape key in the frame + var frameWnd = frame[0].contentWindow; + if (frameWnd.$) { + var jqFrameDoc = frameWnd.$(frameWnd.document); + jqFrameDoc.ready(function () { + jqFrameDoc + .off("keydown.scada.dropdown", removePopupOnEscapeFunc) + .on("keydown.scada.dropdown", removePopupOnEscapeFunc); + }); + } + }) + .one("load", function () { + // set the popup position + var frameBody = frame.contents().find("body"); + var width = frameBody.outerWidth(true); + var height = frameBody.outerHeight(true); + var left = 0; + var top = 0; + + if (anchorElem.length > 0) { + var offset = thisObj._getOffset(anchorElem); + left = offset.left; + top = offset.top + anchorElem.outerHeight(); + var borderWidthX2 = parseInt(wrapper.css("border-width"), 10) * 2; + + if (left + width + borderWidthX2 > $(document).width()) + left = Math.max($(document).width() - width - borderWidthX2, 0); + + if (top + height + borderWidthX2 > $(document).height()) + top = Math.max($(document).height() - height - borderWidthX2, 0); + } + else { + left = Math.max(($(window).width() - width) / 2, 0); + top = Math.max(($(window).height() - height) / 2, 0); + } + + wrapper.css({ + "left": left, + "top": top + }); + + // set the popup size and display the popup + frame + .css({ + "width": width, + "height": height + }) + .focus(); + + wrapper.css({ + "width": width, + "height": height, + "opacity": 1.0 + }); + }) + .attr("src", url); +}; + +// Close the dropdown popup and execute a callback with the specified result +scada.Popup.prototype.closeDropdown = function (popupWnd, dialogResult, extraParams) { + var frame = $(popupWnd.frameElement); + var popupElem = frame.closest(".popup-dropdown"); + var callback = popupElem.data("popup-callback"); + popupElem.remove(); + + if (callback) { + callback(dialogResult, extraParams); + } +}; + +// Show modal dialog with the specified url. +// opt_callback is a function (dialogResult, extraParams), +// requires Bootstrap +scada.Popup.prototype.showModal = function (url, opt_options, opt_callback) { + // create temporary overlay to prevent user activity + var tempOverlay = $(""); + $("body").append(tempOverlay); + + // create the modal + var buttons = opt_options ? opt_options.buttons : null; + var footerHtml = buttons && buttons.length ? + "" : ""; + + var size = opt_options ? opt_options.size : scada.ModalSizes.NORMAL; + var sizeClass = ""; + if (size == scada.ModalSizes.SMALL) { + sizeClass = " modal-sm"; + } else if (size == scada.ModalSizes.LARGE) { + sizeClass = " modal-lg"; + } + + var modalElem = $( + "" + + "" + + "" + + "" + + "×" + + "" + + "" + + footerHtml + + ""); + + if (opt_callback) { + modalElem + .data("modal-callback", opt_callback) + .data("dialog-result", false); + } + + // create the frame + var modalFrame = $(""); + modalFrame.css({ + "position": "fixed", + "opacity": 0.0 // hide the frame while it's loading + }); + $("body").append(modalFrame); + + // create a function that hides the modal on press Escape key + var hideModalOnEscapeFunc = function (event) { + if (event.which == 27 /*Escape*/) { + modalElem.modal("hide"); + } + } + + // load the frame + modalFrame + .on("load", function () { + // remove the modal on press Escape key in the frame + var frameWnd = modalFrame[0].contentWindow; + if (frameWnd.$) { + var jqFrameDoc = frameWnd.$(frameWnd.document); + jqFrameDoc.ready(function () { + jqFrameDoc + .off("keydown.scada.modal", hideModalOnEscapeFunc) + .on("keydown.scada.modal", hideModalOnEscapeFunc); + }); + } + }) + .one("load", function () { + // get the frame size + var frameBody = modalFrame.contents().find("body"); + var frameWidth = frameBody.outerWidth(true); + var frameHeight = frameBody.outerHeight(true); + + // tune the modal + var modalBody = modalElem.find(".modal-body"); + var modalPaddings = parseInt(modalBody.css("padding-left")) + parseInt(modalBody.css("padding-right")); + modalElem.find(".modal-content").css("min-width", frameWidth + modalPaddings) + modalElem.find(".modal-title").text(modalFrame[0].contentWindow.document.title); + + // move the frame into the modal + modalFrame.detach(); + modalBody.append(modalFrame); + $("body").append(modalElem); + + // set the frame style + modalFrame.css({ + "width": "100%", + "height": frameHeight, + "position": "", + "opacity": 1.0 + }); + + // raise event on modal button click + modalElem.find(".modal-footer button").click(function () { + var result = $(this).data("result"); + var frameWnd = modalFrame[0].contentWindow; + var frameJq = frameWnd.$; + if (result && frameJq) { + frameJq(frameWnd).trigger(scada.EventTypes.MODAL_BTN_CLICK, result); + } + }); + + // display the modal + modalElem + .on('shown.bs.modal', function () { + tempOverlay.remove(); + modalFrame.focus(); + }) + .on('hidden.bs.modal', function () { + var callback = $(this).data("modal-callback"); + if (callback) { + callback($(this).data("dialog-result"), $(this).data("extra-params")); + } + + $(this).remove(); + }) + .modal("show"); + }) + .attr("src", url); +}; + +// Close the modal dialog +scada.Popup.prototype.closeModal = function (modalWnd, dialogResult, extraParams) { + this.setModalResult(modalWnd, dialogResult, extraParams).modal("hide"); +} + +// Update the modal dialog height according to a frame height +scada.Popup.prototype.updateModalHeight = function (modalWnd) { + var frame = $(modalWnd.frameElement); + var frameBody = frame.contents().find("body"); + var modalElem = frame.closest(".modal"); + + var iosScrollFix = scada.utils.iOS(); + if (iosScrollFix) { + modalElem.css("overflow-y", "hidden"); + } + + frame.css("height", frameBody.outerHeight(true)); + + if (iosScrollFix) { + modalElem.css("overflow-y", ""); + } + + modalElem.modal("handleUpdate"); +} + +// Set dialog result for the whole modal dialog +scada.Popup.prototype.setModalResult = function (modalWnd, dialogResult, extraParams) { + var frame = $(modalWnd.frameElement); + var modalElem = frame.closest(".modal"); + modalElem + .data("dialog-result", dialogResult) + .data("extra-params", extraParams); + return modalElem; +} + +// Show or hide the button of the modal dialog +scada.Popup.prototype.setButtonVisible = function (modalWnd, btn, val) { + this._findModalButton(modalWnd, btn).css("display", val ? "" : "none"); +} + +// Enable or disable the button of the modal dialog +scada.Popup.prototype.setButtonEnabled = function (modalWnd, btn, val) { + var btnElem = this._findModalButton(modalWnd, btn); + if (val) { + btnElem.removeAttr("disabled"); + } else { + btnElem.attr("disabled", "disabled"); + } +} + +/********** Popup Locator **********/ + +// Popup locator object +scada.popupLocator = { + // Find and return an existing popup object + getPopup: function () { + var wnd = window; + while (wnd) { + if (wnd.popup) { + return wnd.popup; + } + wnd = wnd == window.top ? null : window.parent; + } + return null; + } +}; \ No newline at end of file diff --git a/ScadaWeb/OpenPlugins/PlgTable/js/controls/tableheader.js b/ScadaWeb/OpenPlugins/PlgTable/js/controls/tableheader.js new file mode 100644 index 000000000..230042f41 --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgTable/js/controls/tableheader.js @@ -0,0 +1,69 @@ +/* + * Fixed table header + * + * Author : Mikhail Shiryaev + * Created : 2016 + * Modified : 2016 + * + * Requires: + * - jquery + */ + +// Rapid SCADA namespace +var scada = scada || {}; + +// Fixed table headers processing object +scada.tableHeader = { + // Set the fixed header cell widths equal to the original header cell widths + _updateHeaderCellWidths: function (origHeader, fixedHeader) { + var origCells = origHeader.find("td"); + + fixedHeader.find("td").each(function (index) { + // we need spans to set width exactly + var origCell = origCells.eq(index); + + if (origCell.css("display") != "none") { + var origCellSpan = origCell.children("span"); + var cellWidth = origCellSpan.width(); // jQuery may round fractional part + origCellSpan.width(cellWidth); + $(this).find("span").width(cellWidth); + } + }); + }, + + // Create fixed table headers and bind their events + create: function () { + var thisObj = this; + + $(".table-wrapper").each(function () { + var wrapper = $(this); + var table = wrapper.children("table"); + var origHeader = table.find("tr:first"); + var fixedHeader = origHeader.clone(false); + + origHeader.addClass("orig-table-header"); + fixedHeader.addClass("fixed-table-header"); + table.append(fixedHeader); + thisObj._updateHeaderCellWidths(origHeader, fixedHeader); + + wrapper + .off("scroll") + .scroll(function () { + var fixedHeaderTop = -table.position().top; + fixedHeader.css("top", fixedHeaderTop); + }); + }); + }, + + // Update the fixed header cell widths + update: function () { + var thisObj = this; + + $(".table-wrapper").each(function () { + var wrapper = $(this); + var origHeader = $(this).find("tr.orig-table-header:first"); + var fixedHeader = $(this).find("tr.fixed-table-header:first"); + thisObj._updateHeaderCellWidths(origHeader, fixedHeader); + }); + } +} \ No newline at end of file diff --git a/ScadaWeb/ScadaWebShell5Beta/lib/font-awesome/css/font-awesome.css b/ScadaWeb/OpenPlugins/PlgTable/lib/font-awesome/css/font-awesome.css similarity index 100% rename from ScadaWeb/ScadaWebShell5Beta/lib/font-awesome/css/font-awesome.css rename to ScadaWeb/OpenPlugins/PlgTable/lib/font-awesome/css/font-awesome.css diff --git a/ScadaWeb/OpenPlugins/PlgTable/lib/font-awesome/css/font-awesome.min.css b/ScadaWeb/OpenPlugins/PlgTable/lib/font-awesome/css/font-awesome.min.css new file mode 100644 index 000000000..d0603cb4b --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgTable/lib/font-awesome/css/font-awesome.min.css @@ -0,0 +1,4 @@ +/*! + * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.5.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.5.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.5.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.5.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.5.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.5.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"} diff --git a/ScadaWeb/ScadaWebShell5Beta/lib/font-awesome/fonts/FontAwesome.otf b/ScadaWeb/OpenPlugins/PlgTable/lib/font-awesome/fonts/FontAwesome.otf similarity index 100% rename from ScadaWeb/ScadaWebShell5Beta/lib/font-awesome/fonts/FontAwesome.otf rename to ScadaWeb/OpenPlugins/PlgTable/lib/font-awesome/fonts/FontAwesome.otf diff --git a/ScadaWeb/ScadaWebShell5Beta/lib/font-awesome/fonts/fontawesome-webfont.eot b/ScadaWeb/OpenPlugins/PlgTable/lib/font-awesome/fonts/fontawesome-webfont.eot similarity index 100% rename from ScadaWeb/ScadaWebShell5Beta/lib/font-awesome/fonts/fontawesome-webfont.eot rename to ScadaWeb/OpenPlugins/PlgTable/lib/font-awesome/fonts/fontawesome-webfont.eot diff --git a/ScadaWeb/ScadaWebShell5Beta/lib/font-awesome/fonts/fontawesome-webfont.svg b/ScadaWeb/OpenPlugins/PlgTable/lib/font-awesome/fonts/fontawesome-webfont.svg similarity index 100% rename from ScadaWeb/ScadaWebShell5Beta/lib/font-awesome/fonts/fontawesome-webfont.svg rename to ScadaWeb/OpenPlugins/PlgTable/lib/font-awesome/fonts/fontawesome-webfont.svg diff --git a/ScadaWeb/OpenPlugins/PlgTable/lib/font-awesome/fonts/fontawesome-webfont.ttf b/ScadaWeb/OpenPlugins/PlgTable/lib/font-awesome/fonts/fontawesome-webfont.ttf new file mode 100644 index 000000000..26dea7951 Binary files /dev/null and b/ScadaWeb/OpenPlugins/PlgTable/lib/font-awesome/fonts/fontawesome-webfont.ttf differ diff --git a/ScadaWeb/ScadaWebShell5Beta/lib/font-awesome/fonts/fontawesome-webfont.woff b/ScadaWeb/OpenPlugins/PlgTable/lib/font-awesome/fonts/fontawesome-webfont.woff similarity index 100% rename from ScadaWeb/ScadaWebShell5Beta/lib/font-awesome/fonts/fontawesome-webfont.woff rename to ScadaWeb/OpenPlugins/PlgTable/lib/font-awesome/fonts/fontawesome-webfont.woff diff --git a/ScadaWeb/ScadaWebShell5Beta/lib/font-awesome/fonts/fontawesome-webfont.woff2 b/ScadaWeb/OpenPlugins/PlgTable/lib/font-awesome/fonts/fontawesome-webfont.woff2 similarity index 100% rename from ScadaWeb/ScadaWebShell5Beta/lib/font-awesome/fonts/fontawesome-webfont.woff2 rename to ScadaWeb/OpenPlugins/PlgTable/lib/font-awesome/fonts/fontawesome-webfont.woff2 diff --git a/ScadaWeb/OpenPlugins/PlgTable/lib/jquery/jquery.min.js b/ScadaWeb/OpenPlugins/PlgTable/lib/jquery/jquery.min.js new file mode 100644 index 000000000..b8c4187de --- /dev/null +++ b/ScadaWeb/OpenPlugins/PlgTable/lib/jquery/jquery.min.js @@ -0,0 +1,4 @@ +/*! jQuery v2.2.3 | (c) jQuery Foundation | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=a.document,e=c.slice,f=c.concat,g=c.push,h=c.indexOf,i={},j=i.toString,k=i.hasOwnProperty,l={},m="2.2.3",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return e.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:e.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a){return n.each(this,a)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(e.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor()},push:g,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){var b=a&&a.toString();return!n.isArray(a)&&b-parseFloat(b)+1>=0},isPlainObject:function(a){var b;if("object"!==n.type(a)||a.nodeType||n.isWindow(a))return!1;if(a.constructor&&!k.call(a,"constructor")&&!k.call(a.constructor.prototype||{},"isPrototypeOf"))return!1;for(b in a);return void 0===b||k.call(a,b)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?i[j.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=d.createElement("script"),b.text=a,d.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b){var c,d=0;if(s(a)){for(c=a.length;c>d;d++)if(b.call(a[d],d,a[d])===!1)break}else for(d in a)if(b.call(a[d],d,a[d])===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):g.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:h.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,e,g=0,h=[];if(s(a))for(d=a.length;d>g;g++)e=b(a[g],g,c),null!=e&&h.push(e);else for(g in a)e=b(a[g],g,c),null!=e&&h.push(e);return f.apply([],h)},guid:1,proxy:function(a,b){var c,d,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(d=e.call(arguments,2),f=function(){return a.apply(b||this,d.concat(e.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:l}),"function"==typeof Symbol&&(n.fn[Symbol.iterator]=c[Symbol.iterator]),n.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function(a,b){i["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=!!a&&"length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ga(),z=ga(),A=ga(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+M+"))|)"+L+"*\\]",O=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+N+")*)|.*)\\)|)",P=new RegExp(L+"+","g"),Q=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),R=new RegExp("^"+L+"*,"+L+"*"),S=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),T=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),U=new RegExp(O),V=new RegExp("^"+M+"$"),W={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M+"|[*])"),ATTR:new RegExp("^"+N),PSEUDO:new RegExp("^"+O),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},X=/^(?:input|select|textarea|button)$/i,Y=/^h\d$/i,Z=/^[^{]+\{\s*\[native \w/,$=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,_=/[+~]/,aa=/'|\\/g,ba=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),ca=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},da=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(ea){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function fa(a,b,d,e){var f,h,j,k,l,o,r,s,w=b&&b.ownerDocument,x=b?b.nodeType:9;if(d=d||[],"string"!=typeof a||!a||1!==x&&9!==x&&11!==x)return d;if(!e&&((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,p)){if(11!==x&&(o=$.exec(a)))if(f=o[1]){if(9===x){if(!(j=b.getElementById(f)))return d;if(j.id===f)return d.push(j),d}else if(w&&(j=w.getElementById(f))&&t(b,j)&&j.id===f)return d.push(j),d}else{if(o[2])return H.apply(d,b.getElementsByTagName(a)),d;if((f=o[3])&&c.getElementsByClassName&&b.getElementsByClassName)return H.apply(d,b.getElementsByClassName(f)),d}if(c.qsa&&!A[a+" "]&&(!q||!q.test(a))){if(1!==x)w=b,s=a;else if("object"!==b.nodeName.toLowerCase()){(k=b.getAttribute("id"))?k=k.replace(aa,"\\$&"):b.setAttribute("id",k=u),r=g(a),h=r.length,l=V.test(k)?"#"+k:"[id='"+k+"']";while(h--)r[h]=l+" "+qa(r[h]);s=r.join(","),w=_.test(a)&&oa(b.parentNode)||b}if(s)try{return H.apply(d,w.querySelectorAll(s)),d}catch(y){}finally{k===u&&b.removeAttribute("id")}}}return i(a.replace(Q,"$1"),b,d,e)}function ga(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ha(a){return a[u]=!0,a}function ia(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ja(a,b){var c=a.split("|"),e=c.length;while(e--)d.attrHandle[c[e]]=b}function ka(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function la(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function na(a){return ha(function(b){return b=+b,ha(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function oa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=fa.support={},f=fa.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=fa.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=n.documentElement,p=!f(n),(e=n.defaultView)&&e.top!==e&&(e.addEventListener?e.addEventListener("unload",da,!1):e.attachEvent&&e.attachEvent("onunload",da)),c.attributes=ia(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ia(function(a){return a.appendChild(n.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=Z.test(n.getElementsByClassName),c.getById=ia(function(a){return o.appendChild(a).id=u,!n.getElementsByName||!n.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ba,ca);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return"undefined"!=typeof b.getElementsByClassName&&p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=Z.test(n.querySelectorAll))&&(ia(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ia(function(a){var b=n.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=Z.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ia(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",O)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=Z.test(o.compareDocumentPosition),t=b||Z.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===n||a.ownerDocument===v&&t(v,a)?-1:b===n||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,g=[a],h=[b];if(!e||!f)return a===n?-1:b===n?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return ka(a,b);c=a;while(c=c.parentNode)g.unshift(c);c=b;while(c=c.parentNode)h.unshift(c);while(g[d]===h[d])d++;return d?ka(g[d],h[d]):g[d]===v?-1:h[d]===v?1:0},n):n},fa.matches=function(a,b){return fa(a,null,null,b)},fa.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(T,"='$1']"),c.matchesSelector&&p&&!A[b+" "]&&(!r||!r.test(b))&&(!q||!q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return fa(b,n,null,[a]).length>0},fa.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},fa.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},fa.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},fa.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=fa.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=fa.selectors={cacheLength:50,createPseudo:ha,match:W,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ba,ca),a[3]=(a[3]||a[4]||a[5]||"").replace(ba,ca),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||fa.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&fa.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return W.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&U.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ba,ca).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=fa.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(P," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h,t=!1;if(q){if(f){while(p){m=b;while(m=m[p])if(h?m.nodeName.toLowerCase()===r:1===m.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){m=q,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n&&j[2],m=n&&q.childNodes[n];while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if(1===m.nodeType&&++t&&m===b){k[a]=[w,n,t];break}}else if(s&&(m=b,l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),j=k[a]||[],n=j[0]===w&&j[1],t=n),t===!1)while(m=++n&&m&&m[p]||(t=n=0)||o.pop())if((h?m.nodeName.toLowerCase()===r:1===m.nodeType)&&++t&&(s&&(l=m[u]||(m[u]={}),k=l[m.uniqueID]||(l[m.uniqueID]={}),k[a]=[w,t]),m===b))break;return t-=e,t===d||t%d===0&&t/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||fa.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ha(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ha(function(a){var b=[],c=[],d=h(a.replace(Q,"$1"));return d[u]?ha(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ha(function(a){return function(b){return fa(a,b).length>0}}),contains:ha(function(a){return a=a.replace(ba,ca),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ha(function(a){return V.test(a||"")||fa.error("unsupported lang: "+a),a=a.replace(ba,ca).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Y.test(a.nodeName)},input:function(a){return X.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:na(function(){return[0]}),last:na(function(a,b){return[b-1]}),eq:na(function(a,b,c){return[0>c?c+b:c]}),even:na(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:na(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:na(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:na(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function ra(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j,k=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(j=b[u]||(b[u]={}),i=j[b.uniqueID]||(j[b.uniqueID]={}),(h=i[d])&&h[0]===w&&h[1]===f)return k[2]=h[2];if(i[d]=k,k[2]=a(b,c,g))return!0}}}function sa(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ta(a,b,c){for(var d=0,e=b.length;e>d;d++)fa(a,b[d],c);return c}function ua(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(c&&!c(f,d,e)||(g.push(f),j&&b.push(h)));return g}function va(a,b,c,d,e,f){return d&&!d[u]&&(d=va(d)),e&&!e[u]&&(e=va(e,f)),ha(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ta(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:ua(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=ua(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=ua(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function wa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=ra(function(a){return a===b},h,!0),l=ra(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[ra(sa(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return va(i>1&&sa(m),i>1&&qa(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(Q,"$1"),c,e>i&&wa(a.slice(i,e)),f>e&&wa(a=a.slice(e)),f>e&&qa(a))}m.push(c)}return sa(m)}function xa(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,o,q,r=0,s="0",t=f&&[],u=[],v=j,x=f||e&&d.find.TAG("*",k),y=w+=null==v?1:Math.random()||.1,z=x.length;for(k&&(j=g===n||g||k);s!==z&&null!=(l=x[s]);s++){if(e&&l){o=0,g||l.ownerDocument===n||(m(l),h=!p);while(q=a[o++])if(q(l,g||n,h)){i.push(l);break}k&&(w=y)}c&&((l=!q&&l)&&r--,f&&t.push(l))}if(r+=s,c&&s!==r){o=0;while(q=b[o++])q(t,u,g,h);if(f){if(r>0)while(s--)t[s]||u[s]||(u[s]=F.call(i));u=ua(u)}H.apply(i,u),k&&!f&&u.length>0&&r+b.length>1&&fa.uniqueSort(i)}return k&&(w=y,j=v),t};return c?ha(f):f}return h=fa.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=wa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,xa(e,d)),f.selector=a}return f},i=fa.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ba,ca),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=W.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ba,ca),_.test(j[0].type)&&oa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&qa(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,!b||_.test(a)&&oa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ia(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ia(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ja("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ia(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ja("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ia(function(a){return null==a.getAttribute("disabled")})||ja(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fa}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.uniqueSort=n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},v=function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c},w=n.expr.match.needsContext,x=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,y=/^.[^:#\[\.,]*$/;function z(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(y.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return h.call(b,a)>-1!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(z(this,a||[],!1))},not:function(a){return this.pushStack(z(this,a||[],!0))},is:function(a){return!!z(this,"string"==typeof a&&w.test(a)?n(a):a||[],!1).length}});var A,B=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,C=n.fn.init=function(a,b,c){var e,f;if(!a)return this;if(c=c||A,"string"==typeof a){if(e="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:B.exec(a),!e||!e[1]&&b)return!b||b.jquery?(b||c).find(a):this.constructor(b).find(a);if(e[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(e[1],b&&b.nodeType?b.ownerDocument||b:d,!0)),x.test(e[1])&&n.isPlainObject(b))for(e in b)n.isFunction(this[e])?this[e](b[e]):this.attr(e,b[e]);return this}return f=d.getElementById(e[2]),f&&f.parentNode&&(this.length=1,this[0]=f),this.context=d,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?void 0!==c.ready?c.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};C.prototype=n.fn,A=n(d);var D=/^(?:parents|prev(?:Until|All))/,E={children:!0,contents:!0,next:!0,prev:!0};n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=w.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.uniqueSort(f):f)},index:function(a){return a?"string"==typeof a?h.call(n(a),this[0]):h.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.uniqueSort(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function F(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return u(a,"parentNode")},parentsUntil:function(a,b,c){return u(a,"parentNode",c)},next:function(a){return F(a,"nextSibling")},prev:function(a){return F(a,"previousSibling")},nextAll:function(a){return u(a,"nextSibling")},prevAll:function(a){return u(a,"previousSibling")},nextUntil:function(a,b,c){return u(a,"nextSibling",c)},prevUntil:function(a,b,c){return u(a,"previousSibling",c)},siblings:function(a){return v((a.parentNode||{}).firstChild,a)},children:function(a){return v(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(E[a]||n.uniqueSort(e),D.test(a)&&e.reverse()),this.pushStack(e)}});var G=/\S+/g;function H(a){var b={};return n.each(a.match(G)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?H(a):n.extend({},a);var b,c,d,e,f=[],g=[],h=-1,i=function(){for(e=a.once,d=b=!0;g.length;h=-1){c=g.shift();while(++h-1)f.splice(c,1),h>=c&&h--}),this},has:function(a){return a?n.inArray(a,f)>-1:f.length>0},empty:function(){return f&&(f=[]),this},disable:function(){return e=g=[],f=c="",this},disabled:function(){return!f},lock:function(){return e=g=[],c||(f=c=""),this},locked:function(){return!!e},fireWith:function(a,c){return e||(c=c||[],c=[a,c.slice?c.slice():c],g.push(c),b||i()),this},fire:function(){return j.fireWith(this,arguments),this},fired:function(){return!!d}};return j},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().progress(c.notify).done(c.resolve).fail(c.reject):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=e.call(arguments),d=c.length,f=1!==d||a&&n.isFunction(a.promise)?d:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?e.call(arguments):d,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(d>1)for(i=new Array(d),j=new Array(d),k=new Array(d);d>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().progress(h(b,j,i)).done(h(b,k,c)).fail(g.reject):--f;return f||g.resolveWith(k,c),g.promise()}});var I;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(I.resolveWith(d,[n]),n.fn.triggerHandler&&(n(d).triggerHandler("ready"),n(d).off("ready"))))}});function J(){d.removeEventListener("DOMContentLoaded",J),a.removeEventListener("load",J),n.ready()}n.ready.promise=function(b){return I||(I=n.Deferred(),"complete"===d.readyState||"loading"!==d.readyState&&!d.documentElement.doScroll?a.setTimeout(n.ready):(d.addEventListener("DOMContentLoaded",J),a.addEventListener("load",J))),I.promise(b)},n.ready.promise();var K=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)K(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},L=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function M(){this.expando=n.expando+M.uid++}M.uid=1,M.prototype={register:function(a,b){var c=b||{};return a.nodeType?a[this.expando]=c:Object.defineProperty(a,this.expando,{value:c,writable:!0,configurable:!0}),a[this.expando]},cache:function(a){if(!L(a))return{};var b=a[this.expando];return b||(b={},L(a)&&(a.nodeType?a[this.expando]=b:Object.defineProperty(a,this.expando,{value:b,configurable:!0}))),b},set:function(a,b,c){var d,e=this.cache(a);if("string"==typeof b)e[b]=c;else for(d in b)e[d]=b[d];return e},get:function(a,b){return void 0===b?this.cache(a):a[this.expando]&&a[this.expando][b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=a[this.expando];if(void 0!==f){if(void 0===b)this.register(a);else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in f?d=[b,e]:(d=e,d=d in f?[d]:d.match(G)||[])),c=d.length;while(c--)delete f[d[c]]}(void 0===b||n.isEmptyObject(f))&&(a.nodeType?a[this.expando]=void 0:delete a[this.expando])}},hasData:function(a){var b=a[this.expando];return void 0!==b&&!n.isEmptyObject(b)}};var N=new M,O=new M,P=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,Q=/[A-Z]/g;function R(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(Q,"-$&").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:P.test(c)?n.parseJSON(c):c; +}catch(e){}O.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return O.hasData(a)||N.hasData(a)},data:function(a,b,c){return O.access(a,b,c)},removeData:function(a,b){O.remove(a,b)},_data:function(a,b,c){return N.access(a,b,c)},_removeData:function(a,b){N.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=O.get(f),1===f.nodeType&&!N.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),R(f,d,e[d])));N.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){O.set(this,a)}):K(this,function(b){var c,d;if(f&&void 0===b){if(c=O.get(f,a)||O.get(f,a.replace(Q,"-$&").toLowerCase()),void 0!==c)return c;if(d=n.camelCase(a),c=O.get(f,d),void 0!==c)return c;if(c=R(f,d,void 0),void 0!==c)return c}else d=n.camelCase(a),this.each(function(){var c=O.get(this,d);O.set(this,d,b),a.indexOf("-")>-1&&void 0!==c&&O.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){O.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=N.get(a,b),c&&(!d||n.isArray(c)?d=N.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return N.get(a,c)||N.access(a,c,{empty:n.Callbacks("once memory").add(function(){N.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.length",""],thead:[1,"",""],col:[2,"",""],tr:[2,"",""],td:[3,"",""],_default:[0,"",""]};$.optgroup=$.option,$.tbody=$.tfoot=$.colgroup=$.caption=$.thead,$.th=$.td;function _(a,b){var c="undefined"!=typeof a.getElementsByTagName?a.getElementsByTagName(b||"*"):"undefined"!=typeof a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function aa(a,b){for(var c=0,d=a.length;d>c;c++)N.set(a[c],"globalEval",!b||N.get(b[c],"globalEval"))}var ba=/<|?\w+;/;function ca(a,b,c,d,e){for(var f,g,h,i,j,k,l=b.createDocumentFragment(),m=[],o=0,p=a.length;p>o;o++)if(f=a[o],f||0===f)if("object"===n.type(f))n.merge(m,f.nodeType?[f]:f);else if(ba.test(f)){g=g||l.appendChild(b.createElement("div")),h=(Y.exec(f)||["",""])[1].toLowerCase(),i=$[h]||$._default,g.innerHTML=i[1]+n.htmlPrefilter(f)+i[2],k=i[0];while(k--)g=g.lastChild;n.merge(m,g.childNodes),g=l.firstChild,g.textContent=""}else m.push(b.createTextNode(f));l.textContent="",o=0;while(f=m[o++])if(d&&n.inArray(f,d)>-1)e&&e.push(f);else if(j=n.contains(f.ownerDocument,f),g=_(l.appendChild(f),"script"),j&&aa(g),c){k=0;while(f=g[k++])Z.test(f.type||"")&&c.push(f)}return l}!function(){var a=d.createDocumentFragment(),b=a.appendChild(d.createElement("div")),c=d.createElement("input");c.setAttribute("type","radio"),c.setAttribute("checked","checked"),c.setAttribute("name","t"),b.appendChild(c),l.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,b.innerHTML="x",l.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var da=/^key/,ea=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,fa=/^([^.]*)(?:\.(.+)|)/;function ga(){return!0}function ha(){return!1}function ia(){try{return d.activeElement}catch(a){}}function ja(a,b,c,d,e,f){var g,h;if("object"==typeof b){"string"!=typeof c&&(d=d||c,c=void 0);for(h in b)ja(a,h,c,d,b[h],f);return a}if(null==d&&null==e?(e=c,d=c=void 0):null==e&&("string"==typeof c?(e=d,d=void 0):(e=d,d=c,c=void 0)),e===!1)e=ha;else if(!e)return a;return 1===f&&(g=e,e=function(a){return n().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=n.guid++)),a.each(function(){n.event.add(this,b,e,d,c)})}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return"undefined"!=typeof n&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(G)||[""],j=b.length;while(j--)h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=N.hasData(a)&&N.get(a);if(r&&(i=r.events)){b=(b||"").match(G)||[""],j=b.length;while(j--)if(h=fa.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&N.remove(a,"handle events")}},dispatch:function(a){a=n.event.fix(a);var b,c,d,f,g,h=[],i=e.call(arguments),j=(N.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())a.rnamespace&&!a.rnamespace.test(g.namespace)||(a.handleObj=g,a.data=g.data,d=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==d&&(a.result=d)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&("click"!==a.type||isNaN(a.button)||a.button<1))for(;i!==this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>-1:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,la=/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Vertical Spacer Begin + Vertical Spacer End + + + + + + + + + + + + + + + + +