Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
8656adc
rename NodeStats to NodeDetails
nbeatty-gpa Apr 3, 2026
711f6ab
rename NodeStats component to NodeDetails
nbeatty-gpa Apr 3, 2026
19ab4a7
move openXDA stats into separate component
nbeatty-gpa Apr 3, 2026
6391bb7
rename NodeHealth to NodeConnections
nbeatty-gpa Apr 3, 2026
574c347
include openXDAHealth in solution
nbeatty-gpa Apr 3, 2026
1b02a3b
move health, connections, and console to tabs
nbeatty-gpa Apr 3, 2026
db5cafb
standardize layout
nbeatty-gpa Apr 3, 2026
df5fbc6
get console working
nbeatty-gpa Apr 3, 2026
fdbda1e
move openXDAHealth to NodeHealth
nbeatty-gpa Apr 6, 2026
4208598
add openMIC to Nodes tab
nbeatty-gpa Apr 6, 2026
80d3956
condense openMIC AppStatus
nbeatty-gpa Apr 6, 2026
0d6bc5f
fix Stats layout
nbeatty-gpa Apr 8, 2026
7b76dbe
improve console layout
nbeatty-gpa Apr 8, 2026
5e17532
Update Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCen…
nbeatty-gpa Apr 9, 2026
11ba1c9
Update Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCen…
nbeatty-gpa Apr 9, 2026
acd4657
Update Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCen…
nbeatty-gpa Apr 9, 2026
b5b41e6
Update Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCen…
nbeatty-gpa Apr 9, 2026
1ebf109
Update Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCen…
nbeatty-gpa Apr 9, 2026
34577e1
Update Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCen…
nbeatty-gpa Apr 9, 2026
f7d8d70
Update Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCen…
nbeatty-gpa Apr 9, 2026
5a8ec17
Update Source/Applications/SystemCenter/wwwroot/Scripts/TSX/SystemCen…
elwills Apr 9, 2026
17e4b06
correct comma to semicolon
nbeatty-gpa Apr 9, 2026
94a04a6
make application type nullabel
nbeatty-gpa Apr 9, 2026
d7022ed
remove duplicate application type definitions
nbeatty-gpa Apr 9, 2026
55c6234
correct header
nbeatty-gpa Apr 9, 2026
48c3709
move available tab setting logic into use effect
nbeatty-gpa Apr 9, 2026
06951fc
move hasError logic out of tooltip render
nbeatty-gpa Apr 9, 2026
7421b15
provide defaults for nullable values
nbeatty-gpa Apr 9, 2026
202a8bd
return cleanup in openMIC branch
nbeatty-gpa Apr 9, 2026
8dfbcbb
add key to child map
nbeatty-gpa Apr 9, 2026
1d5e0e4
memoize available tabs
nbeatty-gpa Apr 10, 2026
6dbce37
remove RemoteXDAConnections fieldset if there are no remote XDA conne…
nbeatty-gpa Apr 13, 2026
94ad6fe
remove scrolling from node details modal
nbeatty-gpa Apr 13, 2026
1825aad
add scrolling to node health fieldset
nbeatty-gpa Apr 13, 2026
a745272
set status according to openMIC status call success
nbeatty-gpa Apr 14, 2026
30882f1
remove unneeded using
nbeatty-gpa Apr 14, 2026
d9cd94f
clean up tab selection
nbeatty-gpa Apr 14, 2026
fbf8d3d
add margin to node details
nbeatty-gpa Apr 14, 2026
181afe5
fix guard against empty details
nbeatty-gpa Apr 17, 2026
30f6c81
move well div
nbeatty-gpa Apr 17, 2026
9202378
make disabled apphost buttons more clear
nbeatty-gpa Apr 20, 2026
d37eca4
improve scrolling and removal of empty status groups
nbeatty-gpa Apr 20, 2026
eb9349e
add pagination information to results message
nbeatty-gpa Apr 20, 2026
c358ea7
remove bullets from bad days tooltip
nbeatty-gpa Apr 20, 2026
2e2e1a5
simplify tab typing
nbeatty-gpa Apr 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
using GSF.Data.Model;
using GSF.Reflection;
using Newtonsoft.Json;
using openXDA.APIMiddleware;
using openXDA.APIAuthentication;
using openXDA.APIMiddleware;
using openXDA.Model;
using System;
using System.Collections.Generic;
Expand Down Expand Up @@ -88,6 +88,8 @@ public IHttpActionResult Get()
// Add MiMD
hosts.Add(GetMiMD());

hosts.Add(GetOpenMIC());

// Add XDA Nodes
using (AdoDataConnection connection = CreateDbConnection())
{
Expand Down Expand Up @@ -208,6 +210,28 @@ void ConfigureRequest(HttpRequestMessage request)
return ResponseMessage(responseMessage);
}

[Route("openMIC/Ping"), HttpGet]
public IHttpActionResult openMICPing()
{
using (AdoDataConnection connection = new AdoDataConnection("systemSettings"))
{
string url = new TableOperations<SystemCenter.Model.Setting>(connection).QueryRecordWhere($"Name = 'OpenMIC.Url'")?.Value ?? "";
string credential = new TableOperations<SystemCenter.Model.Setting>(connection).QueryRecordWhere($"Name = 'OpenMIC.Credential'")?.Value ?? "";
string password = new TableOperations<SystemCenter.Model.Setting>(connection).QueryRecordWhere($"Name = 'OpenMIC.Password'")?.Value ?? "";

//string token = GenerateAntiForgeryToken(application);
//return Get(httpClient, url, requestURI, credential, password, token);
APIQuery query = new APIQuery(credential, password, url);
void ConfigureRequest(HttpRequestMessage request)
{
request.Method = HttpMethod.Get;
}
HttpResponseMessage response = query.SendWebRequestAsync(ConfigureRequest, $"api/health").Result;
if (response.IsSuccessStatusCode) return Ok(1);
return ResponseMessage(response);
}
Comment thread
nbeatty-gpa marked this conversation as resolved.
}

[Route("MiMDConsole/Ping"), HttpGet]
public IHttpActionResult MiMDPing()
{
Expand Down Expand Up @@ -299,6 +323,14 @@ private string GetMiMDBaseURL()
}
}

private string GetOpenMICBaseURL()
{
using (AdoDataConnection connection = CreateDbConnection())
{
return connection.ExecuteScalar("", "SELECT Value FROM [SystemCenter.Setting] WHERE Name = {0}", "OpenMIC.Url");
}
}

private AdoDataConnection CreateDbConnection()
{
AdoDataConnection connection = new AdoDataConnection("systemSettings");
Expand Down Expand Up @@ -344,6 +376,21 @@ private AppHost GetMiMD()
};
}

private AppHost GetOpenMIC()
{
return new AppHost()
{
Name = "openMIC",
App = "openMIC",
PingURL = "./api/SystemCenter/AppHost/openMIC/Ping",
Properties = new AppProperty[]
{
new AppProperty() { Name= "Host", Value = GetOpenMICBaseURL() }
},
Image = "../Images/NodeTiles/openMIC.png"
};
}

private string DatabaseName
{
get
Expand Down
14 changes: 13 additions & 1 deletion Source/Applications/SystemCenter/SystemCenter.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,9 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Content Include="wwwroot\Images\GiantLogo.png" />
<Content Include="wwwroot\Images\NodeTiles\openMIC.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="wwwroot\Images\NodeTiles\XDA.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
Expand All @@ -504,6 +507,9 @@
<Content Include="wwwroot\Images\NodeTiles\XDAIcon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="wwwroot\Images\NodeTiles\openMICIcon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="wwwroot\Images\NodeTiles\SystemCenterIcon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
Expand Down Expand Up @@ -547,7 +553,12 @@
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\AdditionalFields\ByAdditionalField.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\AppHost\ConsoleWindow.tsx" />
<Content Include="wwwroot\Scripts\TSX\SystemCenter\LineSegment\ByLineSegment.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\AppHost\NodeStats.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\AppHost\NodeConnections.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\AppHost\NodeDetails.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\AppHost\NodeHealth.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\AppHost\StatusDetails.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\AppHost\StatusGroup.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\AppHost\StatusItem.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\AssetAttribute\FawgLineSegmentWizard\Common.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\AssetAttribute\Generation.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\AssetAttribute\StationAux.tsx" />
Expand All @@ -574,6 +585,7 @@
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\CommonComponents\Restrictions\EditionRestrictionTooltip.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\CommonComponents\Restrictions\RoleRestrictionTooltip.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\CommonComponents\ExternalDBUpdate.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\DeviceHealthReport\AppStatus.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\ExternalDB\ExternalDBTable.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\ExternalDB\ExternalDBTableFields.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\ExternalDB\ExternalDBTableForm.tsx" />
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,15 @@ import * as React from 'react';
import { Application } from '@gpa-gemstone/application-typings';
import { LoadingScreen, ServerErrorIcon, LayoutGrid } from '@gpa-gemstone/react-interactive';
import ApplicationCard, { IHost } from './ApplicationCard';
import ConsoleWindow from './ConsoleWindow';
import NodeStats from './NodeStats';
import NodeDetails from './NodeDetails';
import { useMediaQuery } from '@gpa-gemstone/helper-functions';


const AppHost: Application.Types.iByComponent = (props) => {
const [hosts, setHosts] = React.useState<IHost[]>([]);
const [status, setStatus] = React.useState<Application.Types.Status>('uninitiated');
const [console, setConsole] = React.useState<IHost | null>(null);
const [stats, setStats] = React.useState<IHost | null>(null);
const [details, setDetails] = React.useState<IHost | null>(null);

const shouldHaveTwoRowsHeight = useMediaQuery('(max-height: 1250px)');
const shouldBeSmall = useMediaQuery('(max-width: 1750px)');
Expand Down Expand Up @@ -68,23 +67,20 @@ const AppHost: Application.Types.iByComponent = (props) => {
<LayoutGrid RowsPerPage={shouldHaveTwoRowsHeight ? 2 : 3} ColMax={shouldHaveTwoColumns ? 2 : 3}>
{hosts.map((h) => <ApplicationCard {...h}
OpenConsole={() => setConsole(h)}
OpenStats={() => setStats(h)}
OpenDetails={() => { setDetails(h); setConsole(h) }}
key={h.PingURL}
IsSmall={shouldBeSmall}
/>)}
</LayoutGrid>
</div>
<ConsoleWindow
ApplicationName={console?.Name ?? ''}
Close={() => setConsole(null)}
ConsoleURL={console?.ConsoleURL}
/>
<NodeStats
ApplicationName={stats?.Name ?? ''}
Close={() => setStats(null)}
StatsURL={stats?.StatsURL}
ApplicationType={stats?.App ?? null}
Properties={stats?.Properties}
<NodeDetails
ApplicationName={details?.Name ?? ''}
StatsURL={details?.StatsURL ?? ''}
ApplicationType={details?.App ?? null}
Properties={details?.Properties ?? []}
SetDetails={setDetails}
SetConsole={setConsole}
ConsoleURL={console?.ConsoleURL ?? ''}
/>
Comment thread
nbeatty-gpa marked this conversation as resolved.
</div>)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import * as React from 'react';
import { useAppDispatch } from '../hooks';
import { SystemCenter as SC } from '../global'

export interface IHostProperties { Name: string, Value: string }

Expand All @@ -33,9 +34,9 @@ export interface IHost {
StatsURL?: string,
ConsoleURL: string,
Name: string,
App: 'XDA' | 'MiMD' | 'SystemCenter',
App: SC.ApplicationType,
OpenConsole: () => void,
OpenStats: () => void
OpenDetails: () => void
}

export interface IApplicationCard extends IHost {
Expand Down Expand Up @@ -98,13 +99,8 @@ const ApplicationCard = (props: IApplicationCard) => {
</div>
<div className="card-footer">
<div className="row">
<div className={`col align-self-start`}>
<button className={`btn btn-info`} onClick={() => props.OpenConsole()} disabled={status == "Loading" || status == "Unknown" }>Console</button>
</div>
<div className={`col align-self-end`}>
{props.App === 'MiMD' ? null :
<button className={`btn btn-info float-right`} onClick={() => props.OpenStats()} disabled={status == "Loading" || status == "Unknown"}>Status</button>
}
<div className={`col`}>
<button className={`btn btn-${status == "Loading" || status == "Unknown" ? 'secondary' : 'info' }`} onClick={() => props.OpenDetails()} disabled={status == "Loading" || status == "Unknown"}>Details</button>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
//******************************************************************************************************

import * as React from 'react';
import { Modal } from '@gpa-gemstone/react-interactive';

export interface IMessage { Message: string, Type: number }

export interface IProps {
Expand Down Expand Up @@ -103,7 +101,7 @@ const ConsoleWindow = (props: IProps) => {


React.useEffect(() => {
if (props.ConsoleURL != undefined && props.ConsoleURL.length > 0)
if (props.ConsoleURL != undefined && props.ConsoleURL.length > 0)
document.addEventListener("keydown", handleKeyPress, false);

return () => {
Expand All @@ -113,19 +111,19 @@ const ConsoleWindow = (props: IProps) => {

React.useEffect(() => { cmdRef.current = cmd; }, [cmd]);
React.useEffect(() => { lastCmdRef.current = lastCmd; }, [lastCmd]);

function handleKeyPress(event) {

if (event.keyCode == 38 && cmd.length > 0) // arrow down key
{
event.preventDefault();
setCMD(lastCmdRef.current);
setCMD(lastCmdRef.current);
}
else if (event.keyCode == 13) // enter key
{
event.preventDefault();
sendCmd(cmdRef.current);
setCMD('');
setCMD('');
}
}

Expand Down Expand Up @@ -179,31 +177,24 @@ const ConsoleWindow = (props: IProps) => {
}, [props.ConsoleURL]);

return (
<>
<Modal
Show={props.ConsoleURL != undefined && props.ConsoleURL.length > 0}
CallBack={(_conf, button) => { if (!button) { props.Close(); sessionIDRef.current = ''; } else { sendCmd(cmd); setCMD(''); } }}
ShowCancel={false} Size={'xlg'}
Title={'Console - ' + props.ApplicationName}
ShowX={true}
ConfirmText={'Send'}
>
<div className="well" style={{ height: innerHeight - 400, display: 'flex', flexDirection: 'column' }}>
<div className="row">
<div className="col-6">
<label className="small pull-left" >
{lastUpdate !== null ? <small><em>Last update {lastUpdate}</em></small> :
<div className="row h-100">
<div className="col h-100">
<div className="row">
<div className="col-6">
<label className="small pull-left" >
{lastUpdate !== null ? <small><em>Last update {lastUpdate}</em></small> :
<small><em>Updating...</em></small>}
</label>
</div>
<div className="col-6">
<label className="small pull-right" style={{ display: autoScroll ? 'none' : undefined }} >
<small><em>Scrolling paused during mouse interaction...</em></small>
</label>
</div>
</label>
</div>
<div className="col-6">
<label className="small pull-right" style={{ display: autoScroll ? 'none' : undefined }} >
<small><em>Scrolling paused during mouse interaction...</em></small>
</label>
</div>
<div className="row" style={{ flex: 1, overflow: "auto" }}>
<div className="col">
</div>
<div className="row" style={{ flex: 1, overflow: "auto" }}>
<div className="col">
<div className="well" style={{ height: innerHeight - 400, display: 'flex', flexDirection: 'column' }}>
<pre className="small" style={remoteConsoleStyle} ref={consoleDiv}
onMouseEnter={() => setAutoScroll(false)}
onMouseLeave={() => setAutoScroll(true)}
Expand All @@ -214,20 +205,25 @@ const ConsoleWindow = (props: IProps) => {
</pre>
</div>
</div>
<div className="row">
<div className="col">
<div className="input-group">
<input type="text" className="form-control"
placeholder="Server command..."
onChange={(evt) => setCMD(evt.target.value)}
value={cmd}
/>
</div>
</div>

<div className="row my-2">
<div className="col-10">
<div className="input-group">
<input type="text" className="form-control"
placeholder="Server command..."
onChange={(evt) => setCMD(evt.target.value)}
value={cmd}
/>
</div>
</div>
</div>
<div className="col-2">
<button className="btn btn-primary" onClick={() => { sendCmd(cmd); setCMD('') }}> Send </button>
</div>
</div>
</Modal>
</>)
</div>
</div>
)
}


Expand Down
Loading