feat: add search server link to status page

This commit is contained in:
Christian Benincasa
2025-11-19 15:14:16 -05:00
parent fa6db00f27
commit 9ff38ce35e
8 changed files with 79 additions and 968 deletions

File diff suppressed because one or more lines are too long

View File

@@ -69,7 +69,10 @@ import { ExternalStreamDetailsFetcherFactory } from '../stream/StreamDetailsFetc
import { KEYS } from '../types/inject.ts';
import type { Path } from '../types/path.ts';
import type { Maybe } from '../types/util.ts';
import { isProgramGroupingDocument } from '../util/search.ts';
import {
isProgramGroupingDocument,
isTerminalProgramDocument,
} from '../util/search.ts';
import { ApiProgramConverters } from './ApiProgramConverters.ts';
const LookupExternalProgrammingSchema = z.object({
@@ -163,32 +166,53 @@ export const programmingApi: RouterPluginAsyncCallback = async (fastify) => {
const mediaSourceId = decodeCaseSensitiveId(program.mediaSourceId);
const mediaSource = allMediaSourcesById[mediaSourceId];
if (!mediaSource) {
logger.debug(
'Could not find media source %s in DB for program ID %s',
mediaSourceId,
program.id,
);
return;
}
const libraryId = decodeCaseSensitiveId(program.libraryId);
const library = allLibrariesById[libraryId];
if (!library) {
logger.debug(
'COuld not find media source library %s in DB for program ID %s',
libraryId,
program.id,
);
return;
}
if (isProgramGroupingDocument(program) && groupings[program.id]) {
return ApiProgramConverters.convertProgramGroupingSearchResult(
program,
groupings[program.id],
groupingCounts[program.id],
mediaSource,
library,
);
} else if (
!isProgramGroupingDocument(program) &&
programs[program.id]
) {
return ApiProgramConverters.convertProgramSearchResult(
program,
programs[program.id],
mediaSource,
library,
);
if (isProgramGroupingDocument(program)) {
if (groupings[program.id]) {
return ApiProgramConverters.convertProgramGroupingSearchResult(
program,
groupings[program.id],
groupingCounts[program.id],
mediaSource,
library,
);
} else {
logger.debug(
'Could not find program grouping %s in DB, but it exists in search index!',
program.id,
);
}
} else if (isTerminalProgramDocument(program)) {
if (programs[program.id]) {
return ApiProgramConverters.convertProgramSearchResult(
program,
programs[program.id],
mediaSource,
library,
);
} else {
logger.debug(
'Could not find program %s in DB, but it exists in search index!',
program.id,
);
}
}
return;

View File

@@ -30,6 +30,7 @@ import { z } from 'zod/v4';
import { container } from '../container.ts';
import { MigrationStateSchema } from '../db/SettingsDB.ts';
import { NvidiaGpuDetectionHelper } from '../ffmpeg/builder/capabilities/NvidiaHardwareCapabilitiesFactory.ts';
import { MeilisearchService } from '../services/MeilisearchService.ts';
import { SystemDevicesService } from '../services/SystemDevicesService.ts';
import { Result } from '../types/result.ts';
import { TruthyQueryParam } from '../types/schemas.ts';
@@ -226,6 +227,12 @@ export const systemApiRouter: RouterPluginAsyncCallback = async (
},
},
async (req, res) => {
const smiResult = Result.attemptAsync(() =>
new ChildProcessHelper().getStdout('nvidia-smi', [], {
swallowError: true,
isPath: false,
}),
);
const result = await Promise.all([
new NvidiaGpuDetectionHelper()
.getGpuFromFfmpeg(
@@ -238,14 +245,7 @@ export const systemApiRouter: RouterPluginAsyncCallback = async (
(err) => err.message,
),
),
(
await Result.attemptAsync(() =>
new ChildProcessHelper().getStdout('nvidia-smi', [], {
swallowError: true,
isPath: false,
}),
)
).either(identity, (err) => err.message),
smiResult.then((_) => _.either(identity, (err) => err.message)),
]);
return res.send(result.join('\n\n'));
@@ -391,6 +391,7 @@ export const systemApiRouter: RouterPluginAsyncCallback = async (
...(settings.logging as Writable<LoggingSettings>),
environmentLogLevel: envLogLevel,
},
searchServerAddress: `http://localhost:${container.get<MeilisearchService>(MeilisearchService).getPort()}`,
};
}
};

View File

@@ -1,940 +0,0 @@
<!-- This is a generated file -->
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
background-color: hsl(30, 20%, 95%)
}
</style>
<link rel='stylesheet' href='https://unpkg.com/chevrotain@11.0.3/diagrams/diagrams.css'>
<script src='https://unpkg.com/chevrotain@11.0.3/diagrams/vendor/railroad-diagrams.js'></script>
<script src='https://unpkg.com/chevrotain@11.0.3/diagrams/src/diagrams_builder.js'></script>
<script src='https://unpkg.com/chevrotain@11.0.3/diagrams/src/diagrams_behavior.js'></script>
<script src='https://unpkg.com/chevrotain@11.0.3/diagrams/src/main.js'></script>
<div id="diagrams" align="center"></div>
<script>
window.serializedGrammar = [
{
"type": "Rule",
"name": "searchValue",
"orgText": "",
"definition": [
{
"type": "Alternation",
"idx": 0,
"definition": [
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "Quote",
"label": "Quote",
"idx": 0,
"terminalLabel": "str_open",
"pattern": "\""
},
{
"type": "RepetitionMandatory",
"idx": 0,
"definition": [
{
"type": "Repetition",
"idx": 0,
"definition": [
{
"type": "Alternation",
"idx": 2,
"definition": [
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "Identifier",
"label": "Identifier",
"idx": 2,
"terminalLabel": "query",
"pattern": "[a-zA-Z0-9\\-]+"
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "Integer",
"label": "Integer",
"idx": 2,
"terminalLabel": "query",
"pattern": "\\d+"
}
]
}
]
}
]
}
]
},
{
"type": "Terminal",
"name": "Quote",
"label": "Quote",
"idx": 3,
"terminalLabel": "str_close",
"pattern": "\""
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "Repetition",
"idx": 2,
"definition": [
{
"type": "Alternation",
"idx": 3,
"definition": [
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "Identifier",
"label": "Identifier",
"idx": 4,
"terminalLabel": "query",
"pattern": "[a-zA-Z0-9\\-]+"
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "Integer",
"label": "Integer",
"idx": 4,
"terminalLabel": "query",
"pattern": "\\d+"
}
]
}
]
}
]
}
]
}
]
}
]
},
{
"type": "Rule",
"name": "parenGroup",
"orgText": "",
"definition": [
{
"type": "Terminal",
"name": "OpenParenGroup",
"label": "OpenParenGroup",
"idx": 0,
"pattern": "\\("
},
{
"type": "RepetitionMandatory",
"idx": 0,
"definition": [
{
"type": "NonTerminal",
"name": "searchClause",
"idx": 0
}
]
},
{
"type": "Terminal",
"name": "CloseParenGroup",
"label": "CloseParenGroup",
"idx": 0,
"pattern": "\\)"
}
]
},
{
"type": "Rule",
"name": "string_operator",
"orgText": "",
"definition": [
{
"type": "Alternation",
"idx": 0,
"definition": [
{
"type": "Alternative",
"definition": [
{
"type": "Alternation",
"idx": 2,
"definition": [
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "EqOperator",
"label": "EqOperator",
"idx": 0,
"pattern": ":|="
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "NeqOperator",
"label": "NeqOperator",
"idx": 0,
"pattern": "!="
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "LTEOperator",
"label": "LTEOperator",
"idx": 0,
"pattern": "<="
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "LTOperator",
"label": "LTOperator",
"idx": 0,
"pattern": "<"
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "ContainsOperator",
"label": "ContainsOperator",
"idx": 0,
"pattern": "~"
}
]
}
]
},
{
"type": "NonTerminal",
"name": "searchValue",
"idx": 0
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "InOperator",
"label": "InOperator",
"idx": 0,
"pattern": "in"
},
{
"type": "Terminal",
"name": "OpenArray",
"label": "OpenArray",
"idx": 2,
"pattern": "\\["
},
{
"type": "RepetitionMandatoryWithSeparator",
"idx": 0,
"separator": {
"type": "Terminal",
"name": "Comma",
"label": "Comma",
"idx": 1,
"pattern": ","
},
"definition": [
{
"type": "NonTerminal",
"name": "searchValue",
"idx": 2
}
]
},
{
"type": "Terminal",
"name": "CloseArray",
"label": "CloseArray",
"idx": 2,
"pattern": "]"
}
]
}
]
}
]
},
{
"type": "Rule",
"name": "numeric_operator",
"orgText": "",
"definition": [
{
"type": "Alternation",
"idx": 0,
"definition": [
{
"type": "Alternative",
"definition": [
{
"type": "Alternation",
"idx": 2,
"definition": [
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "EqOperator",
"label": "EqOperator",
"idx": 0,
"pattern": ":|="
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "NeqOperator",
"label": "NeqOperator",
"idx": 0,
"pattern": "!="
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "LTEOperator",
"label": "LTEOperator",
"idx": 0,
"pattern": "<="
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "GTEOperator",
"label": "GTEOperator",
"idx": 0,
"pattern": ">="
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "LTOperator",
"label": "LTOperator",
"idx": 0,
"pattern": "<"
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "GTOperator",
"label": "GTOperator",
"idx": 0,
"pattern": ">"
}
]
}
]
},
{
"type": "NonTerminal",
"name": "numeric_value",
"idx": 0
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "BetweenOperator",
"label": "BetweenOperator",
"idx": 0,
"pattern": "between"
},
{
"type": "Alternation",
"idx": 3,
"definition": [
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "OpenParenGroup",
"label": "OpenParenGroup",
"idx": 2,
"pattern": "\\("
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "OpenArray",
"label": "OpenArray",
"idx": 2,
"pattern": "\\["
}
]
}
]
},
{
"type": "NonTerminal",
"name": "numeric_value",
"idx": 2
},
{
"type": "Option",
"idx": 0,
"definition": [
{
"type": "Terminal",
"name": "Comma",
"label": "Comma",
"idx": 2,
"pattern": ","
}
]
},
{
"type": "NonTerminal",
"name": "numeric_value",
"idx": 3
},
{
"type": "Alternation",
"idx": 4,
"definition": [
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "CloseParenGroup",
"label": "CloseParenGroup",
"idx": 3,
"pattern": "\\)"
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "CloseArray",
"label": "CloseArray",
"idx": 3,
"pattern": "]"
}
]
}
]
}
]
}
]
}
]
},
{
"type": "Rule",
"name": "numeric_value",
"orgText": "",
"definition": [
{
"type": "Alternation",
"idx": 0,
"definition": [
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "FloatingPoint",
"label": "FloatingPoint",
"idx": 0,
"pattern": "\\d+\\.\\d+"
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "Integer",
"label": "Integer",
"idx": 0,
"pattern": "\\d+"
}
]
}
]
}
]
},
{
"type": "Rule",
"name": "date_operator",
"orgText": "",
"definition": [
{
"type": "Alternation",
"idx": 0,
"definition": [
{
"type": "Alternative",
"definition": [
{
"type": "Alternation",
"idx": 2,
"definition": [
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "EqOperator",
"label": "EqOperator",
"idx": 0,
"pattern": ":|="
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "LTEOperator",
"label": "LTEOperator",
"idx": 0,
"pattern": "<="
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "LTOperator",
"label": "LTOperator",
"idx": 0,
"pattern": "<"
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "GTEOperator",
"label": "GTEOperator",
"idx": 0,
"pattern": ">="
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "GTOperator",
"label": "GTOperator",
"idx": 0,
"pattern": ">"
}
]
}
]
},
{
"type": "NonTerminal",
"name": "searchValue",
"idx": 0
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "BetweenOperator",
"label": "BetweenOperator",
"idx": 0,
"pattern": "between"
},
{
"type": "Alternation",
"idx": 3,
"definition": [
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "OpenParenGroup",
"label": "OpenParenGroup",
"idx": 2,
"pattern": "\\("
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "OpenArray",
"label": "OpenArray",
"idx": 2,
"pattern": "\\["
}
]
}
]
},
{
"type": "NonTerminal",
"name": "searchValue",
"idx": 2
},
{
"type": "Option",
"idx": 0,
"definition": [
{
"type": "Terminal",
"name": "Comma",
"label": "Comma",
"idx": 2,
"pattern": ","
}
]
},
{
"type": "NonTerminal",
"name": "searchValue",
"idx": 3
},
{
"type": "Alternation",
"idx": 4,
"definition": [
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "CloseParenGroup",
"label": "CloseParenGroup",
"idx": 3,
"pattern": "\\)"
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "CloseArray",
"label": "CloseArray",
"idx": 3,
"pattern": "]"
}
]
}
]
}
]
}
]
}
]
},
{
"type": "Rule",
"name": "singleStringSearch",
"orgText": "",
"definition": [
{
"type": "Terminal",
"name": "StringField",
"label": "StringField",
"idx": 0,
"terminalLabel": "field",
"pattern": "actor|genre|director|writer|library_id|title|video_codec|video_dynamic_range|audio_codec|tags|rating|type"
},
{
"type": "NonTerminal",
"name": "string_operator",
"idx": 0,
"label": "op"
}
]
},
{
"type": "Rule",
"name": "singleNumericSearch",
"orgText": "",
"definition": [
{
"type": "Terminal",
"name": "NumericField",
"label": "NumericField",
"idx": 0,
"pattern": "duration|minutes|seconds|video_bit_depth|video_height|video_width|audio_channels|release_year"
},
{
"type": "NonTerminal",
"name": "numeric_operator",
"idx": 0
}
]
},
{
"type": "Rule",
"name": "singleDateSearch",
"orgText": "",
"definition": [
{
"type": "Terminal",
"name": "DateField",
"label": "DateField",
"idx": 0,
"terminalLabel": "field",
"pattern": "release_date"
},
{
"type": "NonTerminal",
"name": "date_operator",
"idx": 0,
"label": "op"
}
]
},
{
"type": "Rule",
"name": "singleSearch",
"orgText": "",
"definition": [
{
"type": "Alternation",
"idx": 0,
"definition": [
{
"type": "Alternative",
"definition": [
{
"type": "NonTerminal",
"name": "singleStringSearch",
"idx": 0
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "NonTerminal",
"name": "singleNumericSearch",
"idx": 0
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "NonTerminal",
"name": "singleDateSearch",
"idx": 0
}
]
}
]
}
]
},
{
"type": "Rule",
"name": "searchClause",
"orgText": "",
"definition": [
{
"type": "Alternation",
"idx": 0,
"definition": [
{
"type": "Alternative",
"definition": [
{
"type": "NonTerminal",
"name": "parenGroup",
"idx": 0
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "NonTerminal",
"name": "singleSearch",
"idx": 0
},
{
"type": "Option",
"idx": 0,
"definition": [
{
"type": "Alternation",
"idx": 2,
"definition": [
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "CombineOr",
"label": "CombineOr",
"idx": 0,
"pattern": "OR"
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "CombineAnd",
"label": "CombineAnd",
"idx": 0,
"pattern": "AND"
}
]
},
{
"type": "Alternative",
"definition": [
{
"type": "Terminal",
"name": "WhiteSpace",
"label": "WhiteSpace",
"idx": 0,
"pattern": "\\s+"
}
]
}
]
},
{
"type": "NonTerminal",
"name": "searchClause",
"idx": 0
}
]
}
]
}
]
}
]
},
{
"type": "Rule",
"name": "searchExpression",
"orgText": "",
"definition": [
{
"type": "RepetitionMandatory",
"idx": 0,
"definition": [
{
"type": "NonTerminal",
"name": "searchClause",
"idx": 0
}
]
}
]
}
];
</script>
<script>
var diagramsDiv = document.getElementById("diagrams");
main.drawDiagramsFromSerializedGrammar(serializedGrammar, diagramsDiv);
</script>

View File

@@ -253,6 +253,7 @@ export const SystemSettingsResponseSchema = SystemSettingsSchema.extend({
logging: LoggingSettingsSchema.extend({
environmentLogLevel: LogLevelsSchema.optional(),
}),
searchServerAddress: z.url(),
});
export type SystemSettingsResponse = z.infer<

View File

@@ -15811,6 +15811,7 @@ export type GetApiSystemSettingsResponses = {
};
};
dataDirectory: string;
searchServerAddress: string;
};
};
@@ -15904,6 +15905,7 @@ export type PutApiSystemSettingsResponses = {
};
};
dataDirectory: string;
searchServerAddress: string;
};
};

View File

@@ -242,6 +242,26 @@ export const StatusPage = () => {
</Typography>
</TableCell>
</TableRow>
<TableRow>
<TableCell>
<Typography>
<strong>Search Server URL:</strong>
</Typography>
</TableCell>
<TableCell>
<Typography>
<Link
href={systemSettings.data.searchServerAddress}
target="_blank"
>
{systemSettings.data.searchServerAddress}
</Link>
<CopyToClipboardButton
content={systemSettings.data.searchServerAddress}
/>
</Typography>
</TableCell>
</TableRow>
<TableRow hover>
<TableCell>
<Typography>

View File

@@ -22,6 +22,9 @@ export const Route = createRootRouteWithContext<RouterContext>()({
validateSearch: (searchParams) => {
const { query: searchString } = searchQuerySchema.parse(searchParams);
if (isUndefined(searchString) || isEmpty(searchString)) {
useStore.setState((s) => {
s.currentSearchRequest = null;
});
return;
}