mirror of
https://github.com/QingWei-Li/notea.git
synced 2024-11-26 00:09:59 +03:00
feat: Add rudimentary debugging utilities
/debug: Added a /debug page that's only accessible if a fatal error in configuration has occurred. settings: The settings page now includes a debugging section with similar content to the debug page. This still needs styling but is otherwise OK.
This commit is contained in:
parent
2431c96409
commit
615041a927
1
.gitignore
vendored
1
.gitignore
vendored
@ -23,6 +23,7 @@ node_modules
|
|||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
/logs
|
||||||
|
|
||||||
# local env files
|
# local env files
|
||||||
.env
|
.env
|
||||||
|
@ -26,7 +26,7 @@ COPY --from=builder /app/node_modules ./node_modules
|
|||||||
VOLUME /app/config
|
VOLUME /app/config
|
||||||
# VOLUME /app/data
|
# VOLUME /app/data
|
||||||
|
|
||||||
ENV CONFIG_FILE=/app/config/notea.yml
|
ENV CONFIG_FILE=/app/config/notea.yml LOG_DIRECTORY=/app/logs
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
|
74
components/debug/debug-info-copy-button.tsx
Normal file
74
components/debug/debug-info-copy-button.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { Button } from '@material-ui/core';
|
||||||
|
import useI18n from 'libs/web/hooks/use-i18n';
|
||||||
|
import { FC } from 'react';
|
||||||
|
import { logLevelToString, DebugInformation } from 'libs/shared/debugging';
|
||||||
|
|
||||||
|
export const DebugInfoCopyButton: FC<{
|
||||||
|
debugInfo: DebugInformation;
|
||||||
|
}> = ({ debugInfo }) => {
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
function generateDebugInfo(): string {
|
||||||
|
let data =
|
||||||
|
'Notea debug information' +
|
||||||
|
'\nTime ' +
|
||||||
|
new Date(Date.now()).toISOString() +
|
||||||
|
'\n\n';
|
||||||
|
function ensureNewline() {
|
||||||
|
if (!data.endsWith('\n') && data.length >= 1) {
|
||||||
|
data += '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (debugInfo.issues.length > 0) {
|
||||||
|
ensureNewline();
|
||||||
|
data += 'Configuration errors: ';
|
||||||
|
let i = 1;
|
||||||
|
const prefixLength = debugInfo.issues.length.toString().length;
|
||||||
|
for (const issue of debugInfo.issues) {
|
||||||
|
const prefix = i.toString().padStart(prefixLength, ' ') + ': ';
|
||||||
|
const empty = ' '.repeat(prefixLength + 2);
|
||||||
|
|
||||||
|
data += prefix + issue.name;
|
||||||
|
data += empty + '// ' + issue.cause;
|
||||||
|
data += empty + issue.description;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data += 'No detected configuration errors.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (debugInfo.logs.length > 0) {
|
||||||
|
ensureNewline();
|
||||||
|
for (const log of debugInfo.logs) {
|
||||||
|
data += `[${new Date(log.time).toISOString()} ${log.name}] ${logLevelToString(log.level)}: ${log.msg}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyDebugInfo() {
|
||||||
|
const text = generateDebugInfo();
|
||||||
|
|
||||||
|
navigator.clipboard
|
||||||
|
.writeText(text)
|
||||||
|
.then(() => {
|
||||||
|
// nothing
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(
|
||||||
|
'Error when trying to copy debugging information to clipboard: %O',
|
||||||
|
e
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button variant="contained" onClick={copyDebugInfo}>
|
||||||
|
{t('Copy debugging information')}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
17
components/debug/issue-list.tsx
Normal file
17
components/debug/issue-list.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { FC } from 'react';
|
||||||
|
import { Issue } from 'libs/shared/debugging';
|
||||||
|
import { Issue as IssueDisplay } from './issue';
|
||||||
|
|
||||||
|
export const IssueList: FC<{
|
||||||
|
issues: Array<Issue>;
|
||||||
|
}> = ({ issues }) => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col">
|
||||||
|
{issues.map((v, i) => {
|
||||||
|
return (
|
||||||
|
<IssueDisplay key={i} issue={v} id={`issue-${i}`}/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
165
components/debug/issue.tsx
Normal file
165
components/debug/issue.tsx
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
import { FC } from 'react';
|
||||||
|
import {
|
||||||
|
getNameFromRecommendation,
|
||||||
|
getNameFromSeverity,
|
||||||
|
Issue as IssueInfo,
|
||||||
|
IssueFix,
|
||||||
|
IssueSeverity
|
||||||
|
} from 'libs/shared/debugging';
|
||||||
|
import {
|
||||||
|
Accordion as MuiAccordion,
|
||||||
|
AccordionDetails as MuiAccordionDetails,
|
||||||
|
AccordionSummary as MuiAccordionSummary,
|
||||||
|
withStyles
|
||||||
|
} from '@material-ui/core';
|
||||||
|
import { ChevronDownIcon } from '@heroicons/react/outline';
|
||||||
|
import useI18n from 'libs/web/hooks/use-i18n';
|
||||||
|
|
||||||
|
const Accordion = withStyles({
|
||||||
|
root: {
|
||||||
|
boxShadow: 'none',
|
||||||
|
'&:not(:last-child)': {
|
||||||
|
borderBottom: 0,
|
||||||
|
},
|
||||||
|
'&:before': {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
'&$expanded': {
|
||||||
|
margin: 'auto 0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expanded: {
|
||||||
|
},
|
||||||
|
})(MuiAccordion);
|
||||||
|
|
||||||
|
const AccordionSummary = withStyles({
|
||||||
|
root: {
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, .03)',
|
||||||
|
borderBottom: '1px solid rgba(0, 0, 0, .125)',
|
||||||
|
borderTopRightRadius: 'inherit',
|
||||||
|
borderBottomRightRadius: 'inherit',
|
||||||
|
marginBottom: -1,
|
||||||
|
minHeight: 56,
|
||||||
|
'&$expanded': {
|
||||||
|
minHeight: 56,
|
||||||
|
borderBottomRightRadius: '0'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
'&$expanded': {
|
||||||
|
margin: '12px 0',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
expanded: {},
|
||||||
|
})(MuiAccordionSummary);
|
||||||
|
|
||||||
|
const AccordionDetails = withStyles((theme) => ({
|
||||||
|
root: {
|
||||||
|
padding: theme.spacing(2),
|
||||||
|
},
|
||||||
|
}))(MuiAccordionDetails);
|
||||||
|
|
||||||
|
interface FixProps {
|
||||||
|
id: string;
|
||||||
|
fix: IssueFix;
|
||||||
|
}
|
||||||
|
const Fix: FC<FixProps> = ({ id, fix }) => {
|
||||||
|
const i18n = useI18n();
|
||||||
|
const { t } = i18n;
|
||||||
|
const steps = fix.steps ?? [];
|
||||||
|
return (
|
||||||
|
<Accordion key={id} className={"bg-gray-300"}>
|
||||||
|
<AccordionSummary
|
||||||
|
expandIcon={<ChevronDownIcon width=".8em"/>}
|
||||||
|
aria-controls={`${id}-details`}
|
||||||
|
id={`${id}-summary`}
|
||||||
|
>
|
||||||
|
<div className={"flex flex-col"}>
|
||||||
|
{fix.recommendation === 0 && (
|
||||||
|
<span className={"text-xs uppercase"}>{getNameFromRecommendation(fix.recommendation, i18n)}</span>
|
||||||
|
)}
|
||||||
|
<span className={"font-bold"}>{fix.description}</span>
|
||||||
|
</div>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails className={"rounded-[inherit]"}>
|
||||||
|
{steps.length > 0 ? (
|
||||||
|
<ol className="list-decimal list-inside">
|
||||||
|
{steps.map((step, i) => {
|
||||||
|
const stepId = `${id}-step-${i}`;
|
||||||
|
return (
|
||||||
|
<li key={stepId}>{step}</li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</ol>
|
||||||
|
) : (
|
||||||
|
<span>{t('No steps were provided by Notea to perform this fix.')}</span>
|
||||||
|
)}
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IssueProps {
|
||||||
|
issue: IssueInfo;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
export const Issue: FC<IssueProps> = function (props) {
|
||||||
|
const { issue, id } = props;
|
||||||
|
const i18n = useI18n();
|
||||||
|
const { t } = i18n;
|
||||||
|
|
||||||
|
let borderColour: string;
|
||||||
|
switch (issue.severity) {
|
||||||
|
case IssueSeverity.SUGGESTION:
|
||||||
|
borderColour = "border-gray-500";
|
||||||
|
break;
|
||||||
|
case IssueSeverity.WARNING:
|
||||||
|
borderColour = "border-yellow-100";
|
||||||
|
break;
|
||||||
|
case IssueSeverity.ERROR:
|
||||||
|
borderColour = "border-red-500";
|
||||||
|
break;
|
||||||
|
case IssueSeverity.FATAL_ERROR:
|
||||||
|
borderColour = "border-red-900";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Accordion className={`border-l-4 ${borderColour} bg-gray-200`}>
|
||||||
|
<AccordionSummary
|
||||||
|
className={"bg-gray-100"}
|
||||||
|
expandIcon={<ChevronDownIcon width=".8em"/>}
|
||||||
|
aria-controls={`${id}-details`}
|
||||||
|
id={`${id}-summary`}
|
||||||
|
>
|
||||||
|
<div className={"flex flex-col bg-transparent"}>
|
||||||
|
<span className={"text-xs uppercase"}>{getNameFromSeverity(issue.severity, i18n)}</span>
|
||||||
|
<span className={"font-bold"}>{issue.name}</span>
|
||||||
|
</div>
|
||||||
|
</AccordionSummary>
|
||||||
|
<AccordionDetails className={"flex flex-col"}>
|
||||||
|
<span>{issue.description ?? t('No description was provided for this issue.')}</span>
|
||||||
|
|
||||||
|
{issue.fixes.length > 0 ? (
|
||||||
|
<div className={"mt-1 flex flex-col"}>
|
||||||
|
<span className={"font-bold"}>{t('Potential fixes')}</span>
|
||||||
|
<div>
|
||||||
|
{issue.fixes.map((fix, i) => {
|
||||||
|
const fixId = `${id}-fix-${i}`;
|
||||||
|
return (
|
||||||
|
<Fix
|
||||||
|
key={fixId}
|
||||||
|
id={fixId}
|
||||||
|
fix={fix}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span>{t('No fixes are known by Notea for this issue.')}</span>
|
||||||
|
)}
|
||||||
|
</AccordionDetails>
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
};
|
27
components/debug/logs.tsx
Normal file
27
components/debug/logs.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { FC } from 'react';
|
||||||
|
import { logLevelToString, LogLike } from 'libs/shared/debugging';
|
||||||
|
|
||||||
|
interface LogsProps {
|
||||||
|
logs: Array<LogLike>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Logs: FC<LogsProps> = (props) => {
|
||||||
|
return (
|
||||||
|
<div className={"flex flex-col space-y-1"}>
|
||||||
|
{props.logs.length > 0 ? props.logs.map((log, i) => {
|
||||||
|
return (
|
||||||
|
<div className={"flex flex-col border-l pl-2"} key={i}>
|
||||||
|
<span className={"text-sm uppercase"}>
|
||||||
|
{logLevelToString(log.level)} at {new Date(log.time ?? 0).toLocaleString()} from <b>{log.name}</b>
|
||||||
|
</span>
|
||||||
|
<span className={"font-mono"}>
|
||||||
|
{log.msg}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}) : (
|
||||||
|
<span>No logs.</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
20
components/settings/debugging.tsx
Normal file
20
components/settings/debugging.tsx
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { IssueList } from 'components/debug/issue-list';
|
||||||
|
import type { FC } from 'react';
|
||||||
|
import { DebugInfoCopyButton } from 'components/debug/debug-info-copy-button';
|
||||||
|
import { DebugInformation, Issue } from 'libs/shared/debugging';
|
||||||
|
|
||||||
|
export const Debugging: FC<{
|
||||||
|
debugInfo: DebugInformation;
|
||||||
|
}> = (props) => {
|
||||||
|
const issues: Array<Issue> = [...props.debugInfo.issues].sort((a, b) => b.severity - a.severity);
|
||||||
|
return (
|
||||||
|
<div className="my-2">
|
||||||
|
<IssueList
|
||||||
|
issues={issues}
|
||||||
|
/>
|
||||||
|
<div className={'flex flex-row my-2'}>
|
||||||
|
<DebugInfoCopyButton debugInfo={props.debugInfo} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -8,6 +8,8 @@ import { ImportOrExport } from './import-or-export';
|
|||||||
import { SnippetInjection } from './snippet-injection';
|
import { SnippetInjection } from './snippet-injection';
|
||||||
import useI18n from 'libs/web/hooks/use-i18n';
|
import useI18n from 'libs/web/hooks/use-i18n';
|
||||||
import { SettingsHeader } from './settings-header';
|
import { SettingsHeader } from './settings-header';
|
||||||
|
import { Debugging } from 'components/settings/debugging';
|
||||||
|
import { DebugInformation } from 'libs/shared/debugging';
|
||||||
|
|
||||||
export const defaultFieldConfig: TextFieldProps = {
|
export const defaultFieldConfig: TextFieldProps = {
|
||||||
fullWidth: true,
|
fullWidth: true,
|
||||||
@ -26,7 +28,9 @@ const HR = () => {
|
|||||||
return <hr className="my-10 border-gray-200" />;
|
return <hr className="my-10 border-gray-200" />;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SettingsContainer: FC = () => {
|
export const SettingsContainer: FC<{
|
||||||
|
debugInfo: DebugInformation
|
||||||
|
}> = (props) => {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -36,6 +40,7 @@ export const SettingsContainer: FC = () => {
|
|||||||
<Language></Language>
|
<Language></Language>
|
||||||
<Theme></Theme>
|
<Theme></Theme>
|
||||||
<EditorWidth></EditorWidth>
|
<EditorWidth></EditorWidth>
|
||||||
|
|
||||||
<HR />
|
<HR />
|
||||||
<SettingsHeader
|
<SettingsHeader
|
||||||
id="import-and-export"
|
id="import-and-export"
|
||||||
@ -44,11 +49,21 @@ export const SettingsContainer: FC = () => {
|
|||||||
'Import a zip file containing markdown files to this location, or export all pages from this location.'
|
'Import a zip file containing markdown files to this location, or export all pages from this location.'
|
||||||
)}
|
)}
|
||||||
></SettingsHeader>
|
></SettingsHeader>
|
||||||
|
|
||||||
<ImportOrExport></ImportOrExport>
|
<ImportOrExport></ImportOrExport>
|
||||||
|
|
||||||
<HR />
|
<HR />
|
||||||
<SettingsHeader id="sharing" title={t('Sharing')}></SettingsHeader>
|
<SettingsHeader id="sharing" title={t('Sharing')}></SettingsHeader>
|
||||||
<SnippetInjection></SnippetInjection>
|
<SnippetInjection></SnippetInjection>
|
||||||
|
|
||||||
|
<HR />
|
||||||
|
<SettingsHeader
|
||||||
|
id="debug"
|
||||||
|
title={t('Debugging')}
|
||||||
|
description={t(
|
||||||
|
'Provides information about your Notea instance that can be helpful when trying to fix problems.'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Debugging debugInfo={props.debugInfo} />
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
import yaml from 'js-yaml';
|
import yaml from 'js-yaml';
|
||||||
import * as env from 'libs/shared/env';
|
import * as env from 'libs/shared/env';
|
||||||
import { existsSync, readFileSync } from 'fs';
|
import { existsSync, readFileSync } from 'fs';
|
||||||
|
import { isProbablyError } from 'libs/shared/util';
|
||||||
|
import { createLogger, Issue, IssueCategory, IssueFixRecommendation, IssueSeverity } from 'libs/server/debugging';
|
||||||
|
|
||||||
|
const logger = createLogger("config");
|
||||||
|
|
||||||
export type BasicUser = { username: string; password: string };
|
export type BasicUser = { username: string; password: string };
|
||||||
type BasicMultiUserConfiguration = {
|
type BasicMultiUserConfiguration = {
|
||||||
@ -45,23 +49,106 @@ export interface Configuration {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
let loaded: Configuration | undefined = undefined;
|
let loaded: Configuration | undefined = undefined;
|
||||||
|
|
||||||
export function loadConfig() {
|
enum ErrTitle {
|
||||||
|
CONFIG_FILE_READ_FAIL = 'Failed to load configuration file',
|
||||||
|
INVALID_AUTH_CONFIG = 'Invalid authorisation configuration',
|
||||||
|
CONFIG_FILE_PARSE_FAIL = 'Could not parse configuration file',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadConfigAndListErrors(): {
|
||||||
|
config?: Configuration;
|
||||||
|
errors: Array<Issue>;
|
||||||
|
} {
|
||||||
|
logger.debug("Loading configuration from scratch (loadConfigAndListErrors)");
|
||||||
|
// TODO: More config errors
|
||||||
const configFile = env.getEnvRaw('CONFIG_FILE', false) ?? './notea.yml';
|
const configFile = env.getEnvRaw('CONFIG_FILE', false) ?? './notea.yml';
|
||||||
|
const errors: Array<Issue> = [];
|
||||||
|
|
||||||
let baseConfig: Configuration = {} as Configuration;
|
let baseConfig: Configuration = {} as Configuration;
|
||||||
if (existsSync(configFile)) {
|
if (existsSync(configFile)) {
|
||||||
const data = readFileSync(configFile, 'utf-8');
|
let data;
|
||||||
|
try {
|
||||||
|
data = readFileSync(configFile, 'utf-8');
|
||||||
|
} catch (e) {
|
||||||
|
let cause;
|
||||||
|
if (isProbablyError(e)) {
|
||||||
|
cause = e.message;
|
||||||
|
}
|
||||||
|
errors.push({
|
||||||
|
name: ErrTitle.CONFIG_FILE_READ_FAIL,
|
||||||
|
description: "The configuration file couldn't be read.",
|
||||||
|
cause,
|
||||||
|
severity: IssueSeverity.WARNING,
|
||||||
|
category: IssueCategory.CONFIG,
|
||||||
|
fixes: [
|
||||||
|
{
|
||||||
|
description: "Make sure Notea has read access to the configuration file",
|
||||||
|
recommendation: IssueFixRecommendation.NEUTRAL,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'Make sure no other programme is using the configuration file',
|
||||||
|
recommendation: IssueFixRecommendation.NEUTRAL
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (data) {
|
||||||
|
try {
|
||||||
baseConfig = yaml.load(data) as Configuration;
|
baseConfig = yaml.load(data) as Configuration;
|
||||||
|
} catch (e) {
|
||||||
|
let cause;
|
||||||
|
if (isProbablyError(e)) {
|
||||||
|
cause = e.message;
|
||||||
|
}
|
||||||
|
errors.push({
|
||||||
|
name: ErrTitle.CONFIG_FILE_PARSE_FAIL,
|
||||||
|
description:
|
||||||
|
'The configuration file could not be parsed, probably due to a syntax error.',
|
||||||
|
severity: IssueSeverity.WARNING,
|
||||||
|
category: IssueCategory.CONFIG,
|
||||||
|
cause,
|
||||||
|
fixes: [
|
||||||
|
{
|
||||||
|
description: 'Check your configuration file for syntax errors.',
|
||||||
|
recommendation: IssueFixRecommendation.RECOMMENDED
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const disablePassword = env.parseBool(env.getEnvRaw('DISABLE_PASSWORD', false), false);
|
const disablePassword = env.parseBool(
|
||||||
|
env.getEnvRaw('DISABLE_PASSWORD', false),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
let auth: AuthConfiguration;
|
let auth: AuthConfiguration = { type: 'none' };
|
||||||
if (!disablePassword) {
|
if (!disablePassword) {
|
||||||
const envPassword = env.getEnvRaw('PASSWORD', false);
|
const envPassword = env.getEnvRaw('PASSWORD', false);
|
||||||
if (baseConfig.auth === undefined) {
|
if (baseConfig.auth === undefined) {
|
||||||
if (envPassword === undefined) {
|
if (envPassword === undefined) {
|
||||||
throw new Error('Authentication undefined');
|
errors.push({
|
||||||
|
name: ErrTitle.INVALID_AUTH_CONFIG,
|
||||||
|
description:
|
||||||
|
'Neither the configuration file, the PASSWORD environment variable, nor the DISABLE_PASSWORD environment variable was set.',
|
||||||
|
severity: IssueSeverity.FATAL_ERROR,
|
||||||
|
category: IssueCategory.CONFIG,
|
||||||
|
fixes: [
|
||||||
|
{
|
||||||
|
description: 'Include an auth section the configuration file',
|
||||||
|
recommendation: IssueFixRecommendation.RECOMMENDED,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'Set the password using the environment variable',
|
||||||
|
recommendation: IssueFixRecommendation.NEUTRAL,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'Disable authentication',
|
||||||
|
recommendation: IssueFixRecommendation.NOT_ADVISED
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
auth = {
|
auth = {
|
||||||
type: 'basic',
|
type: 'basic',
|
||||||
@ -71,14 +158,39 @@ export function loadConfig() {
|
|||||||
} else {
|
} else {
|
||||||
auth = baseConfig.auth;
|
auth = baseConfig.auth;
|
||||||
if (envPassword !== undefined) {
|
if (envPassword !== undefined) {
|
||||||
throw new Error(
|
errors.push({
|
||||||
'Cannot specify PASSWORD when auth config section is present'
|
name: ErrTitle.INVALID_AUTH_CONFIG,
|
||||||
);
|
description:
|
||||||
|
'The PASSWORD environment variable cannot be set when the file configuration contains an auth section.',
|
||||||
|
category: IssueCategory.CONFIG,
|
||||||
|
severity: IssueSeverity.FATAL_ERROR,
|
||||||
|
fixes: [
|
||||||
|
{
|
||||||
|
description: "Don't set the PASSWORD environment variable prior to running Notea.",
|
||||||
|
recommendation: IssueFixRecommendation.RECOMMENDED
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'Remove the auth section from your file configuration.',
|
||||||
|
recommendation: IssueFixRecommendation.NEUTRAL
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (auth.type === 'basic') {
|
if (auth.type === 'basic') {
|
||||||
if (auth.users) {
|
if (auth.users) {
|
||||||
// TEMPORARILY;
|
// TEMPORARILY;
|
||||||
throw new Error('Multiple users are not yet supported');
|
errors.push({
|
||||||
|
name: ErrTitle.INVALID_AUTH_CONFIG,
|
||||||
|
description: 'Multiple users are not yet supported',
|
||||||
|
severity: IssueSeverity.FATAL_ERROR,
|
||||||
|
category: IssueCategory.CONFIG,
|
||||||
|
fixes: [
|
||||||
|
{
|
||||||
|
description: "Change to a single-user configuration.",
|
||||||
|
recommendation: IssueFixRecommendation.RECOMMENDED
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
/*for (const user of auth.users) {
|
/*for (const user of auth.users) {
|
||||||
user.username = user.username.toString();
|
user.username = user.username.toString();
|
||||||
@ -132,7 +244,10 @@ export function loadConfig() {
|
|||||||
'STORE_PREFIX',
|
'STORE_PREFIX',
|
||||||
false,
|
false,
|
||||||
) ?? store.prefix ?? '';
|
) ?? store.prefix ?? '';
|
||||||
store.proxyAttachments = env.parseBool(env.getEnvRaw('DIRECT_RESPONSE_ATTACHMENT', false), store.proxyAttachments ?? false);
|
store.proxyAttachments = env.parseBool(
|
||||||
|
env.getEnvRaw('DIRECT_RESPONSE_ATTACHMENT', false),
|
||||||
|
store.proxyAttachments ?? false
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let server: ServerConfiguration;
|
let server: ServerConfiguration;
|
||||||
@ -142,20 +257,50 @@ export function loadConfig() {
|
|||||||
server = baseConfig.server;
|
server = baseConfig.server;
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
server.useSecureCookies = env.parseBool(env.getEnvRaw('COOKIE_SECURE', false), server.useSecureCookies ?? process.env.NODE_ENV === 'production');
|
server.useSecureCookies = env.parseBool(
|
||||||
|
env.getEnvRaw('COOKIE_SECURE', false),
|
||||||
|
server.useSecureCookies ?? process.env.NODE_ENV === 'production'
|
||||||
|
);
|
||||||
server.baseUrl = env.getEnvRaw('BASE_URL', false) ?? server.baseUrl;
|
server.baseUrl = env.getEnvRaw('BASE_URL', false) ?? server.baseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
loaded = {
|
return {
|
||||||
|
config: {
|
||||||
auth,
|
auth,
|
||||||
store,
|
store,
|
||||||
server
|
server,
|
||||||
|
},
|
||||||
|
errors,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MAX_ERRORS = 2;
|
||||||
|
export function loadConfig() {
|
||||||
|
const result = loadConfigAndListErrors();
|
||||||
|
|
||||||
|
if (!result.config) {
|
||||||
|
const { errors } = result;
|
||||||
|
let name = errors
|
||||||
|
.slice(0, MAX_ERRORS)
|
||||||
|
.map((v) => v.name)
|
||||||
|
.join(', ');
|
||||||
|
if (errors.length > MAX_ERRORS) {
|
||||||
|
const rest = errors.length - MAX_ERRORS;
|
||||||
|
name += ' and ' + rest + ' other error' + (rest > 1 ? 's' : '');
|
||||||
|
}
|
||||||
|
throw new Error(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
loaded = result.config;
|
||||||
|
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
export function config(): Configuration {
|
export function config(): Configuration {
|
||||||
if (!loaded) {
|
if (!loaded) {
|
||||||
|
logger.debug("Loading configuration");
|
||||||
loadConfig();
|
loadConfig();
|
||||||
|
logger.debug("Successfully loaded configuration");
|
||||||
}
|
}
|
||||||
|
|
||||||
return loaded as Configuration;
|
return loaded as Configuration;
|
||||||
|
@ -18,6 +18,7 @@ import { Settings } from 'libs/shared/settings';
|
|||||||
import { TreeModel } from 'libs/shared/tree';
|
import { TreeModel } from 'libs/shared/tree';
|
||||||
import { UserAgentType } from 'libs/shared/ua';
|
import { UserAgentType } from 'libs/shared/ua';
|
||||||
import { useStore } from './middlewares/store';
|
import { useStore } from './middlewares/store';
|
||||||
|
import { DebugInformation } from 'libs/server/debugging';
|
||||||
|
|
||||||
export interface ServerState {
|
export interface ServerState {
|
||||||
store: StoreProvider;
|
store: StoreProvider;
|
||||||
@ -36,6 +37,7 @@ export interface ServerProps {
|
|||||||
tree?: TreeModel;
|
tree?: TreeModel;
|
||||||
ua?: UserAgentType;
|
ua?: UserAgentType;
|
||||||
disablePassword: boolean;
|
disablePassword: boolean;
|
||||||
|
debugInformation?: DebugInformation;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ApiRequest = NextApiRequest & {
|
export type ApiRequest = NextApiRequest & {
|
||||||
|
68
libs/server/debugging.ts
Normal file
68
libs/server/debugging.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { loadConfigAndListErrors } from 'libs/server/config';
|
||||||
|
import pino from 'pino';
|
||||||
|
import pinoPretty from 'pino-pretty';
|
||||||
|
import Logger = pino.Logger;
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import { DebugInformation, Issue } from 'libs/shared/debugging';
|
||||||
|
|
||||||
|
export * from 'libs/shared/debugging'; // here's a lil' lesson in trickery
|
||||||
|
|
||||||
|
const runtimeIssues: Array<Issue> = [];
|
||||||
|
export function reportRuntimeIssue(issue: Issue) {
|
||||||
|
runtimeIssues.push({
|
||||||
|
...issue,
|
||||||
|
isRuntime: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findIssues(): Array<Issue> {
|
||||||
|
const issues: Array<Issue> = [];
|
||||||
|
|
||||||
|
const cfg = loadConfigAndListErrors();
|
||||||
|
issues.push(...cfg.errors);
|
||||||
|
|
||||||
|
issues.push(...runtimeIssues);
|
||||||
|
|
||||||
|
return issues;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function collectDebugInformation(): DebugInformation {
|
||||||
|
const issues = findIssues();
|
||||||
|
return {
|
||||||
|
issues,
|
||||||
|
logs: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLogFile(name: string) {
|
||||||
|
const dir = path.resolve(process.cwd(), process.env.LOG_DIRECTORY ?? 'logs');
|
||||||
|
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir, {
|
||||||
|
recursive: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.resolve(dir, `${name}.log`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const loggerTransport: Parameters<typeof pino.multistream>[0] = [
|
||||||
|
{
|
||||||
|
stream: fs.createWriteStream(getLogFile('debug'), { flags: 'a' }),
|
||||||
|
level: "debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
stream: pinoPretty(),
|
||||||
|
level: "info"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const multistream = pino.multistream(loggerTransport);
|
||||||
|
|
||||||
|
export function createLogger(name: string): Logger {
|
||||||
|
return pino({
|
||||||
|
name,
|
||||||
|
level: "trace",
|
||||||
|
}, multistream);
|
||||||
|
}
|
14
libs/server/middlewares/misconfiguration.ts
Normal file
14
libs/server/middlewares/misconfiguration.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { SSRMiddleware } from '../connect';
|
||||||
|
import { collectDebugInformation } from 'libs/server/debugging';
|
||||||
|
|
||||||
|
export const applyMisconfiguration: SSRMiddleware = async (req, _res, next) => {
|
||||||
|
// const IS_DEMO = getEnv<boolean>('IS_DEMO', false);
|
||||||
|
|
||||||
|
const collected = collectDebugInformation();
|
||||||
|
req.props = {
|
||||||
|
...req.props,
|
||||||
|
debugInformation: collected
|
||||||
|
};
|
||||||
|
|
||||||
|
next();
|
||||||
|
};
|
@ -1,19 +1,31 @@
|
|||||||
import { DEFAULT_SETTINGS } from 'libs/shared/settings';
|
import { DEFAULT_SETTINGS } from 'libs/shared/settings';
|
||||||
import { getSettings } from 'pages/api/settings';
|
import { getSettings } from 'pages/api/settings';
|
||||||
import { SSRMiddleware } from '../connect';
|
import { SSRMiddleware } from '../connect';
|
||||||
|
import { Redirect } from 'next';
|
||||||
|
|
||||||
export const applySettings: SSRMiddleware = async (req, _res, next) => {
|
export const applySettings: SSRMiddleware = async (req, _res, next) => {
|
||||||
const settings = await getSettings(req.state.store);
|
let settings, redirect: Redirect = req.redirect;
|
||||||
|
try {
|
||||||
|
settings = await getSettings(req.state.store);
|
||||||
|
} catch (e) {
|
||||||
|
redirect = {
|
||||||
|
permanent: false,
|
||||||
|
destination: '/debug'
|
||||||
|
};
|
||||||
|
}
|
||||||
let lngDict = {};
|
let lngDict = {};
|
||||||
|
|
||||||
|
if (settings) {
|
||||||
// import language dict
|
// import language dict
|
||||||
if (settings.locale && settings.locale !== DEFAULT_SETTINGS.locale) {
|
if (settings.locale && settings.locale !== DEFAULT_SETTINGS.locale) {
|
||||||
lngDict = (await import(`locales/${settings.locale}.json`)).default;
|
lngDict = (await import(`locales/${settings.locale}.json`)).default;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
req.props = {
|
req.props = {
|
||||||
...req.props,
|
...req.props,
|
||||||
...{ settings, lngDict },
|
...{ settings, lngDict }
|
||||||
};
|
};
|
||||||
|
req.redirect = redirect;
|
||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
|
89
libs/shared/debugging.ts
Normal file
89
libs/shared/debugging.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
import { ContextProps as I18nContextProps } from 'libs/web/utils/i18n-provider';
|
||||||
|
import pino from 'pino';
|
||||||
|
|
||||||
|
export enum IssueCategory {
|
||||||
|
CONFIG = "config",
|
||||||
|
MISC = "misc",
|
||||||
|
STORE = "store",
|
||||||
|
}
|
||||||
|
export enum IssueSeverity {
|
||||||
|
/**
|
||||||
|
* Suggestions are issues that suggest things the user can do to improve
|
||||||
|
* their experience with Notea.
|
||||||
|
*/
|
||||||
|
SUGGESTION = 0,
|
||||||
|
/**
|
||||||
|
* Warnings are issues that aren't severe enough to cause major issues by
|
||||||
|
* themselves, but that *could* cause worse issues.
|
||||||
|
*/
|
||||||
|
WARNING = 1,
|
||||||
|
/**
|
||||||
|
* Errors are issues that are severe enough to cause major issues by themselves.
|
||||||
|
* They don't necessarily cause the entire instance to stop working though.
|
||||||
|
*/
|
||||||
|
ERROR = 2,
|
||||||
|
/**
|
||||||
|
* Fatal errors are issues that must be resolved before Notea starts working.
|
||||||
|
*/
|
||||||
|
FATAL_ERROR = 3
|
||||||
|
}
|
||||||
|
export function getNameFromSeverity(severity: IssueSeverity, { t }: I18nContextProps) {
|
||||||
|
switch (severity) {
|
||||||
|
case IssueSeverity.SUGGESTION:
|
||||||
|
return t('Suggestion');
|
||||||
|
case IssueSeverity.WARNING:
|
||||||
|
return t('Warning');
|
||||||
|
case IssueSeverity.ERROR:
|
||||||
|
return t('Error');
|
||||||
|
case IssueSeverity.FATAL_ERROR:
|
||||||
|
return t('Fatal error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum IssueFixRecommendation {
|
||||||
|
NEUTRAL,
|
||||||
|
RECOMMENDED,
|
||||||
|
NOT_ADVISED
|
||||||
|
}
|
||||||
|
export function getNameFromRecommendation(recommendation: IssueFixRecommendation, { t }: I18nContextProps) {
|
||||||
|
switch (recommendation) {
|
||||||
|
case IssueFixRecommendation.NEUTRAL:
|
||||||
|
return t('Neutral');
|
||||||
|
case IssueFixRecommendation.RECOMMENDED:
|
||||||
|
return t('Recommended');
|
||||||
|
case IssueFixRecommendation.NOT_ADVISED:
|
||||||
|
return t('Not advised');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IssueFix {
|
||||||
|
recommendation: IssueFixRecommendation;
|
||||||
|
description: string;
|
||||||
|
steps?: Array<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Issue {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
category: IssueCategory;
|
||||||
|
severity: IssueSeverity;
|
||||||
|
fixes: Array<IssueFix>;
|
||||||
|
cause?: string;
|
||||||
|
isRuntime?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LogLike {
|
||||||
|
name: string;
|
||||||
|
msg: string;
|
||||||
|
pid: number;
|
||||||
|
time: number;
|
||||||
|
level: number;
|
||||||
|
}
|
||||||
|
export interface DebugInformation {
|
||||||
|
issues: Array<Issue>;
|
||||||
|
logs: Array<LogLike>; // TODO: Logging
|
||||||
|
}
|
||||||
|
|
||||||
|
export function logLevelToString(level: number) {
|
||||||
|
return pino.levels.labels[level];
|
||||||
|
}
|
6
libs/shared/util.ts
Normal file
6
libs/shared/util.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export function isProbablyError(e: any): e is Error {
|
||||||
|
if (!e) return false;
|
||||||
|
if (e instanceof Error) return true;
|
||||||
|
|
||||||
|
return e && e.stack && e.message;
|
||||||
|
}
|
@ -10,7 +10,7 @@ const i18n = rosetta<Record<string, string>>();
|
|||||||
export const defaultLanguage = DEFAULT_SETTINGS.locale;
|
export const defaultLanguage = DEFAULT_SETTINGS.locale;
|
||||||
export const languages = values(Locale);
|
export const languages = values(Locale);
|
||||||
|
|
||||||
interface ContextProps {
|
export interface ContextProps {
|
||||||
activeLocale: Locale;
|
activeLocale: Locale;
|
||||||
t: Rosetta<Record<string, string>>['t'];
|
t: Rosetta<Record<string, string>>['t'];
|
||||||
locale: (l: Locale, dict: Record<string, string>) => void;
|
locale: (l: Locale, dict: Record<string, string>) => void;
|
||||||
|
@ -63,6 +63,8 @@
|
|||||||
"next-themes": "^0.0.14",
|
"next-themes": "^0.0.14",
|
||||||
"notistack": "^1.0.7",
|
"notistack": "^1.0.7",
|
||||||
"outline-icons": "^1.27.0",
|
"outline-icons": "^1.27.0",
|
||||||
|
"pino": "^8.7.0",
|
||||||
|
"pino-pretty": "^9.1.1",
|
||||||
"prosemirror-inputrules": "^1.1.3",
|
"prosemirror-inputrules": "^1.1.3",
|
||||||
"pupa": "^2.1.1",
|
"pupa": "^2.1.1",
|
||||||
"qss": "^2.0.3",
|
"qss": "^2.0.3",
|
||||||
|
@ -6,15 +6,32 @@ import { formatSettings, Settings } from 'libs/shared/settings';
|
|||||||
import { isEqual } from 'lodash';
|
import { isEqual } from 'lodash';
|
||||||
import { tryJSON } from 'libs/shared/str';
|
import { tryJSON } from 'libs/shared/str';
|
||||||
import { StoreProvider } from 'libs/server/store';
|
import { StoreProvider } from 'libs/server/store';
|
||||||
|
import { IssueCategory, IssueFixRecommendation, IssueSeverity, reportRuntimeIssue } from 'libs/server/debugging';
|
||||||
|
|
||||||
export async function getSettings(store: StoreProvider): Promise<Settings> {
|
export async function getSettings(store: StoreProvider): Promise<Settings> {
|
||||||
const settingsPath = getPathSettings();
|
const settingsPath = getPathSettings();
|
||||||
let settings;
|
let settings;
|
||||||
|
try {
|
||||||
if (await store.hasObject(settingsPath)) {
|
if (await store.hasObject(settingsPath)) {
|
||||||
settings = tryJSON<Settings>(
|
settings = tryJSON<Settings>(
|
||||||
await store.getObject(settingsPath)
|
await store.getObject(settingsPath)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
reportRuntimeIssue({
|
||||||
|
category: IssueCategory.STORE,
|
||||||
|
severity: IssueSeverity.FATAL_ERROR,
|
||||||
|
name: "Could not get settings",
|
||||||
|
cause: String(e),
|
||||||
|
fixes: [
|
||||||
|
{
|
||||||
|
description: "Make sure Notea can connect to the store.",
|
||||||
|
recommendation: IssueFixRecommendation.RECOMMENDED
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
const formatted = formatSettings(settings || {});
|
const formatted = formatSettings(settings || {});
|
||||||
|
|
||||||
if (!settings || !isEqual(settings, formatted)) {
|
if (!settings || !isEqual(settings, formatted)) {
|
||||||
|
91
pages/debug.tsx
Normal file
91
pages/debug.tsx
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import { ServerProps, ssr, SSRContext } from 'libs/server/connect';
|
||||||
|
import { applyMisconfiguration } from 'libs/server/middlewares/misconfiguration';
|
||||||
|
import pkg from 'package.json';
|
||||||
|
import { IssueList } from 'components/debug/issue-list';
|
||||||
|
import { DebugInfoCopyButton } from 'components/debug/debug-info-copy-button';
|
||||||
|
import { DebugInformation, IssueSeverity } from 'libs/shared/debugging';
|
||||||
|
import { Logs } from 'components/debug/logs';
|
||||||
|
|
||||||
|
export function DebugPage({ debugInformation }: ServerProps) {
|
||||||
|
if (!debugInformation) throw new Error('Missing debug information');
|
||||||
|
|
||||||
|
const issues = debugInformation.issues;
|
||||||
|
const logs = debugInformation.logs ?? [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="h-screen flex flex-col">
|
||||||
|
<main className="flex flex-col space-y-4 mx-auto my-auto">
|
||||||
|
<h1 className="text-4xl font-bold my-auto mx-auto">Backup debugging page</h1>
|
||||||
|
<div className="flex flex-row my-auto mx-auto">
|
||||||
|
<DebugInfoCopyButton debugInfo={debugInformation} />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row space-x-5 my-auto">
|
||||||
|
{issues.length > 0 && (
|
||||||
|
<div className={"flex flex-col"}>
|
||||||
|
<h2 className={"text-2xl"}>Issues</h2>
|
||||||
|
<IssueList
|
||||||
|
issues={debugInformation.issues}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{logs.length > 0 && (
|
||||||
|
<div>
|
||||||
|
<h2 className={"text-2xl"}>Logs</h2>
|
||||||
|
<Logs logs={logs}/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{issues.length < 1 && logs.length < 1 && (
|
||||||
|
<div className={"mx-auto text-lg"}>
|
||||||
|
No debug information available.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
<footer className="flex flex-col my-auto mx-auto">
|
||||||
|
<div className="mx-auto">
|
||||||
|
<a
|
||||||
|
href="https://github.com/notea-org/notea"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Notea v{pkg.version}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="space-x-1">
|
||||||
|
<span>MIT ©</span>
|
||||||
|
<a
|
||||||
|
href="https://github.com/notea-org/notea"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Notea Contributors
|
||||||
|
</a>
|
||||||
|
<span>2022</span>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DebugPage;
|
||||||
|
|
||||||
|
export const getServerSideProps = async (ctx: SSRContext) => {
|
||||||
|
await ssr().use(applyMisconfiguration).run(ctx.req, ctx.res);
|
||||||
|
|
||||||
|
// has to be cast to non-null
|
||||||
|
const debugInformation = ctx.req.props.debugInformation as DebugInformation;
|
||||||
|
|
||||||
|
let redirect;
|
||||||
|
if (!debugInformation.issues.some((v) => v.severity === IssueSeverity.FATAL_ERROR)) {
|
||||||
|
redirect = {
|
||||||
|
destination: '/',
|
||||||
|
permanent: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
redirect,
|
||||||
|
props: ctx.req.props,
|
||||||
|
};
|
||||||
|
};
|
@ -11,8 +11,10 @@ import { applyCsrf } from 'libs/server/middlewares/csrf';
|
|||||||
import { SettingFooter } from 'components/settings/setting-footer';
|
import { SettingFooter } from 'components/settings/setting-footer';
|
||||||
import { SSRContext, ssr } from 'libs/server/connect';
|
import { SSRContext, ssr } from 'libs/server/connect';
|
||||||
import { applyReset } from 'libs/server/middlewares/reset';
|
import { applyReset } from 'libs/server/middlewares/reset';
|
||||||
|
import { applyMisconfiguration } from 'libs/server/middlewares/misconfiguration';
|
||||||
|
import { DebugInformation } from 'libs/shared/debugging';
|
||||||
|
|
||||||
const SettingsPage: NextPage<{ tree: TreeModel }> = ({ tree }) => {
|
const SettingsPage: NextPage<{ debugInformation: DebugInformation, tree: TreeModel }> = ({ tree, debugInformation }) => {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -23,7 +25,7 @@ const SettingsPage: NextPage<{ tree: TreeModel }> = ({ tree }) => {
|
|||||||
<span>{t('Settings')}</span>
|
<span>{t('Settings')}</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<SettingsContainer />
|
<SettingsContainer debugInfo={debugInformation} />
|
||||||
<SettingFooter />
|
<SettingFooter />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@ -41,6 +43,7 @@ export const getServerSideProps = async (ctx: SSRContext) => {
|
|||||||
.use(applySettings)
|
.use(applySettings)
|
||||||
.use(applyCsrf)
|
.use(applyCsrf)
|
||||||
.use(applyUA)
|
.use(applyUA)
|
||||||
|
.use(applyMisconfiguration)
|
||||||
.run(ctx.req, ctx.res);
|
.run(ctx.req, ctx.res);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -9,6 +9,8 @@ module.exports = {
|
|||||||
colors: {
|
colors: {
|
||||||
gray: colors.gray,
|
gray: colors.gray,
|
||||||
blue: colors.blue,
|
blue: colors.blue,
|
||||||
|
red: colors.red,
|
||||||
|
yellow: colors.yellow,
|
||||||
transparent: 'transparent',
|
transparent: 'transparent',
|
||||||
current: 'currentColor',
|
current: 'currentColor',
|
||||||
},
|
},
|
||||||
@ -26,6 +28,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
fontFamily: {
|
fontFamily: {
|
||||||
sans: ['Noto Sans'].concat(defaultConfig.theme.fontFamily['sans']),
|
sans: ['Noto Sans'].concat(defaultConfig.theme.fontFamily['sans']),
|
||||||
|
mono: defaultConfig.theme.fontFamily['mono']
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
variants: {
|
variants: {
|
||||||
|
145
yarn.lock
145
yarn.lock
@ -3225,6 +3225,11 @@ at-least-node@^1.0.0:
|
|||||||
resolved "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz"
|
resolved "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz"
|
||||||
integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
|
integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
|
||||||
|
|
||||||
|
atomic-sleep@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b"
|
||||||
|
integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==
|
||||||
|
|
||||||
autoprefixer@^10.2.4:
|
autoprefixer@^10.2.4:
|
||||||
version "10.4.12"
|
version "10.4.12"
|
||||||
resolved "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.12.tgz"
|
resolved "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.12.tgz"
|
||||||
@ -3817,7 +3822,7 @@ color@^4.0.1:
|
|||||||
color-convert "^2.0.1"
|
color-convert "^2.0.1"
|
||||||
color-string "^1.9.0"
|
color-string "^1.9.0"
|
||||||
|
|
||||||
colorette@^2.0.16:
|
colorette@^2.0.16, colorette@^2.0.7:
|
||||||
version "2.0.19"
|
version "2.0.19"
|
||||||
resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz"
|
resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz"
|
||||||
integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==
|
integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==
|
||||||
@ -4117,6 +4122,11 @@ data-urls@^2.0.0:
|
|||||||
whatwg-mimetype "^2.3.0"
|
whatwg-mimetype "^2.3.0"
|
||||||
whatwg-url "^8.0.0"
|
whatwg-url "^8.0.0"
|
||||||
|
|
||||||
|
dateformat@^4.6.3:
|
||||||
|
version "4.6.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.6.3.tgz#556fa6497e5217fedb78821424f8a1c22fa3f4b5"
|
||||||
|
integrity sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==
|
||||||
|
|
||||||
dayjs@^1.10.4:
|
dayjs@^1.10.4:
|
||||||
version "1.11.5"
|
version "1.11.5"
|
||||||
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.5.tgz"
|
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.5.tgz"
|
||||||
@ -4846,6 +4856,11 @@ expect@^27.5.1:
|
|||||||
jest-matcher-utils "^27.5.1"
|
jest-matcher-utils "^27.5.1"
|
||||||
jest-message-util "^27.5.1"
|
jest-message-util "^27.5.1"
|
||||||
|
|
||||||
|
fast-copy@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-3.0.0.tgz#875ebf33b13948ae012b6e51d33da5e6e7571ab8"
|
||||||
|
integrity sha512-4HzS+9pQ5Yxtv13Lhs1Z1unMXamBdn5nA4bEi1abYpDNSpSp7ODYQ1KPMF6nTatfEzgH6/zPvXKU1zvHiUjWlA==
|
||||||
|
|
||||||
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
||||||
version "3.1.3"
|
version "3.1.3"
|
||||||
resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
|
resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
|
||||||
@ -4872,6 +4887,11 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6:
|
|||||||
resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz"
|
resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz"
|
||||||
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
|
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
|
||||||
|
|
||||||
|
fast-redact@^3.1.1:
|
||||||
|
version "3.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.1.2.tgz#d58e69e9084ce9fa4c1a6fa98a3e1ecf5d7839aa"
|
||||||
|
integrity sha512-+0em+Iya9fKGfEQGcd62Yv6onjBmmhV1uh86XVfOU8VwAe6kaFdQCWI9s0/Nnugx5Vd9tdbZ7e6gE2tR9dzXdw==
|
||||||
|
|
||||||
fast-safe-stringify@^2.1.1:
|
fast-safe-stringify@^2.1.1:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz"
|
resolved "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz"
|
||||||
@ -5182,6 +5202,17 @@ glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, gl
|
|||||||
once "^1.3.0"
|
once "^1.3.0"
|
||||||
path-is-absolute "^1.0.0"
|
path-is-absolute "^1.0.0"
|
||||||
|
|
||||||
|
glob@^8.0.0:
|
||||||
|
version "8.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/glob/-/glob-8.0.3.tgz#415c6eb2deed9e502c68fa44a272e6da6eeca42e"
|
||||||
|
integrity sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==
|
||||||
|
dependencies:
|
||||||
|
fs.realpath "^1.0.0"
|
||||||
|
inflight "^1.0.4"
|
||||||
|
inherits "2"
|
||||||
|
minimatch "^5.0.1"
|
||||||
|
once "^1.3.0"
|
||||||
|
|
||||||
globals@^11.1.0:
|
globals@^11.1.0:
|
||||||
version "11.12.0"
|
version "11.12.0"
|
||||||
resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz"
|
resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz"
|
||||||
@ -5301,6 +5332,14 @@ he@^1.2.0:
|
|||||||
resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz"
|
resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz"
|
||||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||||
|
|
||||||
|
help-me@^4.0.1:
|
||||||
|
version "4.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/help-me/-/help-me-4.1.0.tgz#c105e78ba490d6fcaa61a3d0cd06e0054554efab"
|
||||||
|
integrity sha512-5HMrkOks2j8Fpu2j5nTLhrBhT7VwHwELpqnSnx802ckofys5MO2SkLpgSz3dgNFHV7IYFX2igm5CM75SmuYidw==
|
||||||
|
dependencies:
|
||||||
|
glob "^8.0.0"
|
||||||
|
readable-stream "^3.6.0"
|
||||||
|
|
||||||
hex-color-regex@^1.1.0:
|
hex-color-regex@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz"
|
resolved "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz"
|
||||||
@ -6283,6 +6322,11 @@ jest@^27.1.0:
|
|||||||
import-local "^3.0.2"
|
import-local "^3.0.2"
|
||||||
jest-cli "^27.5.1"
|
jest-cli "^27.5.1"
|
||||||
|
|
||||||
|
joycon@^3.1.1:
|
||||||
|
version "3.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03"
|
||||||
|
integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==
|
||||||
|
|
||||||
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
|
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
|
||||||
@ -7191,6 +7235,11 @@ object.values@^1.1.5:
|
|||||||
define-properties "^1.1.3"
|
define-properties "^1.1.3"
|
||||||
es-abstract "^1.19.1"
|
es-abstract "^1.19.1"
|
||||||
|
|
||||||
|
on-exit-leak-free@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz#5c703c968f7e7f851885f6459bf8a8a57edc9cc4"
|
||||||
|
integrity sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==
|
||||||
|
|
||||||
once@1.4.0, once@^1.3.0, once@^1.3.1, once@^1.4.0:
|
once@1.4.0, once@^1.3.0, once@^1.3.1, once@^1.4.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz"
|
resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz"
|
||||||
@ -7445,6 +7494,56 @@ pinkie@^2.0.0:
|
|||||||
resolved "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz"
|
resolved "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz"
|
||||||
integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==
|
integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==
|
||||||
|
|
||||||
|
pino-abstract-transport@^1.0.0, pino-abstract-transport@v1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz#cc0d6955fffcadb91b7b49ef220a6cc111d48bb3"
|
||||||
|
integrity sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==
|
||||||
|
dependencies:
|
||||||
|
readable-stream "^4.0.0"
|
||||||
|
split2 "^4.0.0"
|
||||||
|
|
||||||
|
pino-pretty@^9.1.1:
|
||||||
|
version "9.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/pino-pretty/-/pino-pretty-9.1.1.tgz#e7d64c1db98266ca428ab56567b844ba780cd0e1"
|
||||||
|
integrity sha512-iJrnjgR4FWQIXZkUF48oNgoRI9BpyMhaEmihonHeCnZ6F50ZHAS4YGfGBT/ZVNsPmd+hzkIPGzjKdY08+/yAXw==
|
||||||
|
dependencies:
|
||||||
|
colorette "^2.0.7"
|
||||||
|
dateformat "^4.6.3"
|
||||||
|
fast-copy "^3.0.0"
|
||||||
|
fast-safe-stringify "^2.1.1"
|
||||||
|
help-me "^4.0.1"
|
||||||
|
joycon "^3.1.1"
|
||||||
|
minimist "^1.2.6"
|
||||||
|
on-exit-leak-free "^2.1.0"
|
||||||
|
pino-abstract-transport "^1.0.0"
|
||||||
|
pump "^3.0.0"
|
||||||
|
readable-stream "^4.0.0"
|
||||||
|
secure-json-parse "^2.4.0"
|
||||||
|
sonic-boom "^3.0.0"
|
||||||
|
strip-json-comments "^3.1.1"
|
||||||
|
|
||||||
|
pino-std-serializers@^6.0.0:
|
||||||
|
version "6.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-6.0.0.tgz#4c20928a1bafca122fdc2a7a4a171ca1c5f9c526"
|
||||||
|
integrity sha512-mMMOwSKrmyl+Y12Ri2xhH1lbzQxwwpuru9VjyJpgFIH4asSj88F2csdMwN6+M5g1Ll4rmsYghHLQJw81tgZ7LQ==
|
||||||
|
|
||||||
|
pino@^8.7.0:
|
||||||
|
version "8.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/pino/-/pino-8.7.0.tgz#58621608a3d8540ae643cdd9194cdd94130c78d9"
|
||||||
|
integrity sha512-l9sA5uPxmZzwydhMWUcm1gI0YxNnYl8MfSr2h8cwLvOAzQLBLewzF247h/vqHe3/tt6fgtXeG9wdjjoetdI/vA==
|
||||||
|
dependencies:
|
||||||
|
atomic-sleep "^1.0.0"
|
||||||
|
fast-redact "^3.1.1"
|
||||||
|
on-exit-leak-free "^2.1.0"
|
||||||
|
pino-abstract-transport v1.0.0
|
||||||
|
pino-std-serializers "^6.0.0"
|
||||||
|
process-warning "^2.0.0"
|
||||||
|
quick-format-unescaped "^4.0.3"
|
||||||
|
real-require "^0.2.0"
|
||||||
|
safe-stable-stringify "^2.3.1"
|
||||||
|
sonic-boom "^3.1.0"
|
||||||
|
thread-stream "^2.0.0"
|
||||||
|
|
||||||
pirates@^4.0.4, pirates@^4.0.5:
|
pirates@^4.0.4, pirates@^4.0.5:
|
||||||
version "4.0.5"
|
version "4.0.5"
|
||||||
resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz"
|
resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz"
|
||||||
@ -7574,6 +7673,11 @@ prismjs@~1.27.0:
|
|||||||
resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz"
|
resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz"
|
||||||
integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==
|
integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==
|
||||||
|
|
||||||
|
process-warning@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-2.0.0.tgz#341dbeaac985b90a04ebcd844d50097c7737b2ee"
|
||||||
|
integrity sha512-+MmoAXoUX+VTHAlwns0h+kFUWFs/3FZy+ZuchkgjyOu3oioLAo2LB5aCfKPh2+P9O18i3m43tUEv3YqttSy0Ww==
|
||||||
|
|
||||||
process@^0.11.10:
|
process@^0.11.10:
|
||||||
version "0.11.10"
|
version "0.11.10"
|
||||||
resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz"
|
resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz"
|
||||||
@ -7809,6 +7913,11 @@ queue-microtask@^1.2.2:
|
|||||||
resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
|
resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
|
||||||
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
||||||
|
|
||||||
|
quick-format-unescaped@^4.0.3:
|
||||||
|
version "4.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7"
|
||||||
|
integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==
|
||||||
|
|
||||||
quick-lru@^5.1.1:
|
quick-lru@^5.1.1:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz"
|
resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz"
|
||||||
@ -7978,6 +8087,11 @@ readdirp@~3.6.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
picomatch "^2.2.1"
|
picomatch "^2.2.1"
|
||||||
|
|
||||||
|
real-require@^0.2.0:
|
||||||
|
version "0.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78"
|
||||||
|
integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==
|
||||||
|
|
||||||
reduce-css-calc@^2.1.8:
|
reduce-css-calc@^2.1.8:
|
||||||
version "2.1.8"
|
version "2.1.8"
|
||||||
resolved "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz"
|
resolved "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz"
|
||||||
@ -8252,6 +8366,11 @@ safe-regex-test@^1.0.0:
|
|||||||
get-intrinsic "^1.1.3"
|
get-intrinsic "^1.1.3"
|
||||||
is-regex "^1.1.4"
|
is-regex "^1.1.4"
|
||||||
|
|
||||||
|
safe-stable-stringify@^2.3.1:
|
||||||
|
version "2.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.1.tgz#34694bd8a30575b7f94792aa51527551bd733d61"
|
||||||
|
integrity sha512-dVHE6bMtS/bnL2mwualjc6IxEv1F+OCUpA46pKUj6F8uDbUM0jCCulPqRNPSnWwGNKx5etqMjZYdXtrm5KJZGA==
|
||||||
|
|
||||||
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.1.0:
|
"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.1.0:
|
||||||
version "2.1.2"
|
version "2.1.2"
|
||||||
resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz"
|
resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz"
|
||||||
@ -8302,6 +8421,11 @@ scroll-into-view-if-needed@^2.2.28:
|
|||||||
dependencies:
|
dependencies:
|
||||||
compute-scroll-into-view "^1.0.17"
|
compute-scroll-into-view "^1.0.17"
|
||||||
|
|
||||||
|
secure-json-parse@^2.4.0:
|
||||||
|
version "2.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.5.0.tgz#f929829df2adc7ccfb53703569894d051493a6ac"
|
||||||
|
integrity sha512-ZQruFgZnIWH+WyO9t5rWt4ZEGqCKPwhiw+YbzTwpmT9elgLrLcfuyUiSnwwjUiVy9r4VM3urtbNF1xmEh9IL2w==
|
||||||
|
|
||||||
semver-compare@^1.0.0:
|
semver-compare@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz"
|
resolved "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz"
|
||||||
@ -8448,6 +8572,13 @@ smooth-scroll-into-view-if-needed@^1.1.29:
|
|||||||
dependencies:
|
dependencies:
|
||||||
scroll-into-view-if-needed "^2.2.28"
|
scroll-into-view-if-needed "^2.2.28"
|
||||||
|
|
||||||
|
sonic-boom@^3.0.0, sonic-boom@^3.1.0:
|
||||||
|
version "3.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-3.2.0.tgz#ce9f2de7557e68be2e52c8df6d9b052e7d348143"
|
||||||
|
integrity sha512-SbbZ+Kqj/XIunvIAgUZRlqd6CGQYq71tRRbXR92Za8J/R3Yh4Av+TWENiSiEgnlwckYLyP0YZQWVfyNC0dzLaA==
|
||||||
|
dependencies:
|
||||||
|
atomic-sleep "^1.0.0"
|
||||||
|
|
||||||
source-list-map@^2.0.0:
|
source-list-map@^2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz"
|
resolved "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz"
|
||||||
@ -8529,6 +8660,11 @@ split.js@^1.6.0:
|
|||||||
resolved "https://registry.npmjs.org/split.js/-/split.js-1.6.5.tgz"
|
resolved "https://registry.npmjs.org/split.js/-/split.js-1.6.5.tgz"
|
||||||
integrity sha512-mPTnGCiS/RiuTNsVhCm9De9cCAUsrNFFviRbADdKiiV+Kk8HKp/0fWu7Kr8pi3/yBmsqLFHuXGT9UUZ+CNLwFw==
|
integrity sha512-mPTnGCiS/RiuTNsVhCm9De9cCAUsrNFFviRbADdKiiV+Kk8HKp/0fWu7Kr8pi3/yBmsqLFHuXGT9UUZ+CNLwFw==
|
||||||
|
|
||||||
|
split2@^4.0.0:
|
||||||
|
version "4.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/split2/-/split2-4.1.0.tgz#101907a24370f85bb782f08adaabe4e281ecf809"
|
||||||
|
integrity sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==
|
||||||
|
|
||||||
sprintf-js@~1.0.2:
|
sprintf-js@~1.0.2:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz"
|
resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz"
|
||||||
@ -8858,6 +8994,13 @@ text-table@^0.2.0:
|
|||||||
resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
|
resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
|
||||||
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
|
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
|
||||||
|
|
||||||
|
thread-stream@^2.0.0:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.2.0.tgz#310c03a253f729094ce5d4638ef5186dfa80a9e8"
|
||||||
|
integrity sha512-rUkv4/fnb4rqy/gGy7VuqK6wE1+1DOCOWy4RMeaV69ZHMP11tQKZvZSip1yTgrKCMZzEMcCL/bKfHvSfDHx+iQ==
|
||||||
|
dependencies:
|
||||||
|
real-require "^0.2.0"
|
||||||
|
|
||||||
throat@^6.0.1:
|
throat@^6.0.1:
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz"
|
resolved "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz"
|
||||||
|
Loading…
Reference in New Issue
Block a user