Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separate API call into its own file and simplify #38

Merged
merged 5 commits into from
Jul 8, 2024
Merged
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
352 changes: 127 additions & 225 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import { ThemeProvider } from "@emotion/react";
import { useEffect, useState, useReducer, useMemo } from "react";
import FilterMenu from "./components/FilterMenu.tsx";
import { useEffect, useState } from "react";
import { theme } from "./theme";
import BoxBasic from "./components/Box";
import {
PayloadInterface,
ActionType,
QueryString,
LogRecord,
LogData,
LogMessageApplication,
LogMessageCluster,
Messages,
LogTableRow,
isLogMessageApplication,
isLogMessageCluster,
} from "./schema/interfaces.ts";
import { payload } from "./schema/payload.ts";

import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
Expand All @@ -23,251 +21,155 @@ import TableRow from "@mui/material/TableRow";
import Paper from "@mui/material/Paper";
import { TableHead } from "@mui/material";
import { LogLevel } from "./schema/level.ts";
import { buildSearchQuery, graylogSearch } from "./utils/api/search.ts";

function App() {
// Initialize information for Queries and Auth
const apiURL = "/api/views/search/sync";
const query: QueryString = {};

// Hard-code Actions for Reducer Function
const ACTIONS = {
LOGFILTER: "level",
BEAMLINE: "beamline",
APP: "application",
};

// Initialize states
const EmptyLogRecord = useMemo<LogRecord>(
() => ({
timestamp: [],
host: [],
log_level_str: [],
log_message: [],
log_level: [],
app_name: [],
}),
[],
// Init states
const [logTable, setLogTable] = useState<LogTableRow[]>([]);
const [logDisplayError, setLogDisplayError] = useState<string | undefined>(
undefined,
);

const [LogResponse, setlogResponse] = useState<LogRecord>(EmptyLogRecord);
const [logPayload, handlePayload] = useReducer(reducer, payload);
const [logFilter, setLogfilter] = useState(7);

// Payload Modifiers based off of Application Name or Beamline
const handleBeamline = (beamline: string) => {
if (beamline == "") {
beamline = "*";
}
handlePayload({ type: ACTIONS.BEAMLINE, query_condition: beamline });
};

const handleAppName = (app_name: string) => {
if (app_name == "") {
app_name = "*";
}
handlePayload({ type: ACTIONS.APP, query_condition: app_name });
};

const handleLogFilterChange = (newLogFilterValue: number) => {
setLogfilter(newLogFilterValue);
handlePayload({ type: ACTIONS.LOGFILTER, log_level: newLogFilterValue });
};

// Reducer Function to modify Payload according to hard-coded action
function reducer(payload: PayloadInterface, action: ActionType) {
switch (action.type) {
case ACTIONS.LOGFILTER:
query.filter = `level: <=${action.log_level}`;
break;
case ACTIONS.BEAMLINE:
query.beamline = `beamline: ${action.query_condition}`;
break;
case ACTIONS.APP:
query.app_name = `application_name: ${action.query_condition}`;
break;
}
const query_arr: string[] = Object.values(query);
payload.queries[0].query.query_string = query_arr.join(" AND ");
const newPayload = { ...payload };
return newPayload;
}
const [applicationFilter, setApplicationFilter] = useState("*");
const [beamlineFilter, setBeamlineFilter] = useState("*");

useEffect(() => {
// POSTS API Call at Stores Response For Front-End
async function fetchData(url: string, payload: object): Promise<undefined> {
try {
// Adding required headers for API Call
const headers = new Headers();
headers.append("Content-Type", "application/json");
headers.append("X-Requested-By", "XMLHttpRequest");

// Making the fetch request
const response = await fetch(url, {
method: "POST",
headers: headers,
body: JSON.stringify(payload),
});
// Checking if the response is OK
if (!response.ok) {
throw new Error("Failed to fetch data");
}

// Parsing the response as JSON
const logdata = await response.json();
const ApiResponse: LogRecord = getMessage(logdata) || EmptyLogRecord;
setlogResponse(ApiResponse);
} catch (error) {
console.error("Error fetching data:", error);
throw error;
}
}

// Calls file for auth and calls POST API call
(async () => {
try {
await fetchData(apiURL, payload);
const payload = buildSearchQuery(
logFilter,
applicationFilter,
beamlineFilter,
);
const searchResults = await graylogSearch(payload);
const table = buildTable(searchResults);
setLogTable(table);
} catch (error) {
console.error("Error:", error);
console.error(error);
if (error instanceof Error) {
setLogDisplayError(error.toString());
} else {
setLogDisplayError("Unknown error: see console");
}
}
})();
}, [logPayload, EmptyLogRecord]);
}, [logFilter, applicationFilter, beamlineFilter]);

const colors: { [id: string]: string } = {
WARN: "#d1a317",
ERROR: "#990f0f",
ALERT: "#990f0f",
CRIT: "#990f0f",
default: "",
};

function buildTable(data: LogData): LogTableRow[] {
const logs: Messages[] = Object.values(
Object.values(data.results)[0].search_types,
)[0].messages;
return logs.map(log => {
const message: LogMessageApplication | LogMessageCluster = log.message;
const timestamp = message.timestamp.replace(/[TZ]/g, " ");
let application = "";
let text = "";
if (isLogMessageApplication(message)) {
application = message.application_name;
text = message.full_message;
} else if (isLogMessageCluster(message)) {
application = message.cluster_name;
text = message.message;
} else {
throw new TypeError(
"Graylog response not supported: " + JSON.stringify(message),
);
}
return {
id: message._id,
timestamp: timestamp,
host: message.source,
level: LogLevel[message.level],
text: text,
application: application,
};
});
}

return (
<ThemeProvider theme={theme}>
<h1>Athena Logpanel </h1>
<FilterMenu
level={logFilter}
onLevelChange={handleLogFilterChange}
onLevelChange={setLogfilter}
beamline=""
onBeamlineChange={handleBeamline}
onBeamlineChange={beamline =>
setBeamlineFilter(beamline === "" ? "*" : beamline)
}
application=""
onApplicationChange={handleAppName}
onApplicationChange={application =>
setApplicationFilter(application == "" ? "*" : application)
}
/>

<BoxBasic>
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>
<b>Timestamp</b>
</TableCell>
<TableCell>
<b>Debugging Level</b>
</TableCell>
<TableCell>
<b>Host</b>
</TableCell>
<TableCell>
<b>App Name</b>
</TableCell>
<TableCell>
<b>Log Messages</b>
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{LogResponse.timestamp.map((timestamp, index) => {
return (
<TableRow
sx={{
backgroundColor: getColor(LogResponse.log_level[index]),
}}
>
<TableCell>
<pre>{timestamp}</pre>
</TableCell>
<TableCell>
<pre>{LogResponse.log_level_str[index]}</pre>
</TableCell>
<TableCell>
<pre>{LogResponse.host[index]}</pre>
</TableCell>
<TableCell>
<pre>{LogResponse.app_name[index]}</pre>
</TableCell>
<TableCell>{LogResponse.log_message[index]}</TableCell>
{/* sx={{ '&:last-child td, &:last-child th': { border: 0 } }} */}
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
{(() => {
if (logDisplayError === undefined) {
return (
<TableContainer component={Paper}>
<Table sx={{ minWidth: 650 }} aria-label="simple table">
<TableHead>
<TableRow>
<TableCell>
<b>Timestamp</b>
</TableCell>
<TableCell>
<b>Level</b>
</TableCell>
<TableCell>
<b>Host</b>
</TableCell>
<TableCell>
<b>Application</b>
</TableCell>
<TableCell>
<b>Message</b>
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{logTable.map(row => (
<TableRow
key={row.id}
sx={{
backgroundColor: colors[row.level] || colors.default,
}}
>
<TableCell>
<pre>{row.timestamp}</pre>
</TableCell>
<TableCell>
<pre>{row.level}</pre>
</TableCell>
<TableCell>
<pre>{row.host}</pre>
</TableCell>
<TableCell>
<pre>{row.application}</pre>
</TableCell>
<TableCell>{row.text}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
} else {
return <p>{logDisplayError}</p>;
}
})()}
</BoxBasic>
</ThemeProvider>
);
}

// Collecting relevant information based off of Response Type
function getMessage(logging: JSON): LogRecord | undefined {
const data: LogData = JSON.parse(JSON.stringify(logging));
const log_message: string[] = [];
const timestamp: string[] = [];
const host: string[] = [];
const log_level_str: string[] = [];
const log_level: number[] = [];
const app_name: string[] = [];

const logs: Messages[] = Object.values(
Object.values(data.results)[0].search_types,
)[0].messages;
for (const msg of logs) {
const message: LogMessageApplication | LogMessageCluster = msg.message;
const formattedTimestamp = message.timestamp.replace(/[TZ]/g, " ");
timestamp.push(`${formattedTimestamp}`);
host.push(message.source);
// Type Checking of API Response
if (MessageType(message)) {
app_name.push(message.application_name);
const level_str = LogLevel[message.level] || "UNKNOWN";
log_level_str.push(level_str);
log_message.push(message.full_message);
log_level.push(message.level);
}
if (isLogMessageCluster(message)) {
app_name.push(message.cluster_name);
const level_str = LogLevel[message.level] || "UNKNOWN";
log_level_str.push(level_str);
log_message.push(message.message);
log_level.push(message.level);
// some clusters have a different leveling systems and needs exploring
}
}
return {
timestamp,
host,
log_level_str,
log_message,
log_level,
app_name,
};
}

// Select colour of log row based off log level
const getColor = (level: number) => {
// yellow = #d1a317
// red = #990f0f
if (level === 4) {
return "#d1a317";
} else if (level < 4) {
return "#990f0f";
} else {
return "";
}
};

// Type-Checker Functions for API Response
function MessageType(
_message: LogMessageApplication | LogMessageCluster,
): _message is LogMessageApplication {
return true;
}

function isLogMessageCluster(
_message: LogMessageCluster | LogMessageApplication,
): _message is LogMessageCluster {
return true;
}

export default App;
Loading
Loading