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

More PatchComponent Changes #5139

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
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
41 changes: 21 additions & 20 deletions ui/v2.5/src/components/Settings/SettingSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { PropsWithChildren } from "react";
import { Card } from "react-bootstrap";
import { useIntl } from "react-intl";
import { useSettings } from "./context";
import { PatchComponent } from "src/patch";

interface ISettingGroup {
id?: string;
Expand All @@ -10,27 +11,27 @@ interface ISettingGroup {
advanced?: boolean;
}

export const SettingSection: React.FC<PropsWithChildren<ISettingGroup>> = ({
id,
children,
headingID,
subHeadingID,
advanced,
}) => {
const intl = useIntl();
const { advancedMode } = useSettings();
export const SettingSection: React.FC<PropsWithChildren<ISettingGroup>> =
PatchComponent(
"SettingSection",
({ id, children, headingID, subHeadingID, advanced }) => {
const intl = useIntl();
const { advancedMode } = useSettings();

if (advanced && !advancedMode) return null;
if (advanced && !advancedMode) return null;

return (
<div className="setting-section" id={id}>
<h1>{headingID ? intl.formatMessage({ id: headingID }) : undefined}</h1>
{subHeadingID ? (
<div className="sub-heading">
{intl.formatMessage({ id: subHeadingID })}
return (
<div className="setting-section" id={id}>
<h1>
{headingID ? intl.formatMessage({ id: headingID }) : undefined}
</h1>
{subHeadingID ? (
<div className="sub-heading">
{intl.formatMessage({ id: subHeadingID })}
</div>
) : undefined}
<Card>{children}</Card>
</div>
) : undefined}
<Card>{children}</Card>
</div>
);
}
);
};
191 changes: 99 additions & 92 deletions ui/v2.5/src/components/Settings/SettingsLogsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useLoggingSubscribe, queryLogs } from "src/core/StashService";
import { SelectSetting } from "./Inputs";
import { SettingSection } from "./SettingSection";
import { JobTable } from "./Tasks/JobTable";
import { PatchComponent } from "src/patch";

function convertTime(logEntry: GQL.LogEntryDataFragment) {
function pad(val: number) {
Expand All @@ -31,23 +32,6 @@ function levelClass(level: string) {
return level.toLowerCase().trim();
}

interface ILogElementProps {
logEntry: LogEntry;
}

const LogElement: React.FC<ILogElementProps> = ({ logEntry }) => {
// pad to maximum length of level enum
const level = logEntry.level.padEnd(GQL.LogLevel.Progress.length);

return (
<div className="row">
<span className="log-time">{logEntry.time}</span>
<span className={`${levelClass(logEntry.level)}`}>{level}</span>
<span className="col col-sm-9">{logEntry.message}</span>
</div>
);
};

class LogEntry {
public time: string;
public level: string;
Expand All @@ -66,92 +50,115 @@ class LogEntry {
}
}

interface ILogElementProps {
logEntry: LogEntry;
}

const LogElement: React.FC<ILogElementProps> = PatchComponent(
"LogElement",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where a component is not exported, it usually indicates that it's part of a larger component. In these cases, it's ideal to name these as a sub-component of the parent component. In this case, since this is a child component of SettingsLogsPanel, I think this should be SettingsLogsPanel.LogElement. This helps reduce potential duplicate component labels.

({ logEntry }) => {
// pad to maximum length of level enum
const level = logEntry.level.padEnd(GQL.LogLevel.Progress.length);

return (
<div className="row">
<span className="log-time">{logEntry.time}</span>
<span className={`${levelClass(logEntry.level)}`}>{level}</span>
<span className="col col-sm-9">{logEntry.message}</span>
</div>
);
}
);

// maximum number of log entries to keep - entries are discarded oldest-first
const MAX_LOG_ENTRIES = 50000;
// maximum number of log entries to display
const MAX_DISPLAY_LOG_ENTRIES = 1000;
const logLevels = ["Trace", "Debug", "Info", "Warning", "Error"];

export const SettingsLogsPanel: React.FC = () => {
const [entries, setEntries] = useState<LogEntry[]>([]);
const { data, error } = useLoggingSubscribe();
const [logLevel, setLogLevel] = useState<string>("Info");
const intl = useIntl();

useEffect(() => {
async function getInitialLogs() {
const logQuery = await queryLogs();
if (logQuery.error) return;

const initEntries = logQuery.data.logs.map((e) => new LogEntry(e));
if (initEntries.length !== 0) {
setEntries((prev) => {
return [...prev, ...initEntries].slice(0, MAX_LOG_ENTRIES);
});
export const SettingsLogsPanel: React.FC = PatchComponent(
"SettingsLogsPanel",
() => {
const [entries, setEntries] = useState<LogEntry[]>([]);
const { data, error } = useLoggingSubscribe();
const [logLevel, setLogLevel] = useState<string>("Info");
const intl = useIntl();

useEffect(() => {
async function getInitialLogs() {
const logQuery = await queryLogs();
if (logQuery.error) return;

const initEntries = logQuery.data.logs.map((e) => new LogEntry(e));
if (initEntries.length !== 0) {
setEntries((prev) => {
return [...prev, ...initEntries].slice(0, MAX_LOG_ENTRIES);
});
}
}
}

getInitialLogs();
}, []);

useEffect(() => {
if (!data) return;

const newEntries = data.loggingSubscribe.map((e) => new LogEntry(e));
newEntries.reverse();
setEntries((prev) => {
return [...newEntries, ...prev].slice(0, MAX_LOG_ENTRIES);
});
}, [data]);

const displayEntries = entries
.filter(filterByLogLevel)
.slice(0, MAX_DISPLAY_LOG_ENTRIES);

function maybeRenderError() {
if (error) {
return (
<div className="error">
Error connecting to log server: {error.message}
</div>
);
getInitialLogs();
}, []);

useEffect(() => {
if (!data) return;

const newEntries = data.loggingSubscribe.map((e) => new LogEntry(e));
newEntries.reverse();
setEntries((prev) => {
return [...newEntries, ...prev].slice(0, MAX_LOG_ENTRIES);
});
}, [data]);

const displayEntries = entries
.filter(filterByLogLevel)
.slice(0, MAX_DISPLAY_LOG_ENTRIES);

function maybeRenderError() {
if (error) {
return (
<div className="error">
Error connecting to log server: {error.message}
</div>
);
}
}
}

function filterByLogLevel(logEntry: LogEntry) {
if (logLevel === "Trace") return true;
function filterByLogLevel(logEntry: LogEntry) {
if (logLevel === "Trace") return true;

const logLevelIndex = logLevels.indexOf(logLevel);
const levelIndex = logLevels.indexOf(logEntry.level);
const logLevelIndex = logLevels.indexOf(logLevel);
const levelIndex = logLevels.indexOf(logEntry.level);

return levelIndex >= logLevelIndex;
}
return levelIndex >= logLevelIndex;
}

return (
<>
<h2>{intl.formatMessage({ id: "config.tasks.job_queue" })}</h2>
<JobTable />
<SettingSection headingID="config.categories.logs">
<SelectSetting
id="log-level"
headingID="config.logs.log_level"
value={logLevel}
onChange={(v) => setLogLevel(v)}
>
{logLevels.map((level) => (
<option key={level} value={level}>
{level}
</option>
return (
<>
<h2>{intl.formatMessage({ id: "config.tasks.job_queue" })}</h2>
<JobTable />
<SettingSection headingID="config.categories.logs">
<SelectSetting
id="log-level"
headingID="config.logs.log_level"
value={logLevel}
onChange={(v) => setLogLevel(v)}
>
{logLevels.map((level) => (
<option key={level} value={level}>
{level}
</option>
))}
</SelectSetting>
</SettingSection>

<div className="logs">
{maybeRenderError()}
{displayEntries.map((logEntry) => (
<LogElement logEntry={logEntry} key={logEntry.id} />
))}
</SelectSetting>
</SettingSection>

<div className="logs">
{maybeRenderError()}
{displayEntries.map((logEntry) => (
<LogElement logEntry={logEntry} key={logEntry.id} />
))}
</div>
</>
);
};
</div>
</>
);
}
);
Loading
Loading