History

PhasorWebUI: Added import / export config, serialized section and inital connection builder to wizard screen

J. Ritchie Carroll committed 6 years ago

Commit 352378c1 parent 139422b4 files · +887 −135Browse files

Unified · Split

4 files
  1. Source/Libraries/Adapters/PhasorWebUI/Model/SynchrophasorProtocol.cs +10 −0
  2. Source/Libraries/Adapters/PhasorWebUI/PhasorHub.cs +15 −1
  3. Source/Libraries/Adapters/PhasorWebUI/PhasorWebUI.csproj +1 −0
  4. Source/Libraries/Adapters/PhasorWebUI/Views/AddSynchrophasorDevice.cshtml +861 −134
Source/Libraries/Adapters/PhasorWebUI/Model/SynchrophasorProtocol.cs+10−0
@@ -0,0 +1,10 @@
1+#pragma warning disable 1591
2+
3+namespace PhasorWebUI.Model
4+{
5+ public class SynchrophasorProtocol
6+ {
7+ public string Acronym;
8+ public string Name;
9+ }
10+}
Source/Libraries/Adapters/PhasorWebUI/PhasorHub.cs+15−1
@@ -731,7 +731,7 @@ namespace PhasorWebUI · 730 unmodified lines
731731 if (!string.IsNullOrWhiteSpace(connectionString))
732732 {
733733 Dictionary<string, string> settings = connectionString.ParseKeyValuePairs();
734− protocolID = DataContext.Table<Protocol>().QueryRecordWhere("Acronym = {0}", settings["phasorProtocol"]).ID;
734+ protocolID = GetProtocolID(settings["phasorProtocol"]);
735735 }
736736
737737 derivedFrame = new ConfigurationFrame
@@ -818,11 +818,25 @@ namespace PhasorWebUI · 80 unmodified lines
818818 return tagTemplates;
819819 }
820820
821+ public int GetProtocolID(string protocolAcronym)
822+ {
823+ return DataContext.Table<Protocol>().QueryRecordWhere("Acronym = {0}", protocolAcronym).ID;
824+ }
825+
821826 public string GetProtocolCategory(int protocolID)
822827 {
823828 return DataContext.Table<Protocol>().QueryRecordWhere("ID = {0}", protocolID).Category;
824829 }
825830
831+ public IEnumerable<SynchrophasorProtocol> GetSynchrophasorProtocols()
832+ {
833+ return Enum.GetValues(typeof(PhasorProtocol)).Cast<PhasorProtocol>().Select(protocol => new SynchrophasorProtocol
834+ {
835+ Acronym = protocol.ToString(),
836+ Name = protocol.GetFormattedProtocolName()
837+ });
838+ }
839+
826840 public void ValidateCalculatorConfigurations(int? historianID)
827841 {
828842 const int Avg = 0, Max = 1, Min = 2;
Source/Libraries/Adapters/PhasorWebUI/PhasorWebUI.csproj+1−0
@@ -94,6 +94,7 @@ · 93 unmodified lines
9494 <Compile Include="Model\SignalType.cs" />
9595 <Compile Include="Model\TagTemplate.cs" />
9696 <Compile Include="PhasorHub.cs" />
97+ <Compile Include="Model\SynchrophasorProtocol.cs" />
9798 </ItemGroup>
9899 <ItemGroup>
99100 <ProjectReference Include="..\..\GSF.Communication\GSF.Communication.csproj">
Source/Libraries/Adapters/PhasorWebUI/Views/AddSynchrophasorDevice.cshtml+861−134
@@ -72,6 +72,7 @@ · 71 unmodified lines
7272 bool userIsEditor = UserIsEditor();
7373 }
7474 @section StyleSheets {
75+ <link href="@Resources.Root/Shared/Content/jquery-ui.css" rel="stylesheet">
7576 <link href="@Resources.Root/Shared/Content/select2.css" rel="stylesheet">
7677 <link href="@Resources.Root/Shared/Content/select2-bootstrap.css" rel="stylesheet">
7778 <style>
@@ -79,6 +80,64 @@ · 1 unmodified line
7980 html, body {
8081 height: 100%;
8182 }
83+
84+ .tabs-nohdr {
85+ padding: 0;
86+ background: none;
87+ border-width: 0;
88+ }
89+
90+ .tabs-nohdr .ui-tabs-nav {
91+ padding: 0;
92+ background: transparent;
93+ border: none;
94+ }
95+
96+ .tabs-nohdr .ui-tabs-panel {
97+ border: none;
98+ padding: 0;
99+ margin: 0;
100+ }
101+
102+ #tabs {
103+ border: none;
104+ padding: 0;
105+ }
106+
107+ #tabs ul {
108+ border: none;
109+ background: none;
110+ }
111+
112+ #tabs ul li {
113+ outline: none;
114+ }
115+
116+ #tabs ul li:nth-child(1) {
117+ margin-left: -3px;
118+ }
119+
120+ #tabs ul li a {
121+ outline: none;
122+ padding: 2px 8px;
123+ }
124+
125+ .ui-tabs-nav li.ui-tabs-active,
126+ .ui-tabs-nav li:hover {
127+ background: #337ab7;
128+ }
129+
130+ #tab1 {
131+ padding: 10px 0px;
132+ }
133+
134+ #tab2 {
135+ padding: 5px 0px;
136+ }
137+
138+ .input-group-btn button {
139+ font-size: 14px;
140+ }
82141
83142 span.table-cell-text {
84143 display: table-cell;
@@ -122,6 +181,7 @@ · 37 unmodified lines
122181
123182 div.save-button {
124183 margin-top: -33px;
184+ margin-right: 2px;
125185 z-index: 10;
126186 }
127187
@@ -133,6 +193,11 @@ · 5 unmodified lines
133193 position: relative;
134194 }
135195
196+ div.panel-heading-condensed {
197+ height: 30px;
198+ padding-top: 5px
199+ }
200+
136201 tr.separator-top {
137202 visibility: hidden;
138203 border-bottom-width: 4px;
@@ -158,6 +223,13 @@ · 19 unmodified lines
158223 vertical-align: middle;
159224 }
160225
226+ label.col-input {
227+ padding-right: 0;
228+ margin-right: -10px;
229+ margin-left: 10px;
230+ white-space: nowrap;
231+ }
232+
161233 tr.top-border {
162234 border-top-width: 2px;
163235 border-top-style: solid;
@@ -167,11 +239,86 @@ · 3 unmodified lines
167239 padding-bottom: 10px;
168240 }
169241
242+ .form-group {
243+ padding-top: 5px;
244+ }
245+
246+ .top-padding {
247+ padding-top: 5px;
248+ }
249+
250+ .top-margin {
251+ margin-top: 5px;
252+ }
253+
254+ .bottom-margin {
255+ margin-bottom: 5px;
256+ }
257+
258+ .connection-status {
259+ background-color: black;
260+ padding: 10px;
261+ margin: 0;
262+ overflow: auto;
263+ }
264+
265+ .glyphicon-interpret-type {
266+ color: darkgrey;
267+ vertical-align: middle;
268+ font-size: 15pt;
269+ margin-right: -5px;
270+ margin-left: -5px;
271+ }
272+
273+ .glyphicon-interpret-result {
274+ color: darkgrey;
275+ vertical-align: middle;
276+ font-size: 17pt;
277+ }
278+
279+ .glyphicon-delete-sequence {
280+ vertical-align: text-top;
281+ color: red;
282+ font-size: larger;
283+ }
284+
285+ .glyphicon-action-button {
286+ color: #606060;
287+ vertical-align: text-top;
288+ }
289+
290+ /* Collapsable button style */
291+ .btn-collapsable.btn {
292+ color: #606060;
293+ font-family: "Glyphicons Halflings";
294+ font-size: 8pt;
295+ position: relative;
296+ top: 0;
297+ right: -10px;
298+ padding: 0 2px 2px 2px;
299+ height: 18px;
300+ float: right !important;
301+ }
302+
303+ /* Collapsable button icon when content is shown - arrow down */
304+ .btn-collapsable.btn:after {
305+ content: "\e114";
306+ }
307+
308+ /* Collapsable button icon when content is hidden - arrow right */
309+ .btn-collapsable.btn.collapsed:after {
310+ content: "\e080";
311+ }
312+
170313 @@media screen {
171314 .page-logo {
172315 margin-bottom: 2px;
173316 margin-top: -38px;
174317 }
318+
319+ .offset-width {
320+ width: 10px
321+ }
175322 }
176323
177324 @@media screen and (min-width: 638px) {
@@ -179,6 +326,10 @@ · 1 unmodified line
179326 margin-bottom: 2px;
180327 margin-top: -12px;
181328 }
329+
330+ .offset-width {
331+ width: 10px
332+ }
182333 }
183334
184335 @@media screen and (min-width: 868px) {
@@ -186,6 +337,10 @@ · 1 unmodified line
186337 margin-bottom: 2px;
187338 margin-top: -12px;
188339 }
340+
341+ .offset-width {
342+ width: 10px
343+ }
189344 }
190345
191346 @@media screen and (min-width: 992px) {
@@ -193,6 +348,10 @@ · 1 unmodified line
193348 margin-bottom: -25px;
194349 margin-top: -4px;
195350 }
351+
352+ .offset-width {
353+ width: 150px
354+ }
196355 }
197356 </style>
198357 }
@@ -481,7 +640,7 @@ · 282 unmodified lines
481640 }
482641 else {
483642 result(tagName);
484− }
643+ }
485644 });
486645 });
487646
@@ -864,6 +1023,36 @@ · 376 unmodified lines
8641023 firstCurrent = false;
8651024 }
8661025 }
1026+
1027+ cell.serializeCell = function () {
1028+ var phasorDefinitions = [];
1029+
1030+ cell.PhasorDefinitions.forEach(function (phasorDefinition) {
1031+ const isVoltage = phasorDefinition.PhasorType.toLowerCase() === "voltage";
1032+
1033+ phasorDefinitions.push({
1034+ ID: phasorDefinition.ID,
1035+ Label: phasorDefinition.Label(),
1036+ PhasorType: phasorDefinition.PhasorType,
1037+ Phase: phasorDefinition.Phase(),
1038+ DestinationPhasorID: isVoltage ? phasorDefinition.DestinationPhasorID : phasorDefinition.Current.AssociatedVoltage(),
1039+ NominalVoltage: isVoltage ? phasorDefinition.Voltage.NominalVoltage() : phasorDefinition.Current.AssociatedNominalVoltage(),
1040+ SourceIndex: phasorDefinition.SourceIndex
1041+ });
1042+ });
1043+
1044+ return {
1045+ ID: cell.ID,
1046+ ParentID: cell.ParentID,
1047+ IDCode: cell.IDCode,
1048+ StationName: self.configFrame().Cells.length === 1 ? self.deviceName() : cell.IDLabel(),
1049+ IDLabel: cell.IDLabel(),
1050+ FrequencyDefinition: cell.FrequencyDefinition,
1051+ PhasorDefinitions: phasorDefinitions,
1052+ AnalogDefinitions: cell.AnalogDefinitions,
1053+ DigitalDefinitions: cell.DigitalDefinitions
1054+ };
1055+ };
8671056 }
8681057
8691058 // Make sure table is refreshed
@@ -1127,6 +1316,75 @@ · 257 unmodified lines
11271316 return current;
11281317 };
11291318
1319+ self.serializeConfiguration = function () {
1320+ const configFrame = self.configFrame();
1321+ const cells = configFrame.Cells;
1322+ var serializedCells = [];
1323+
1324+ if (cells) {
1325+ cells.forEach(function (cell) {
1326+ serializedCells.push(cell.serializeCell());
1327+ });
1328+ }
1329+
1330+ return {
1331+ IDCode: configFrame.IDCode,
1332+ StationName: self.deviceName(),
1333+ IDLabel: self.deviceAcronym(),
1334+ FrameRate: configFrame.FrameRate,
1335+ ConnectionString: configFrame.ConnectionString,
1336+ ProtocolID: configFrame.ProtocolID,
1337+ Cells: serializedCells
1338+ };
1339+ };
1340+
1341+ self.saveConfiguration = function (fileName) {
1342+ const data = JSON.stringify(self.serializeConfiguration(), null, 2);
1343+ const anchor = $("#saveConfigurationFileLink");
1344+
1345+ if (typeof anchor[0].download != "undefined") {
1346+ anchor.attr("href", "data:text/json;charset=utf-8," + encodeURIComponent(data));
1347+ anchor.attr("download", fileName);
1348+ anchor[0].click();
1349+ } else {
1350+ if (isIE)
1351+ window.navigator.msSaveBlob(new Blob([data]), fileName);
1352+ else
1353+ window.open("data:text/json;charset=utf-8," + encodeURIComponent(data), "_blank", "");
1354+ }
1355+ };
1356+
1357+ self.loadConfiguration = function (fileBlob) {
1358+ var reader = new FileReader();
1359+
1360+ reader.onload = function () {
1361+ if (!self.phasorHubIsConnected())
1362+ return;
1363+
1364+ const configFrame = JSON.parse(reader.result);
1365+
1366+ self.voltages.removeAll();
1367+ self.currents.removeAll();
1368+
1369+ $("#responsiveTableDiv").show();
1370+ $("[id=voltageRow]").css("visibility", "hidden");
1371+ $("[id=currentRow]").css("visibility", "hidden");
1372+ $("#bulkSelectionButtons").hide();
1373+ showLoadingLabel();
1374+
1375+ self.configFrameDetails("Loading...");
1376+
1377+ self.loadConfigFrame(configFrame);
1378+ }
1379+
1380+ reader.onloadend = function () {
1381+ if (reader.error && reader.error.message)
1382+ showErrorMessage("Failed to load configuration: " + reader.error.message);
1383+ };
1384+
1385+ reader.readAsText(fileBlob);
1386+ };
1387+
11301388 // Validations
11311389 self.deviceAcronym.extend({
11321390 required: true,
@@ -2681,11 +2939,18 @@ · 1548 unmodified lines
26812939 });
26822940 }
26832941
2942+ function getActiveTab() {
2943+ return $("#tabs").tabs("option", "active");
2944+ }
2945+
26842946 function resizePageElements() {
26852947 if (deviceID === 0) {
2686− let inputConfigFileWidth = $("#inputConfigFile").width();
2687− $("#inputDeviceAcronym").width(inputConfigFileWidth);
2688− $("#inputDeviceName").width(inputConfigFileWidth);
2948+ let width = $("#connectionSettings").hasClass("in") ?
2949+ (getActiveTab() === 0 ? $("#inputConfigFile").width() : $("#inputConnectionString").width()) :
2950+ $("#connectionSettingsPanel").width() * 0.60;
2951+
2952+ $("#inputDeviceAcronym").width(width);
2953+ $("#inputDeviceName").width(width);
26892954 }
26902955
26912956 $("#contentWell").height(calculateRemainingBodyHeight() - $("#headerArea").outerHeight(true));
@@ -2707,9 +2972,104 @@ · 15 unmodified lines
27072972 else
27082973 showInfoMessage(message);
27092974 }
2975+
2976+ function showConnectionBuilderDialog() {
2977+ switch ($("#selectTransport").val()) {
2978+ case "TCP":
2979+ $("#tcpBuilderDialog").show();
2980+ $("#inputTcpHostName").focus();
2981+ break;
2982+ case "UDP":
2983+ $("#udpBuilderDialog").show();
2984+ $("#inputUdpPort").focus();
2985+ break;
2986+ case "SERIAL":
2987+ $("#serialBuilderDialog").show();
2988+ $("#inputSerialPort").focus();
2989+ break;
2990+ // TODO: case "FILE:"
2991+ }
2992+ }
2993+
2994+ function applyTcpConnectionSettings() {
2995+ //viewModel.connectionString(String.format("hostName = {0}; port = {1}",
2996+ // $("#inputTcpHostName").val(),
2997+ // $("#inputTcpPort").val()));
2998+
2999+ $('#tcpBuilderDialog').hide();
3000+ }
3001+
3002+ function applyUdpConnectionSettings() {
3003+ //viewModel.connectionString(String.format("port = {0}; interface = {1}",
3004+ // $("#inputUdpPort").val(),
3005+ // $("#inputUdpInterface").val()));
3006+
3007+ $('#udpBuilderDialog').hide();
3008+ }
3009+
3010+ function applySerialConnectionSettings() {
3011+ //viewModel.connectionString(String.format("portName = COM{0}; baudRate = {1}; dataBits = {2}; parity = {3}; stopBits = {4}",
3012+ // $("#inputSerialPort").val(),
3013+ // $("#selectSerialBaudRate").val(),
3014+ // $("#inputSerialDataBits").val(),
3015+ // $("#selectSerialParity").val(),
3016+ // $("#selectSerialStopBits").val()));
3017+
3018+ $('#serialBuilderDialog').hide();
3019+ }
3020+
3021+ function connectToDevice() {
3022+ $("#connectionStatus").collapse("hide");
3023+
3024+ if (hubIsConnected) {
3025+ //if (deviceConnected)
3026+ // dataHub.modbusDisconnect();
3027+ //else
3028+ // dataHub.modbusConnect($("#inputConnectionString").val());
3029+ } else {
3030+ showErrorMessage("Device is inaccessible, cannot connect to server.", -1);
3031+ }
3032+ }
3033+
3034+ function scrollConnectionStatusWindowToBottom() {
3035+ const connectionStatusWindow = $("#connectionStatusWindow");
3036+ connectionStatusWindow.scrollTop(connectionStatusWindow[0].scrollHeight);
3037+ }
3038+
3039+ function showSaveConfigurationFileNameDialog() {
3040+ $("#saveConfigurationFileNameDialog").toggle();
3041+
3042+ if ($("#saveConfigurationFileNameDialog").is(":visible")) {
3043+ $("#inputConfigurationFileName").val(viewModel.deviceAcronym() + ".json");
3044+ $("#inputConfigurationFileName").focus();
3045+ }
3046+ }
3047+
3048+ function showLoadConfigurationFileNameDialog() {
3049+ $("#loadConfigurationFileName").trigger("click");
3050+ }
3051+
3052+ function loadConfigurationFile(event) {
3053+ viewModel.loadConfiguration(event.target.files[0]);
3054+ $("#loadConfigurationFileName").val("");
3055+ }
27103056
27113057 // Page initialization function
27123058 $(function () {
3059+ $("#tabs").tabs({
3060+ active: 0,
3061+ activate: function(event, ui) {
3062+ switch (ui.newPanel.attr("id")) {
3063+ case "tab1":
3064+ break;
3065+ case "tab2":
3066+ break;
3067+ }
3068+
3069+ resizePageElements();
3070+ }
3071+ });
3072+
27133073 // Connect to phasor hub
27143074 phasorHub = $.connection.phasorHub.server;
27153075 phasorHubClient = $.connection.phasorHub.client;
@@ -2795,6 +3155,82 @@ · 79 unmodified lines
27953155 ko.watch(viewModel.devices, { depth: -1 }, function (parents, child, item) {
27963156 viewModel.isDirty(true);
27973157 });
3158+
3159+ $("#connectionSettings").on("shown.bs.collapse", function() {
3160+ resizePageElements();
3161+ });
3162+
3163+ $("#connectionSettings").on("hidden.bs.collapse", function() {
3164+ resizePageElements();
3165+ });
3166+
3167+ $("#connectionStatus").on("shown.bs.collapse", function() {
3168+ scrollConnectionStatusWindowToBottom();
3169+ });
3170+
3171+ $("#tcpBuilderDialog").modal({
3172+ show: false,
3173+ backdrop: "static",
3174+ keyboard: false
3175+ });
3176+
3177+ $("#udpBuilderDialog").modal({
3178+ show: false,
3179+ backdrop: "static",
3180+ keyboard: false
3181+ });
3182+
3183+ $("#serialBuilderDialog").modal({
3184+ show: false,
3185+ backdrop: "static",
3186+ keyboard: false
3187+ });
3188+
3189+ $("#saveDeviceConnectionDialog").modal({
3190+ show: false,
3191+ backdrop: "static",
3192+ keyboard: false
3193+ });
3194+
3195+ $("#inputTcpHostName").focus(function() {
3196+ $(this).select();
3197+ });
3198+
3199+ $("#inputTcpPort").focus(function() {
3200+ $(this).select();
3201+ });
3202+
3203+ $("#inputUdpPort").focus(function() {
3204+ $(this).select();
3205+ });
3206+
3207+ $("#inputUdpInterface").focus(function() {
3208+ $(this).select();
3209+ });
3210+
3211+ $("#inputSerialPort").focus(function() {
3212+ $(this).select();
3213+ });
3214+
3215+ $("#inputSerialDataBits").focus(function() {
3216+ $(this).select();
3217+ });
3218+
3219+ $("#inputDeviceAcronym").focus(function() {
3220+ $(this).select();
3221+ });
3222+
3223+ $("#inputDeviceName").focus(function() {
3224+ $(this).select();
3225+ });
3226+
3227+ $("#latitude").focus(function() {
3228+ $(this).select();
3229+ });
3230+
3231+ $("#longitude").focus(function() {
3232+ $(this).select();
3233+ });
27983234
27993235 // Let view model know about hub connectivity changes
28003236 $(window).on("hubConnected", function () {
@@ -2837,6 +3273,20 @@ · 36 unmodified lines
28373273 });
28383274 }
28393275
3276+ const protocolSelect = $("#selectProtocol");
3277+
3278+ // Load available synchrophasor protocols, if not already loaded
3279+ if (protocolSelect.children("option").length === 0) {
3280+ phasorHub.getSynchrophasorProtocols().done(function (protocols) {
3281+ for (let i = 0; i < protocols.length; i++) {
3282+ const protocol = protocols[i];
3283+ protocolSelect.append($("<option>", { value: protocol.Acronym, text: protocol.Name }));
3284+ }
3285+
3286+ protocolSelect.autoSizeSelect();
3287+ });
3288+ }
3289+
28403290 const templateTypeSelect = $("#templateType");
28413291
28423292 // Load available calculation templates, if not already loaded
@@ -2906,6 +3356,43 @@ · 63 unmodified lines
29063356
29073357 $("#inputConfigFile").tooltip("hide");
29083358 });
3359+
3360+ // Setup save configuration file name dialog functionality
3361+ $("#saveConfigurationFile").click(function() {
3362+ $("#saveConfigurationFileNameDialog").hide();
3363+
3364+ var fileName = notNull($("#inputConfigurationFileName").val());
3365+
3366+ if (fileName.length === 0)
3367+ fileName = viewModel.deviceAcronym() + ".json";
3368+
3369+ if (!fileName.endsWith(".json"))
3370+ fileName += ".json";
3371+
3372+ viewModel.saveConfiguration(fileName);
3373+ });
3374+
3375+ // Make enter key auto-click save
3376+ $("#inputConfigurationFileName").keyup(function(event) {
3377+ if (event.keyCode === 13)
3378+ $("#saveConfigurationFile").click();
3379+ });
3380+
3381+ // Auto-select all text on focus
3382+ $("#inputConifgurationFileName").focus(function() {
3383+ $(this).select();
3384+ });
3385+
3386+ // Prevent default form submission when user presses enter
3387+ $("#saveConfigurationFileNameDialog").submit(function() {
3388+ return false;
3389+ });
3390+
3391+ // Auto-hide pop-up form when user clicks outside form area
3392+ $("#saveConfigurationFileNameDialog").focusout(function() {
3393+ if (!$("#saveConfigurationFileNameDialog").is(":hover") && !$("#saveConfigurationFileNameDialogButton").is(":hover"))
3394+ $("#saveConfigurationFileNameDialog").hide();
3395+ });
29093396
29103397 resizePageElements();
29113398
@@ -2926,12 +3413,96 @@ · 14 unmodified lines
29263413 <form>
29273414 <table style="width: 100%">
29283415 <tr data-bind="visible: !editMode()">
2929− <td class="input-label">
2930− <label for="inputConfigFile">Connection File:</label>
2931− </td>
2932− <td>
2933− <div class="input-group input-area">
2934− <input id="inputConfigFile" type="file" placeholder="Connection file name..." title="Select&nbsp;PMUConnection&nbsp;file..." accept=".PmuConnection,.xml,text/xml" data-toggle="tooltip" data-placement="right" class="form-control" style="width: 100%">
3416+ <td colspan="2">
3417+ @* Connection Settings and Status Panel *@
3418+ <div class="panel panel-primary" id="connectionSettingsPanel" style="margin: 2px">
3419+ <div class="panel-heading panel-heading-condensed clearfix" onclick="$('#connectionSettings').collapse('toggle')">
3420+ <label>
3421+ Connection&nbsp;Settings
3422+ </label>
3423+ <div class="pull-right">
3424+ <span id="notConnected">Not&nbsp;Connected&nbsp;&nbsp;<span class="glyphicon glyphicon-ban-circle" style="color: #FF003B;"></span></span>&nbsp;&nbsp;
3425+ <span id="attemptingConnection" style="display: none">Attempting&nbsp;Connection&nbsp;&nbsp;<span class="glyphicon glyphicon-refresh glyphicon-spin" style="color: yellow"></span></span>&nbsp;&nbsp;
3426+ <span id="connected" style="display: none">Connected&nbsp;&nbsp;<span class="glyphicon glyphicon-check" style="color: chartreuse"></span></span>
3427+ <button type="button" class="btn btn-xs btn-collapsable" data-toggle="collapse" data-target="#connectionSettings"></button>
3428+ </div>
3429+ </div>
3430+ <div class="panel-body collapse in" id="connectionSettings" style="padding: 2px 6px">
3431+ <div id="tabs">
3432+ <ul>
3433+ <li>
3434+ <a href="#tab1">Use Connection File</a>
3435+ </li>
3436+ <li><a href="#tab2">Build Connection</a></li>
3437+ </ul>
3438+ <hr style="margin: 0; border-top-color: lightgray" />
3439+ <div id="tab1" class="ui-tabs-active">
3440+ <table style="width: 100%">
3441+ <tr>
3442+ <td class="input-label" style="width: 150px">
3443+ <label for="inputConfigFile" style="white-space: nowrap">Connection File:</label>
3444+ </td>
3445+ <td>
3446+ <div class="input-group input-area">
3447+ <input id="inputConfigFile" type="file" placeholder="Connection file name..." title="Select&nbsp;PMUConnection&nbsp;file..." accept=".PmuConnection,.xml" data-toggle="tooltip" data-placement="right" class="form-control" style="width: 100%">
3448+ </div>
3449+ </td>
3450+ </tr>
3451+ </table>
3452+ </div>
3453+ <div id="tab2">
3454+ <form class="form-hortizontal" role="form">
3455+ <div class="row">
3456+ <div class="form-group">
3457+ <label class="col-md-2 control-label top-padding text-right text-nowrap col-input offset-width" for="selectProtocol">Protocol:</label>
3458+ <div class="col-md-3">
3459+ <select class="form-control" id="selectProtocol" @*data-bind="value: frameFormat"*@>
3460+ </select>
3461+ </div>
3462+ <label class="col-md-2 control-label top-padding text-right text-nowrap col-input" for="selectTransport">Transport:</label>
3463+ <div class="col-md-1">
3464+ <select class="form-control" id="selectTransport" style="width: 85px" @*data-bind="value: transport"*@>
3465+ <option value="TCP">TCP</option>
3466+ <option value="UDP">UDP</option>
3467+ <option value="SERIAL">Serial</option>
3468+ <option value="FILE">File</option>
3469+ </select>
3470+ </div>
3471+ </div>
3472+ <div class="form-group" style="padding-top: 0; margin-top: -15px;" @*data-bind="validationElement: unitID"*@>
3473+ <label class="col-md-2 control-label top-padding text-right text-nowrap col-input" for="inputUnitID">Access ID:</label>
3474+ <div class="col-md-2">
3475+ <input type="number" class="form-control" id="inputUnitID" @*data-bind="integer, textInput: unitID"*@>
3476+ </div>
3477+ </div>
3478+ </div>
3479+ <div class="row">
3480+ <div class="form-group">
3481+ <label class="col-md-2 control-label top-padding text-right text-nowrap col-input offset-width" for="inputConnectionString">Connection String:</label>
3482+ <div class="col-md-10">
3483+ <div class="input-group">
3484+ <input type="text" class="form-control" id="inputConnectionString" @*data-bind="textInput: connectionString"*@>
3485+ <span class="input-group-btn">
3486+ <button type="button" class="btn btn-default" onclick="showConnectionBuilderDialog()"><span class="glyphicon glyphicon-edit glyphicon-action-button"></span> Build...</button>
3487+ <button type="button" class="btn btn-primary" style="margin-left: 5px" onclick="connectToDevice()" title="Connect to Device" hub-dependent><span class="glyphicon glyphicon-transfer" style="vertical-align: text-top"></span> <strong id="connectButtonLabel">Connect</strong>&nbsp;</button>
3488+ </span>
3489+ </div>
3490+ </div>
3491+ </div>
3492+ </div>
3493+ <div class="panel panel-default" style="margin: 5px 1px">
3494+ <div class="panel-heading panel-heading-condensed" onclick="$('#connectionStatus').collapse('toggle')">
3495+ <label>Connection&nbsp;Status</label>
3496+ <button type="button" class="btn btn-xs btn-collapsable collapsed" data-toggle="collapse" data-target="#connectionStatus"></button>
3497+ </div>
3498+ <div class="panel-body collapse" style="padding: 2px" id="connectionStatus">
3499+ <pre id="connectionStatusWindow" class="small connection-status" style="height: 150px"></pre>
3500+ </div>
3501+ </div>
3502+ </form>
3503+ </div>
3504+ </div>
3505+ </div>
29353506 </div>
29363507 </td>
29373508 </tr>
@@ -2944,20 +3515,20 @@ · 6 unmodified lines
29443515 <input id="inputDeviceAcronym" type="text" class="form-control" style="width: 250px" data-bind="acronym: deviceAcronym, textInput: deviceAcronym, disable: readOnly" required>
29453516 <span class="glyphicon glyphicon-remove form-control-feedback" data-bind="visible: !fieldIsValid(deviceAcronym, true)"></span>
29463517 </div>
2947− @if (userIsEditor)
2948− {
2949− <div class="input-group save-button pull-right">
2950− <button type="button" class="btn btn-sm btn-primary pull-right" id="saveData" data-bind="disable: working() || validationErrors() > 0 || !phasorHubIsConnected() || (!editMode() && isEmpty(configFrame().ConnectionString))">
2951− <span class="glyphicon glyphicon-plus"></span>&nbsp;&nbsp;Save&nbsp;Device
2952− </button>
2953− </div>
2954− }
2955− else
2956− {
2957− <div class="label label-danger label-md no-access pull-right" style="">
2958− User "@GetCurrentUserID()" does not have rights to add or modify device data.
2959− </div>
2960− }
3518+ @if (userIsEditor)
3519+ {
3520+ <div class="input-group save-button pull-right">
3521+ <button type="button" class="btn btn-primary pull-right" id="saveData" data-bind="disable: working() || validationErrors() > 0 || !phasorHubIsConnected() || (!editMode() && isEmpty(configFrame().ConnectionString))">
3522+ <span class="glyphicon glyphicon-plus"></span>&nbsp;&nbsp;Save&nbsp;Device&nbsp;
3523+ </button>
3524+ </div>
3525+ }
3526+ else
3527+ {
3528+ <div class="label label-danger label-md no-access pull-right" style="">
3529+ User "@GetCurrentUserID()" does not have rights to add or modify device data.
3530+ </div>
3531+ }
29613532 </td>
29623533 </tr>
29633534 <tr>
@@ -2969,6 +3540,28 @@ · 5 unmodified lines
29693540 <input id="inputDeviceName" type="text" class="form-control" style="width: 250px" data-bind="textInput: deviceName, disable: readOnly" required>
29703541 <span class="glyphicon glyphicon-remove form-control-feedback" data-bind="visible: !fieldHasValue(deviceName, true)"></span>
29713542 </div>
3543+ @if (userIsEditor)
3544+ {
3545+ <div class="input-group save-button pull-right">
3546+ <button type="button" class="btn btn-default pull-right" onclick="showSaveConfigurationFileNameDialog()" id="saveConfigurationFileNameDialogButton" data-bind="enable: configFrame().Cells.length > 0">
3547+ <span class="glyphicon glyphicon-floppy-save glyphicon-action-button"></span>&nbsp;Export&nbsp;Config
3548+ </button>
3549+ </div>
3550+ <div class="well well-sm floating-form" id="saveConfigurationFileNameDialog" style="z-index: 1000">
3551+ <form class="form-inline" role="form">
3552+ <div class="form-group form-group-sm">
3553+ <button type="button" class="close" onclick="$('#saveConfigurationFileNameDialog').hide()" style="margin-top: -13px; margin-left: 8px">&times;</button>
3554+ <div class="input-group">
3555+ <input type="text" class="form-control" id="inputConfigurationFileName" placeholder="File name..." />
3556+ <span class="input-group-btn">
3557+ <button type="button" class="btn btn-default btn-sm" id="saveConfigurationFile" style="margin-right: -3px; font-weight: bold">Save</button>
3558+ </span>
3559+ </div>
3560+ </div>
3561+ </form>
3562+ </div>
3563+ <a href="#" id="saveConfigurationFileLink" target="_blank" style="display: none"></a>
3564+ }
29723565 </td>
29733566 </tr>
29743567 <tr>
@@ -2993,7 +3586,15 @@ · 18 unmodified lines
29933586 </td>
29943587 </tr>
29953588 </table>
2996−
3589+ @if (userIsEditor)
3590+ {
3591+ <div class="input-group save-button pull-right">
3592+ <button type="button" class="btn btn-default pull-right" onclick="showLoadConfigurationFileNameDialog()">
3593+ <span class="glyphicon glyphicon-import glyphicon-action-button"></span>&nbsp;Import&nbsp;Config
3594+ </button>
3595+ </div>
3596+ <input id="loadConfigurationFileName" type="file" accept="text/json" style="position: fixed; top: -100em" onchange="loadConfigurationFile(event)">
3597+ }
29973598 </td>
29983599 </tr>
29993600 <tr>
@@ -3043,65 +3644,65 @@ · 43 unmodified lines
30433644 <h3>Voltages</h3>
30443645 <table class="table table-condensed table-striped table-hover">
30453646 <thead>
3046− <tr>
3047− <!-- ko if: configFrame().Cells.length > 1 -->
3048− <th class="text-left">Device&nbsp;Acronym</th>
3049− <!-- /ko -->
3050− <th class="text-left">Phasor&nbsp;Label</th>
3051− <th class="text-center">Phase</th>
3052− <th class="text-left">Nominal&nbsp;Voltage</th>
3053− </tr>
3647+ <tr>
3648+ <!-- ko if: configFrame().Cells.length > 1 -->
3649+ <th class="text-left">Device&nbsp;Acronym</th>
3650+ <!-- /ko -->
3651+ <th class="text-left">Phasor&nbsp;Label</th>
3652+ <th class="text-center">Phase</th>
3653+ <th class="text-left">Nominal&nbsp;Voltage</th>
3654+ </tr>
30543655 </thead>
30553656 <tbody data-bind="foreach: { data: voltages }">
3056− <!-- ko if: $index() > 0 && FirstVoltage && $parent.configFrame().Cells.length > 1 -->
3057− <tr id="voltageRow" class="separator-top">
3058− <td colspan="4" style="padding: 2px"></td>
3059− </tr>
3060− <tr id="voltageRow" class="separator-bottom">
3061− <td colspan="4" style="padding: 2px"></td>
3062− </tr>
3657+ <!-- ko if: $index() > 0 && FirstVoltage && $parent.configFrame().Cells.length > 1 -->
3658+ <tr id="voltageRow" class="separator-top">
3659+ <td colspan="4" style="padding: 2px"></td>
3660+ </tr>
3661+ <tr id="voltageRow" class="separator-bottom">
3662+ <td colspan="4" style="padding: 2px"></td>
3663+ </tr>
3664+ <!-- /ko -->
3665+ <tr id="voltageRow" style="visibility: hidden">
3666+ <!-- ko if: !FirstVoltage && $parent.configFrame().Cells.length > 1 -->
3667+ <td width="190px" class="text-left" style="padding-right: 20px">
3668+ <div class="input-group"></div>
3669+ </td>
30633670 <!-- /ko -->
3064− <tr id="voltageRow" style="visibility: hidden">
3065− <!-- ko if: !FirstVoltage && $parent.configFrame().Cells.length > 1 -->
3066− <td width="190px" class="text-left" style="padding-right: 20px">
3067− <div class="input-group"></div>
3068− </td>
3069− <!-- /ko -->
3070− <!-- ko if: FirstVoltage && $parent.configFrame().Cells.length > 1 -->
3071− <td width="190px" class="text-left" style="padding-right: 20px">
3072− <span class="org-label" data-bind="visible: OriginalLabel != Label()">&nbsp;</span>
3073− <div class="input-group" data-bind="css: { 'has-error': !fieldIsValid(Device.IDLabel, true), 'has-feedback': !fieldIsValid(Device.IDLabel, true) }">
3074− <input type="text" class="form-control" style="width: 180px" data-bind="acronym: Device.IDLabel, textInput: Device.IDLabel, disable: $parent.readOnly() || !$data.Enabled()" required>
3075− <span class="glyphicon glyphicon-remove form-control-feedback" data-bind="visible: !fieldIsValid(Device.IDLabel, true)"></span>
3076− </div>
3077− </td>
3078− <!-- /ko -->
3079− <td width="210px" class="text-left" style="position: sticky">
3080− <span class="org-label" data-bind="text: OriginalLabel, visible: OriginalLabel != Label()"></span>
3081− <div class="checkbox" style="margin-top: 0; margin-bottom: 0">
3082− <input type="checkbox" style="margin-top: 10px" data-bind="checked: Enabled">
3083− </div>
3084− <div class="input-group" data-bind="css: { 'has-error': !fieldIsValid(Label, true), 'has-feedback': !fieldIsValid(Label, true), 'error-margin': !fieldIsValid(Label, true) }">
3085− <input type="text" class="form-control" style="width: 200px" data-bind="acronym: Label, textInput: Label, disable: $parent.readOnly() || !$data.Enabled()" required>
3086− <span class="glyphicon glyphicon-remove form-control-feedback" style="z-index: 5" data-bind="visible: !fieldIsValid(Label, true)"></span>
3087− </div>
3088− <span class="tag-name-label">Tag name:</span><span class="tag-name" data-bind="text: TagName"></span>
3089− </td>
3090− <td width="55px" class="text-center">
3091− <span class="org-label" data-bind="visible: OriginalLabel != Label()">&nbsp;</span>
3092− <div class="input-group" data-bind="css: { 'has-error': !fieldIsValid(Phase, true), 'has-feedback': !fieldIsValid(Phase, true), 'error-margin': !fieldIsValid(Phase, true) }">
3093− <input type="text" class="form-control" maxlength="1" style="width: 37px; margin-left: 3px" onclick="this.select()" data-bind="upperCase: Phase, textInput: Phase, style: { width: Phase.isValid() ? '37px' : '70px' }, selectOnError: Phase, enable: Enabled" placeholder="Ø" required>
3094− <span class="glyphicon glyphicon-remove form-control-feedback" style="z-index: 5" data-bind="visible: !fieldIsValid(Phase, true)"></span>
3095− </div>
3096− </td>
3097− <td class="text-left">
3098− <span class="org-label" data-bind="visible: OriginalLabel != Label()">&nbsp;</span>
3099− <div class="input-group" data-bind="css: { 'has-error': !fieldIsValid(NominalVoltage, true), 'has-feedback': !fieldIsValid(NominalVoltage, true), 'error-margin':! fieldIsValid(NominalVoltage, true) }">
3100− <input type="text" class="form-control" style="width: 70px; margin-left: 10px" data-bind="integer: NominalVoltage, textInput: NominalVoltage, enable: Enabled" required>&nbsp;<span class="table-cell-text">kV</span>
3101− <span class="glyphicon glyphicon-remove form-control-feedback" style="z-index: 5; width: 65px" data-bind="visible: !fieldIsValid(NominalVoltage, true)"></span>
3102− </div>
3103− </td>
3104− </tr>
3671+ <!-- ko if: FirstVoltage && $parent.configFrame().Cells.length > 1 -->
3672+ <td width="190px" class="text-left" style="padding-right: 20px">
3673+ <span class="org-label" data-bind="visible: OriginalLabel != Label()">&nbsp;</span>
3674+ <div class="input-group" data-bind="css: { 'has-error': !fieldIsValid(Device.IDLabel, true), 'has-feedback': !fieldIsValid(Device.IDLabel, true) }">
3675+ <input type="text" class="form-control" style="width: 180px" data-bind="acronym: Device.IDLabel, textInput: Device.IDLabel, disable: $parent.readOnly() || !$data.Enabled()" required>
3676+ <span class="glyphicon glyphicon-remove form-control-feedback" data-bind="visible: !fieldIsValid(Device.IDLabel, true)"></span>
3677+ </div>
3678+ </td>
3679+ <!-- /ko -->
3680+ <td width="210px" class="text-left" style="position: sticky">
3681+ <span class="org-label" data-bind="text: OriginalLabel, visible: OriginalLabel != Label()"></span>
3682+ <div class="checkbox" style="margin-top: 0; margin-bottom: 0">
3683+ <input type="checkbox" style="margin-top: 10px" data-bind="checked: Enabled">
3684+ </div>
3685+ <div class="input-group" data-bind="css: { 'has-error': !fieldIsValid(Label, true), 'has-feedback': !fieldIsValid(Label, true), 'error-margin': !fieldIsValid(Label, true) }">
3686+ <input type="text" class="form-control" style="width: 200px" data-bind="acronym: Label, textInput: Label, disable: $parent.readOnly() || !$data.Enabled()" required>
3687+ <span class="glyphicon glyphicon-remove form-control-feedback" style="z-index: 5" data-bind="visible: !fieldIsValid(Label, true)"></span>
3688+ </div>
3689+ <span class="tag-name-label">Tag name:</span><span class="tag-name" data-bind="text: TagName"></span>
3690+ </td>
3691+ <td width="55px" class="text-center">
3692+ <span class="org-label" data-bind="visible: OriginalLabel != Label()">&nbsp;</span>
3693+ <div class="input-group" data-bind="css: { 'has-error': !fieldIsValid(Phase, true), 'has-feedback': !fieldIsValid(Phase, true), 'error-margin': !fieldIsValid(Phase, true) }">
3694+ <input type="text" class="form-control" maxlength="1" style="width: 37px; margin-left: 3px" onclick="this.select()" data-bind="upperCase: Phase, textInput: Phase, style: { width: Phase.isValid() ? '37px' : '70px' }, selectOnError: Phase, enable: Enabled" placeholder="Ø" required>
3695+ <span class="glyphicon glyphicon-remove form-control-feedback" style="z-index: 5" data-bind="visible: !fieldIsValid(Phase, true)"></span>
3696+ </div>
3697+ </td>
3698+ <td class="text-left">
3699+ <span class="org-label" data-bind="visible: OriginalLabel != Label()">&nbsp;</span>
3700+ <div class="input-group" data-bind="css: { 'has-error': !fieldIsValid(NominalVoltage, true), 'has-feedback': !fieldIsValid(NominalVoltage, true), 'error-margin':! fieldIsValid(NominalVoltage, true) }">
3701+ <input type="text" class="form-control" style="width: 70px; margin-left: 10px" data-bind="integer: NominalVoltage, textInput: NominalVoltage, enable: Enabled" required>&nbsp;<span class="table-cell-text">kV</span>
3702+ <span class="glyphicon glyphicon-remove form-control-feedback" style="z-index: 5; width: 65px" data-bind="visible: !fieldIsValid(NominalVoltage, true)"></span>
3703+ </div>
3704+ </td>
3705+ </tr>
31053706 </tbody>
31063707 </table>
31073708 </div>
@@ -3111,67 +3712,193 @@ · 3 unmodified lines
31113712 <h3>Currents</h3>
31123713 <table class="table table-condensed table-striped table-hover">
31133714 <thead>
3114− <tr>
3115− <!-- ko if: configFrame().Cells.length > 1 -->
3116− <th class="text-left">Device&nbsp;Acronym</th>
3117− <!-- /ko -->
3118− <th class="text-left">Phasor&nbsp;Label</th>
3119− <th class="text-center">Phase</th>
3120− <th class="text-left">Associated&nbsp;Voltage</th>
3121− </tr>
3715+ <tr>
3716+ <!-- ko if: configFrame().Cells.length > 1 -->
3717+ <th class="text-left">Device&nbsp;Acronym</th>
3718+ <!-- /ko -->
3719+ <th class="text-left">Phasor&nbsp;Label</th>
3720+ <th class="text-center">Phase</th>
3721+ <th class="text-left">Associated&nbsp;Voltage</th>
3722+ </tr>
31223723 </thead>
31233724 <tbody data-bind="foreach: { data: currents }">
3124− <!-- ko if: $index() > 0 && FirstCurrent && $parent.configFrame().Cells.length > 1 -->
3125− <tr id="currentRow" class="separator-top">
3126− <td colspan="4" style="padding: 2px"></td>
3127− </tr>
3128− <tr id="currentRow" class="separator-bottom">
3129− <td colspan="4" style="padding: 2px"></td>
3130− </tr>
3725+ <!-- ko if: $index() > 0 && FirstCurrent && $parent.configFrame().Cells.length > 1 -->
3726+ <tr id="currentRow" class="separator-top">
3727+ <td colspan="4" style="padding: 2px"></td>
3728+ </tr>
3729+ <tr id="currentRow" class="separator-bottom">
3730+ <td colspan="4" style="padding: 2px"></td>
3731+ </tr>
3732+ <!-- /ko -->
3733+ <tr id="currentRow" style="visibility: hidden">
3734+ <!-- ko if: !FirstCurrent && $parent.configFrame().Cells.length > 1 -->
3735+ <td width="190px" class="text-left" style="padding-right: 20px">
3736+ <div class="input-group"></div>
3737+ </td>
31313738 <!-- /ko -->
3132− <tr id="currentRow" style="visibility: hidden">
3133− <!-- ko if: !FirstCurrent && $parent.configFrame().Cells.length > 1 -->
3134− <td width="190px" class="text-left" style="padding-right: 20px">
3135− <div class="input-group"></div>
3136− </td>
3137− <!-- /ko -->
3138− <!-- ko if: FirstCurrent && $parent.configFrame().Cells.length > 1 -->
3139− <td width="190px" class="text-left" style="padding-right: 20px">
3140− <span class="org-label" data-bind="visible: OriginalLabel != Label()">&nbsp;</span>
3141− <div class="input-group" data-bind="css: { 'has-error': !fieldIsValid(Device.IDLabel, true), 'has-feedback': !fieldIsValid(Device.IDLabel, true) }">
3142− <input type="text" class="form-control" style="width: 180px" data-bind="acronym: Device.IDLabel, textInput: Device.IDLabel, disable: $parent.readOnly() || !$data.Enabled()" required>
3143− <span class="glyphicon glyphicon-remove form-control-feedback" data-bind="visible: !fieldIsValid(Device.IDLabel, true)"></span>
3144− </div>
3145− </td>
3146− <!-- /ko -->
3147− <td width="210px" class="text-left" style="position: sticky">
3148− <span class="org-label" data-bind="text: OriginalLabel, visible: OriginalLabel != Label()"></span>
3149− <div class="checkbox" style="margin-top: 0; margin-bottom: 0">
3150− <input type="checkbox" style="margin-top: 10px" data-bind="checked: Enabled">
3151− </div>
3152− <div class="input-group" data-bind="css: { 'has-error': !fieldIsValid(Label, true), 'has-feedback': !fieldIsValid(Label, true), 'error-margin': !fieldIsValid(Label, true) }">
3153− <input type="text" class="form-control" style="width: 200px" data-bind="acronym: Label, textInput: Label, disable: $parent.readOnly() || !$data.Enabled()" required>
3154− <span class="glyphicon glyphicon-remove form-control-feedback" style="z-index: 5" data-bind="visible: !fieldIsValid(Label, true)"></span>
3155− </div>
3156− <span class="tag-name-label">Tag name:</span><span class="tag-name" data-bind="text: TagName"></span>
3157− </td>
3158− <td width="55px" class="text-center">
3159− <span class="org-label" data-bind="visible: OriginalLabel != Label()">&nbsp;</span>
3160− <div class="input-group" data-bind="css: { 'has-error': !fieldIsValid(Phase, true), 'has-feedback': !fieldIsValid(Phase, true), 'error-margin': !fieldIsValid(Phase, true)}">
3161− <input type="text" class="form-control" maxlength="1" style="width: 37px; margin-left: 3px" onclick="this.select()" data-bind="upperCase: Phase, textInput: Phase, style: { width: Phase.isValid() ? '37px' : '70px' }, selectOnError: Phase, enable: Enabled" placeholder="Ø" required>
3162− <span class="glyphicon glyphicon-remove form-control-feedback" style="z-index: 5" data-bind="visible: !fieldIsValid(Phase, true)"></span>
3163− </div>
3164− </td>
3165− <td class="text-left">
3166− <span class="org-label" data-bind="visible: OriginalLabel != Label()">&nbsp;</span>
3167− <select class="form-control" style="width: 90%" data-bind="options: DeviceVoltages, optionsCaption: 'None', value: AssociatedVoltage, optionsText: associatedVoltageLabel, optionsValue: 'ID', valueAllowUnset: true, enable: Enabled, select2: { templateSelection: formatAssociatedVoltageLabel, templateResult: formatAssociatedVoltageLabel, minimumResultsForSearch: -1, theme: 'bootstrap' }"></select>
3168− </td>
3169− </tr>
3739+ <!-- ko if: FirstCurrent && $parent.configFrame().Cells.length > 1 -->
3740+ <td width="190px" class="text-left" style="padding-right: 20px">
3741+ <span class="org-label" data-bind="visible: OriginalLabel != Label()">&nbsp;</span>
3742+ <div class="input-group" data-bind="css: { 'has-error': !fieldIsValid(Device.IDLabel, true), 'has-feedback': !fieldIsValid(Device.IDLabel, true) }">
3743+ <input type="text" class="form-control" style="width: 180px" data-bind="acronym: Device.IDLabel, textInput: Device.IDLabel, disable: $parent.readOnly() || !$data.Enabled()" required>
3744+ <span class="glyphicon glyphicon-remove form-control-feedback" data-bind="visible: !fieldIsValid(Device.IDLabel, true)"></span>
3745+ </div>
3746+ </td>
3747+ <!-- /ko -->
3748+ <td width="210px" class="text-left" style="position: sticky">
3749+ <span class="org-label" data-bind="text: OriginalLabel, visible: OriginalLabel != Label()"></span>
3750+ <div class="checkbox" style="margin-top: 0; margin-bottom: 0">
3751+ <input type="checkbox" style="margin-top: 10px" data-bind="checked: Enabled">
3752+ </div>
3753+ <div class="input-group" data-bind="css: { 'has-error': !fieldIsValid(Label, true), 'has-feedback': !fieldIsValid(Label, true), 'error-margin': !fieldIsValid(Label, true) }">
3754+ <input type="text" class="form-control" style="width: 200px" data-bind="acronym: Label, textInput: Label, disable: $parent.readOnly() || !$data.Enabled()" required>
3755+ <span class="glyphicon glyphicon-remove form-control-feedback" style="z-index: 5" data-bind="visible: !fieldIsValid(Label, true)"></span>
3756+ </div>
3757+ <span class="tag-name-label">Tag name:</span><span class="tag-name" data-bind="text: TagName"></span>
3758+ </td>
3759+ <td width="55px" class="text-center">
3760+ <span class="org-label" data-bind="visible: OriginalLabel != Label()">&nbsp;</span>
3761+ <div class="input-group" data-bind="css: { 'has-error': !fieldIsValid(Phase, true), 'has-feedback': !fieldIsValid(Phase, true), 'error-margin': !fieldIsValid(Phase, true)}">
3762+ <input type="text" class="form-control" maxlength="1" style="width: 37px; margin-left: 3px" onclick="this.select()" data-bind="upperCase: Phase, textInput: Phase, style: { width: Phase.isValid() ? '37px' : '70px' }, selectOnError: Phase, enable: Enabled" placeholder="Ø" required>
3763+ <span class="glyphicon glyphicon-remove form-control-feedback" style="z-index: 5" data-bind="visible: !fieldIsValid(Phase, true)"></span>
3764+ </div>
3765+ </td>
3766+ <td class="text-left">
3767+ <span class="org-label" data-bind="visible: OriginalLabel != Label()">&nbsp;</span>
3768+ <select class="form-control" style="width: 85%" data-bind="options: DeviceVoltages, optionsCaption: 'None', value: AssociatedVoltage, optionsText: associatedVoltageLabel, optionsValue: 'ID', valueAllowUnset: true, enable: Enabled, select2: { templateSelection: formatAssociatedVoltageLabel, templateResult: formatAssociatedVoltageLabel, minimumResultsForSearch: -1, theme: 'bootstrap' }"></select>
3769+ </td>
3770+ </tr>
31703771 </tbody>
31713772 </table>
31723773 </div>
31733774 </div>
31743775 </div>
3776+@* JSON Sequences Display Panel *@
3777+<hr class="thick-spacer" @*data-bind="visible: sequences().length > 0"*@ />
3778+<div class="panel panel-default">
3779+ <div class="panel-heading panel-heading-condensed" onclick="$('#serializedConfiguration').collapse('toggle')">
3780+ <label>Serialized&nbsp;Configuration</label>
3781+ <button type="button" class="btn btn-xs btn-collapsable collapsed" style="top: 0; right: -10px" data-toggle="collapse" data-target="#serializedConfiguration"></button>
3782+ </div>
3783+ <div class="panel-body collapse" style="padding: 2px; margin-bottom: -10px" id="serializedConfiguration">
3784+ <pre data-bind="text: JSON.stringify(serializeConfiguration(), null, 2)"></pre>
3785+ </div>
3786+</div>
3787+@* TCP Connection String Builder *@
3788+<div id="tcpBuilderDialog" class="modal" role="dialog">
3789+ <div class="modal-dialog modal-md">
3790+ <div class="modal-content">
3791+ <div class="modal-header">
3792+ <button type="button" class="close" onclick="$('#tcpBuilderDialog').hide()">&times;</button>
3793+ <h4 class="modal-title">Define TCP Connection Parameters</h4>
3794+ </div>
3795+ <div class="modal-body">
3796+ <form role="form">
3797+ <div class="form-group" @*data-bind="validationElement: hostName"*@>
3798+ <label for="inputTcpHostName">Host Name or IP:</label>
3799+ <input type="text" class="form-control" id="inputTcpHostName" maxlength="200" size="200" @*data-bind="textInput: hostName"*@>
3800+ </div>
3801+ <div class="form-group" @*data-bind="validationElement: ipPort"*@>
3802+ <label for="inputTcpPort">Port:</label>
3803+ <input type="number" class="form-control" id="inputTcpPort" maxlength="5" size="5" @*data-bind="integer, textInput: ipPort"*@>
3804+ </div>
3805+ </form>
3806+ </div>
3807+ <div class="modal-footer">
3808+ <button type="submit" class="btn btn-primary" onclick="applyTcpConnectionSettings()">Save</button>
3809+ <button type="button" class="btn btn-default" onclick="$('#tcpBuilderDialog').hide()">Cancel</button>
3810+ </div>
3811+ </div>
3812+ </div>
3813+</div>
3814+@* UDP Connection String Builder *@
3815+<div id="udpBuilderDialog" class="modal" role="dialog">
3816+ <div class="modal-dialog modal-md">
3817+ <div class="modal-content">
3818+ <div class="modal-header">
3819+ <button type="button" class="close" onclick="$('#udpBuilderDialog').hide()">&times;</button>
3820+ <h4 class="modal-title">Define UDP Connection Parameters</h4>
3821+ </div>
3822+ <div class="modal-body">
3823+ <form role="form">
3824+ <div class="form-group" @*data-bind="validationElement: ipPort"*@>
3825+ <label for="inputUdpPort">Port:</label>
3826+ <input type="number" class="form-control" id="inputUdpPort" maxlength="5" size="5" @*data-bind="integer, textInput: ipPort"*@>
3827+ </div>
3828+ <div class="form-group" @*data-bind="validationElement: interface"*@>
3829+ <label for="inputUdpInterface">Interface IP:</label>
3830+ <input type="text" class="form-control" id="inputUdpInterface" maxlength="200" size="200" @*data-bind="textInput: interface"*@>
3831+ </div>
3832+ </form>
3833+ </div>
3834+ <div class="modal-footer">
3835+ <button type="submit" class="btn btn-primary" onclick="applyUdpConnectionSettings()">Save</button>
3836+ <button type="button" class="btn btn-default" onclick="$('#udpBuilderDialog').hide()">Cancel</button>
3837+ </div>
3838+ </div>
3839+ </div>
3840+</div>
3841+@* Serial Connection String Builder *@
3842+<div id="serialBuilderDialog" class="modal" role="dialog">
3843+ <div class="modal-dialog modal-md">
3844+ <div class="modal-content">
3845+ <div class="modal-header">
3846+ <button type="button" class="close" onclick="$('#serialBuilderDialog').hide()">&times;</button>
3847+ <h4 class="modal-title">Define Serial Connection Parameters</h4>
3848+ </div>
3849+ <div class="modal-body">
3850+ <form role="form">
3851+ <div class="form-group" @*data-bind="validationElement: comPort"*@>
3852+ <label for="inputSerialPort">Port:</label>
3853+ <div class="input-group">
3854+ <span class="input-group-addon">COM</span>
3855+ <input type="number" class="form-control" id="inputSerialPort" maxlength="5" size="5" @*data-bind="integer, textInput: comPort"*@>
3856+ </div>
3857+ </div>
3858+ <div class="form-group">
3859+ <label for="selectSerialBaudRate">Baud Rate:</label>
3860+ <select class="form-control" id="selectSerialBaudRate">
3861+ <option value="115200">115200</option>
3862+ <option value="57600" selected>57600</option>
3863+ <option value="38400">38400</option>
3864+ <option value="19200">19200</option>
3865+ <option value="9600">9600</option>
3866+ <option value="4800">4800</option>
3867+ <option value="2400">2400</option>
3868+ <option value="1200">1200</option>
3869+ </select>
3870+ </div>
3871+ <div class="form-group">
3872+ <label for="selectSerialParity">Parity:</label>
3873+ <select class="form-control" id="selectSerialParity">
3874+ <option value="None" selected>None</option>
3875+ <option value="Odd">Odd</option>
3876+ <option value="Even">Even</option>
3877+ <option value="Mark">Mark</option>
3878+ <option value="Space">Space</option>
3879+ </select>
3880+ </div>
3881+ <div class="form-group">
3882+ <label for="selectSerialStopBits">Stop Bits:</label>
3883+ <select class="form-control" id="selectSerialStopBits">
3884+ <option value="One" selected>One</option>
3885+ <option value="Two">Two</option>
3886+ <option value="OnePointFive">OnePointFive</option>
3887+ </select>
3888+ </div>
3889+ <div class="form-group" @*data-bind="validationElement: dataBits"*@>
3890+ <label for="inputSerialDataBits">Data Bits:</label>
3891+ <input type="number" class="form-control" id="inputSerialDataBits" maxlength="200" size="200" @*data-bind="integer, textInput: dataBits"*@>
3892+ </div>
3893+ </form>
3894+ </div>
3895+ <div class="modal-footer">
3896+ <button type="submit" class="btn btn-primary" onclick="applySerialConnectionSettings()">Save</button>
3897+ <button type="button" class="btn btn-default" onclick="$('#serialBuilderDialog').hide()">Cancel</button>
3898+ </div>
3899+ </div>
3900+ </div>
3901+</div>
31753902 @{
31763903 ViewBag.StyleSheetsSection = RenderSection("StyleSheets").ToString();
31773904 ViewBag.ScriptsSection = RenderSection("Scripts").ToString();

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t