Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -36,6 +36,7 @@
using openXDA.Model.SystemCenter;
using PQView.Model;
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
Expand Down Expand Up @@ -103,7 +104,34 @@ public class EventTypeAssetTypeController : ModelController<EventTypeAssetType>
[RoutePrefix("api/OpenXDA/DataOperation")]
public class DataOperationController : ModelController<DataOperation> { }
[RoutePrefix("api/OpenXDA/DataOperationFailure")]
public class DataOperationFailureController : ModelController<DataOperationFailureDetails> { }
public class DataOperationFailureController : ModelController<DataOperationFailureDetails> {

[Route("RecentFailures/{page}"), HttpPost]
public IHttpActionResult RecentFailures([FromBody] PostData postData, [FromUri] int page)
{
using DataTable value = GetSearchResults(postData, page);
value.Columns.Add("DataFileName", typeof(String));
using (AdoDataConnection connection = new AdoDataConnection(Connection))
{
foreach (DataRow row in value.Rows)
{
TableOperations<openXDA.Model.DataFile> dataFileTbl = new TableOperations<openXDA.Model.DataFile>(connection);
openXDA.Model.DataFile dataFile = dataFileTbl.QueryRecordWhere("FileGroupID = {0}", row.Field<int>("FileGroupID"));
row["DataFileName"] = Path.GetFileName(dataFile.FilePath);
}
}
int num = CountSearchResults(postData);
int valueOrDefault = Take.GetValueOrDefault(50);
return Ok(new PagedResults
{
Data = JsonConvert.SerializeObject(value),
RecordsPerPage = valueOrDefault,
TotalRecords = num,
NumberOfPages = (num + valueOrDefault - 1) / valueOrDefault
});
}

}
[RoutePrefix("api/OpenXDA/DataReader")]
public class DataReaderController : ModelController<DataReader> { }

Expand Down
63 changes: 62 additions & 1 deletion Source/Applications/SystemCenter/Model/DataFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@

using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
Expand All @@ -47,6 +49,7 @@ namespace SystemCenter.Model
SELECT
DataFile.*,
FileGroup.DataStartTime,
FileGroup.ProcessingStartTime,
FileGroup.ProcessingEndTime,
FileGroup.MeterID,
FileGroup.ProcessingStatus AS ProcessingState
Expand All @@ -59,10 +62,13 @@ public class DataFile : openXDA.Model.DataFile
{
[ParentKey(typeof(Meter))]
public int MeterID { get; set; }
public DateTime ProcessingStartTime { get; set; }
[DefaultSortOrder(false)]
public DateTime ProcessingEndTime { get; set; }
public DateTime DataStartTime { get; set; }
public int ProcessingState { get; set; }
[NonRecordField]
public string FileName => Path.GetFileName(FilePath);
}

[RoutePrefix("api/OpenXDA/DataFile")]
Expand Down Expand Up @@ -206,7 +212,62 @@ public IHttpActionResult Download(int id)
}
}


[Route("AggregateRecentlyProcessedFiles"), HttpGet]
public IHttpActionResult AggregateRecentlyProcessedFiles()
{
String sqlQuery = @"
SELECT
FORMAT (FileGroup.ProcessingStartTime, 'yyyy-MM-dd HH') AS Hour,
COUNT(DataFile.ID) as Count
FROM
openXDA.dbo.DataFile JOIN openXDA.dbo.FileGroup ON DataFile.FileGroupID = FileGroup.ID
WHERE
FileGroup.ProcessingStartTime > DATEADD(HOUR, DATEDIFF(HOUR, 0, GETDATE()) - 48, 0)
GROUP BY FORMAT (FileGroup.ProcessingStartTime, 'yyyy-MM-dd HH');";
DataTable result;
using (AdoDataConnection connection = new AdoDataConnection("systemSettings"))
{
result = connection.RetrieveData(sqlQuery);
}
return Ok(result);
}

[Route("PagedResults"), HttpPost]
public override IHttpActionResult GetPagedList([FromBody] PostData postData, int page)
{
if (!GetAuthCheck())
return Unauthorized();

using DataTable table = GetSearchResults(postData, page);
DataFile[] results = table
.AsEnumerable()
.Select(row => new DataFile()
{
CreationTime = row.Field<DateTime>("CreationTime"),
ID = row.Field<int>("ID"),
FileGroupID = row.Field<int>("FileGroupID"),
FilePath = row.Field<string>("FilePath"),
FilePathHash = row.Field<int>("FilePathHash"),
FileSize = row.Field<long>("FileSize"),
LastWriteTime = row.Field<DateTime>("LastWriteTime"),
LastAccessTime = row.Field<DateTime>("LastAccessTime"),
MeterID = row.Field<int>("MeterID"),
DataStartTime = row.Field<DateTime>("DataStartTime"),
ProcessingEndTime = row.Field<DateTime>("ProcessingEndTime"),
ProcessingState = row.Field<int>("ProcessingState"),
ProcessingStartTime = row.Field<DateTime>("ProcessingStartTime")
}).ToArray();
int recordCount = CountSearchResults(postData);
int recordPerPage = Take ?? 50;
return Ok(new PagedResults()
{
Data = JsonConvert.SerializeObject(results),
RecordsPerPage = recordPerPage,
TotalRecords = recordCount,
NumberOfPages = (recordCount + recordPerPage - 1) / recordPerPage
});
}

}

}
38 changes: 20 additions & 18 deletions Source/Applications/SystemCenter/SystemCenter.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,8 @@
<Compile Include="WebClients\HIDSClient.cs" />
<Compile Include="WebClients\HttpClient.cs" />
<Compile Include="WebClients\XDANodeClient.cs" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\AppHost\DataOperationFailure.tsx" />
<TypeScriptCompile Include="wwwroot\Scripts\TSX\SystemCenter\AppHost\FilesProcessed.tsx" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="DebugHost.resx">
Expand Down Expand Up @@ -493,30 +495,30 @@
<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">
<Content Include="wwwroot\Images\NodeTiles\openMIC.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="wwwroot\Images\NodeTiles\XDA.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="wwwroot\Images\NodeTiles\SystemCenter.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="wwwroot\Images\NodeTiles\miMD.png">
<Content Include="wwwroot\Images\NodeTiles\miMD.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<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>
<Content Include="wwwroot\Images\NodeTiles\miMDIcon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<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>
<Content Include="wwwroot\Images\NodeTiles\miMDIcon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="wwwroot\index.cshtml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
Expand Down Expand Up @@ -827,7 +829,7 @@
</CustomServerUrl>
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
<servers defaultServer="SelfHostServer">
<server name="SelfHostServer" exePath="" cmdArgs="" url="http://localhost:8987/" workingDir="" />
<server name="SelfHostServer" exePath="" cmdArgs="" url="http://localhost:8988/" workingDir="" />
</servers>
</WebProjectProperties>
</FlavorProperties>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
//******************************************************************************************************
// DataOperationFailure.tsx - Gbtc
//
// Copyright © 2026, Grid Protection Alliance. All Rights Reserved.
//
// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
// the NOTICE file distributed with this work for additional information regarding copyright ownership.
// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
// file except in compliance with the License. You may obtain a copy of the License at:
//
// http://opensource.org/licenses/MIT
//
// Unless agreed to in writing, the subject software distributed under the License is distributed on an
// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
// License for the specific language governing permissions and limitations.
//
// Code Modification History:
// ----------------------------------------------------------------------------------------------------
// 05/04/2026 - Natalie Beatty
// Generated original version of source code.
//
//******************************************************************************************************
import * as React from 'react'
import moment from 'moment'
import { ToolTip } from '@gpa-gemstone/react-forms'
import { OpenXDA } from '@gpa-gemstone/application-typings';

export interface INamedDataOperationFailure extends OpenXDA.Types.DataOperationFailure {
DataFileName: string
}
interface IProps {
NamedDataOperationFailure: INamedDataOperationFailure
SelectedFile: number
Hovered: string
HandleViewMoreClick: (info: string, evt: React.MouseEvent) => void
SetHovered: React.Dispatch<React.SetStateAction<string>>
}

const DataOperationFailure = (props: IProps) => {
return <div className={`row alert-${props.NamedDataOperationFailure.FileGroupID === props.SelectedFile ? 'warning' : 'danger'} m-2`}
key={props.NamedDataOperationFailure.ID}
>
<div className={'col-2 d-flex justify-content-center align-items-center'}>
<span className={`badge badge-pill badge-secondary`}>{moment(props.NamedDataOperationFailure.TimeOfFailure).format('MM/DD/YYYY hh:mm')}</span>
</div>
<div className={'col-3 d-flex justify-content-center align-items-center'}>
<h6>{props.NamedDataOperationFailure.DataOperationTypeName.split('.')[props.NamedDataOperationFailure.DataOperationTypeName.split('.').length - 1]}</h6>
</div>
<div className={'col-3 d-flex justify-content-center align-items-center'}>{props.NamedDataOperationFailure.DataFileName}</div>
<div className={'col-2 d-flex justify-content-around align-items-center'}>
<div className={'btn btn-primary'}
onMouseEnter={() => props.SetHovered(`failurelog${props.NamedDataOperationFailure.ID.toString()}`)}
onMouseLeave={() => props.SetHovered('')}
data-tooltip={`failurelog${props.NamedDataOperationFailure.ID.toString()}`}
>
View Log
</div>
<ToolTip
Show={props.Hovered === `failurelog${props.NamedDataOperationFailure.ID.toString()}`}
Target={`failurelog${props.NamedDataOperationFailure.ID.toString()}`}
>
{props.NamedDataOperationFailure.Log.length > 100
? <>
<p>{`${props.NamedDataOperationFailure.Log.slice(0, 100)}...`}</p>
<a href="#" onClick={(evt) => { props.HandleViewMoreClick(props.NamedDataOperationFailure.Log, evt) }}>View more</a>
</>
: <p>{props.NamedDataOperationFailure.Log}</p>}
</ToolTip>
</div>
<div className={'col-2 d-flex justify-content-around align-items-center'}>
<div className={'btn btn-primary'}
onMouseEnter={() => props.SetHovered(`failurestacktrace${props.NamedDataOperationFailure.ID.toString()}`)}
onMouseLeave={() => props.SetHovered('')}
data-tooltip={`failurestacktrace${props.NamedDataOperationFailure.ID.toString()}`}
>
View Stack Trace
</div>
<ToolTip
Show={props.Hovered === `failurestacktrace${props.NamedDataOperationFailure.ID.toString()}`}
Target={`failurestacktrace${props.NamedDataOperationFailure.ID.toString()}`}
>
{props.NamedDataOperationFailure.StackTrace.length > 100
? <>
<p>{`${props.NamedDataOperationFailure.StackTrace.slice(0, 100)}...`}</p>
<a href="#" onClick={(evt) => { props.HandleViewMoreClick(props.NamedDataOperationFailure.StackTrace, evt) }}>View more</a>
</>
: <p>{props.NamedDataOperationFailure.StackTrace}</p>}
</ToolTip>
</div>
</div>
}

export default DataOperationFailure
Loading