| @@ -72,6 +72,7 @@ · 71 unmodified lines |
| 72 | 72 | bool userIsEditor = UserIsEditor(); |
| 73 | 73 | } |
| 74 | 74 | @section StyleSheets { |
| 75 | + <link href="@Resources.Root/Shared/Content/jquery-ui.css" rel="stylesheet"> |
| 75 | 76 | <link href="@Resources.Root/Shared/Content/select2.css" rel="stylesheet"> |
| 76 | 77 | <link href="@Resources.Root/Shared/Content/select2-bootstrap.css" rel="stylesheet"> |
| 77 | 78 | <style> |
| @@ -79,6 +80,64 @@ · 1 unmodified line |
| 79 | 80 | html, body { |
| 80 | 81 | height: 100%; |
| 81 | 82 | } |
| 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 | + } |
| 82 | 141 | |
| 83 | 142 | span.table-cell-text { |
| 84 | 143 | display: table-cell; |
| @@ -122,6 +181,7 @@ · 37 unmodified lines |
| 122 | 181 | |
| 123 | 182 | div.save-button { |
| 124 | 183 | margin-top: -33px; |
| 184 | + margin-right: 2px; |
| 125 | 185 | z-index: 10; |
| 126 | 186 | } |
| 127 | 187 | |
| @@ -133,6 +193,11 @@ · 5 unmodified lines |
| 133 | 193 | position: relative; |
| 134 | 194 | } |
| 135 | 195 | |
| 196 | + div.panel-heading-condensed { |
| 197 | + height: 30px; |
| 198 | + padding-top: 5px |
| 199 | + } |
| 200 | + |
| 136 | 201 | tr.separator-top { |
| 137 | 202 | visibility: hidden; |
| 138 | 203 | border-bottom-width: 4px; |
| @@ -158,6 +223,13 @@ · 19 unmodified lines |
| 158 | 223 | vertical-align: middle; |
| 159 | 224 | } |
| 160 | 225 | |
| 226 | + label.col-input { |
| 227 | + padding-right: 0; |
| 228 | + margin-right: -10px; |
| 229 | + margin-left: 10px; |
| 230 | + white-space: nowrap; |
| 231 | + } |
| 232 | + |
| 161 | 233 | tr.top-border { |
| 162 | 234 | border-top-width: 2px; |
| 163 | 235 | border-top-style: solid; |
| @@ -167,11 +239,86 @@ · 3 unmodified lines |
| 167 | 239 | padding-bottom: 10px; |
| 168 | 240 | } |
| 169 | 241 | |
| 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 | + |
| 170 | 313 | @@media screen { |
| 171 | 314 | .page-logo { |
| 172 | 315 | margin-bottom: 2px; |
| 173 | 316 | margin-top: -38px; |
| 174 | 317 | } |
| 318 | + |
| 319 | + .offset-width { |
| 320 | + width: 10px |
| 321 | + } |
| 175 | 322 | } |
| 176 | 323 | |
| 177 | 324 | @@media screen and (min-width: 638px) { |
| @@ -179,6 +326,10 @@ · 1 unmodified line |
| 179 | 326 | margin-bottom: 2px; |
| 180 | 327 | margin-top: -12px; |
| 181 | 328 | } |
| 329 | + |
| 330 | + .offset-width { |
| 331 | + width: 10px |
| 332 | + } |
| 182 | 333 | } |
| 183 | 334 | |
| 184 | 335 | @@media screen and (min-width: 868px) { |
| @@ -186,6 +337,10 @@ · 1 unmodified line |
| 186 | 337 | margin-bottom: 2px; |
| 187 | 338 | margin-top: -12px; |
| 188 | 339 | } |
| 340 | + |
| 341 | + .offset-width { |
| 342 | + width: 10px |
| 343 | + } |
| 189 | 344 | } |
| 190 | 345 | |
| 191 | 346 | @@media screen and (min-width: 992px) { |
| @@ -193,6 +348,10 @@ · 1 unmodified line |
| 193 | 348 | margin-bottom: -25px; |
| 194 | 349 | margin-top: -4px; |
| 195 | 350 | } |
| 351 | + |
| 352 | + .offset-width { |
| 353 | + width: 150px |
| 354 | + } |
| 196 | 355 | } |
| 197 | 356 | </style> |
| 198 | 357 | } |
| @@ -481,7 +640,7 @@ · 282 unmodified lines |
| 481 | 640 | } |
| 482 | 641 | else { |
| 483 | 642 | result(tagName); |
| 484 | | − } |
| 643 | + } |
| 485 | 644 | }); |
| 486 | 645 | }); |
| 487 | 646 | |
| @@ -864,6 +1023,36 @@ · 376 unmodified lines |
| 864 | 1023 | firstCurrent = false; |
| 865 | 1024 | } |
| 866 | 1025 | } |
| 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 | + }; |
| 867 | 1056 | } |
| 868 | 1057 | |
| 869 | 1058 | // Make sure table is refreshed |
| @@ -1127,6 +1316,75 @@ · 257 unmodified lines |
| 1127 | 1316 | return current; |
| 1128 | 1317 | }; |
| 1129 | 1318 | |
| 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 | + |
| 1130 | 1388 | // Validations |
| 1131 | 1389 | self.deviceAcronym.extend({ |
| 1132 | 1390 | required: true, |
| @@ -2681,11 +2939,18 @@ · 1548 unmodified lines |
| 2681 | 2939 | }); |
| 2682 | 2940 | } |
| 2683 | 2941 | |
| 2942 | + function getActiveTab() { |
| 2943 | + return $("#tabs").tabs("option", "active"); |
| 2944 | + } |
| 2945 | + |
| 2684 | 2946 | function resizePageElements() { |
| 2685 | 2947 | 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); |
| 2689 | 2954 | } |
| 2690 | 2955 | |
| 2691 | 2956 | $("#contentWell").height(calculateRemainingBodyHeight() - $("#headerArea").outerHeight(true)); |
| @@ -2707,9 +2972,104 @@ · 15 unmodified lines |
| 2707 | 2972 | else |
| 2708 | 2973 | showInfoMessage(message); |
| 2709 | 2974 | } |
| 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 | + } |
| 2710 | 3056 | |
| 2711 | 3057 | // Page initialization function |
| 2712 | 3058 | $(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 | + |
| 2713 | 3073 | // Connect to phasor hub |
| 2714 | 3074 | phasorHub = $.connection.phasorHub.server; |
| 2715 | 3075 | phasorHubClient = $.connection.phasorHub.client; |
| @@ -2795,6 +3155,82 @@ · 79 unmodified lines |
| 2795 | 3155 | ko.watch(viewModel.devices, { depth: -1 }, function (parents, child, item) { |
| 2796 | 3156 | viewModel.isDirty(true); |
| 2797 | 3157 | }); |
| 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 | + }); |
| 2798 | 3234 | |
| 2799 | 3235 | // Let view model know about hub connectivity changes |
| 2800 | 3236 | $(window).on("hubConnected", function () { |
| @@ -2837,6 +3273,20 @@ · 36 unmodified lines |
| 2837 | 3273 | }); |
| 2838 | 3274 | } |
| 2839 | 3275 | |
| 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 | + |
| 2840 | 3290 | const templateTypeSelect = $("#templateType"); |
| 2841 | 3291 | |
| 2842 | 3292 | // Load available calculation templates, if not already loaded |
| @@ -2906,6 +3356,43 @@ · 63 unmodified lines |
| 2906 | 3356 | |
| 2907 | 3357 | $("#inputConfigFile").tooltip("hide"); |
| 2908 | 3358 | }); |
| 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 | + }); |
| 2909 | 3396 | |
| 2910 | 3397 | resizePageElements(); |
| 2911 | 3398 | |
| @@ -2926,12 +3413,96 @@ · 14 unmodified lines |
| 2926 | 3413 | <form> |
| 2927 | 3414 | <table style="width: 100%"> |
| 2928 | 3415 | <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 PMUConnection 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 Settings |
| 3422 | + </label> |
| 3423 | + <div class="pull-right"> |
| 3424 | + <span id="notConnected">Not Connected <span class="glyphicon glyphicon-ban-circle" style="color: #FF003B;"></span></span> |
| 3425 | + <span id="attemptingConnection" style="display: none">Attempting Connection <span class="glyphicon glyphicon-refresh glyphicon-spin" style="color: yellow"></span></span> |
| 3426 | + <span id="connected" style="display: none">Connected <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 PMUConnection 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> </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 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> |
| 2935 | 3506 | </div> |
| 2936 | 3507 | </td> |
| 2937 | 3508 | </tr> |
| @@ -2944,20 +3515,20 @@ · 6 unmodified lines |
| 2944 | 3515 | <input id="inputDeviceAcronym" type="text" class="form-control" style="width: 250px" data-bind="acronym: deviceAcronym, textInput: deviceAcronym, disable: readOnly" required> |
| 2945 | 3516 | <span class="glyphicon glyphicon-remove form-control-feedback" data-bind="visible: !fieldIsValid(deviceAcronym, true)"></span> |
| 2946 | 3517 | </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> Save 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> Save Device |
| 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 | + } |
| 2961 | 3532 | </td> |
| 2962 | 3533 | </tr> |
| 2963 | 3534 | <tr> |
| @@ -2969,6 +3540,28 @@ · 5 unmodified lines |
| 2969 | 3540 | <input id="inputDeviceName" type="text" class="form-control" style="width: 250px" data-bind="textInput: deviceName, disable: readOnly" required> |
| 2970 | 3541 | <span class="glyphicon glyphicon-remove form-control-feedback" data-bind="visible: !fieldHasValue(deviceName, true)"></span> |
| 2971 | 3542 | </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> Export 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">×</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 | + } |
| 2972 | 3565 | </td> |
| 2973 | 3566 | </tr> |
| 2974 | 3567 | <tr> |
| @@ -2993,7 +3586,15 @@ · 18 unmodified lines |
| 2993 | 3586 | </td> |
| 2994 | 3587 | </tr> |
| 2995 | 3588 | </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> Import Config |
| 3594 | + </button> |
| 3595 | + </div> |
| 3596 | + <input id="loadConfigurationFileName" type="file" accept="text/json" style="position: fixed; top: -100em" onchange="loadConfigurationFile(event)"> |
| 3597 | + } |
| 2997 | 3598 | </td> |
| 2998 | 3599 | </tr> |
| 2999 | 3600 | <tr> |
| @@ -3043,65 +3644,65 @@ · 43 unmodified lines |
| 3043 | 3644 | <h3>Voltages</h3> |
| 3044 | 3645 | <table class="table table-condensed table-striped table-hover"> |
| 3045 | 3646 | <thead> |
| 3046 | | − <tr> |
| 3047 | | − <!-- ko if: configFrame().Cells.length > 1 --> |
| 3048 | | − <th class="text-left">Device Acronym</th> |
| 3049 | | − <!-- /ko --> |
| 3050 | | − <th class="text-left">Phasor Label</th> |
| 3051 | | − <th class="text-center">Phase</th> |
| 3052 | | − <th class="text-left">Nominal Voltage</th> |
| 3053 | | − </tr> |
| 3647 | + <tr> |
| 3648 | + <!-- ko if: configFrame().Cells.length > 1 --> |
| 3649 | + <th class="text-left">Device Acronym</th> |
| 3650 | + <!-- /ko --> |
| 3651 | + <th class="text-left">Phasor Label</th> |
| 3652 | + <th class="text-center">Phase</th> |
| 3653 | + <th class="text-left">Nominal Voltage</th> |
| 3654 | + </tr> |
| 3054 | 3655 | </thead> |
| 3055 | 3656 | <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> |
| 3063 | 3670 | <!-- /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()"> </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()"> </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()"> </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> <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()"> </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()"> </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()"> </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> <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> |
| 3105 | 3706 | </tbody> |
| 3106 | 3707 | </table> |
| 3107 | 3708 | </div> |
| @@ -3111,67 +3712,193 @@ · 3 unmodified lines |
| 3111 | 3712 | <h3>Currents</h3> |
| 3112 | 3713 | <table class="table table-condensed table-striped table-hover"> |
| 3113 | 3714 | <thead> |
| 3114 | | − <tr> |
| 3115 | | − <!-- ko if: configFrame().Cells.length > 1 --> |
| 3116 | | − <th class="text-left">Device Acronym</th> |
| 3117 | | − <!-- /ko --> |
| 3118 | | − <th class="text-left">Phasor Label</th> |
| 3119 | | − <th class="text-center">Phase</th> |
| 3120 | | − <th class="text-left">Associated Voltage</th> |
| 3121 | | − </tr> |
| 3715 | + <tr> |
| 3716 | + <!-- ko if: configFrame().Cells.length > 1 --> |
| 3717 | + <th class="text-left">Device Acronym</th> |
| 3718 | + <!-- /ko --> |
| 3719 | + <th class="text-left">Phasor Label</th> |
| 3720 | + <th class="text-center">Phase</th> |
| 3721 | + <th class="text-left">Associated Voltage</th> |
| 3722 | + </tr> |
| 3122 | 3723 | </thead> |
| 3123 | 3724 | <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> |
| 3131 | 3738 | <!-- /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()"> </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()"> </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()"> </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()"> </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()"> </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()"> </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> |
| 3170 | 3771 | </tbody> |
| 3171 | 3772 | </table> |
| 3172 | 3773 | </div> |
| 3173 | 3774 | </div> |
| 3174 | 3775 | </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 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()">×</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()">×</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()">×</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> |
| 3175 | 3902 | @{ |
| 3176 | 3903 | ViewBag.StyleSheetsSection = RenderSection("StyleSheets").ToString(); |
| 3177 | 3904 | ViewBag.ScriptsSection = RenderSection("Scripts").ToString(); |