feat(import): bulk tool import (CSV/JSON/YAML) for admins; NetBox-style help button
Build & Push Docker Image / build (push) Successful in 2m49s

- Add POST /admin/tools/import with format auto-detect, CSV delimiters
  (comma/semicolon/tab), per-row validation via CreateToolBody, bulk insert,
  audit log entries per imported tool; gated by new 'tool-import' feature
  flag (premium/enterprise; admins always pass)
- Add tool-import-dialog UI (format tabs, delimiter select, textarea, file
  upload, result/error list) behind hasFeature('tool-import')
- Replace FieldHelp question marks and bare GuideHelp links with a NetBox-style
  'Hilfe/Help' outline button (HelpCircle + text) in form headers only
- Sync locales to 482 keys per language (de/en), update handbook docs
  (administration import section, index/plaene feature tables), regenerate
  API client + zod schemas, add yaml dependency
This commit is contained in:
opencode
2026-08-04 23:09:11 +02:00
parent d47db6d386
commit 8f2fd89847
38 changed files with 870 additions and 159 deletions
@@ -219,6 +219,43 @@ export interface ToolInput {
tags?: string[];
}
export type ToolImportBodyFormat = typeof ToolImportBodyFormat[keyof typeof ToolImportBodyFormat];
export const ToolImportBodyFormat = {
auto: 'auto',
csv: 'csv',
json: 'json',
yaml: 'yaml',
} as const;
export type ToolImportBodyDelimiter = typeof ToolImportBodyDelimiter[keyof typeof ToolImportBodyDelimiter];
export const ToolImportBodyDelimiter = {
auto: 'auto',
comma: 'comma',
semicolon: 'semicolon',
tab: 'tab',
} as const;
export interface ToolImportBody {
format?: ToolImportBodyFormat;
delimiter?: ToolImportBodyDelimiter;
data: string;
}
export type ToolImportResponseErrorsItem = {
row?: number;
error?: string;
};
export interface ToolImportResponse {
imported: number;
total: number;
errors: ToolImportResponseErrorsItem[];
}
export interface ToolUpdate {
/** @minLength 1 */
name?: string;
+73
View File
@@ -45,6 +45,8 @@ import type {
RestoreTools200,
SetPasswordInput,
Tool,
ToolImportBody,
ToolImportResponse,
ToolInput,
ToolUpdate,
ToolWithStats,
@@ -2969,3 +2971,74 @@ export function useListAuditLogs<TData = Awaited<ReturnType<typeof listAuditLogs
export const getImportToolsUrl = () => {
return `/api/admin/tools/import`
}
/**
* @summary Import tools in bulk (premium feature, admin only)
*/
export const importTools = async (toolImportBody: ToolImportBody, options?: Parameters<typeof customFetch>[1]): Promise<ToolImportResponse> => {
return customFetch<ToolImportResponse>(getImportToolsUrl(),
{
...options,
method: 'POST',
headers: { 'Content-Type': 'application/json', ...options?.headers },
body: JSON.stringify(toolImportBody)
}
);}
export const getImportToolsMutationOptions = <TError = ErrorType<ErrorResponse>,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof importTools>>, TError,{data: BodyType<ToolImportBody>}, TContext>, request?: SecondParameter<typeof customFetch>}
): UseMutationOptions<Awaited<ReturnType<typeof importTools>>, TError,{data: BodyType<ToolImportBody>}, TContext> => {
const mutationKey = ['importTools'];
const {mutation: mutationOptions, request: requestOptions} = options ?
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
options
: {...options, mutation: {...options.mutation, mutationKey}}
: {mutation: { mutationKey, }, request: undefined};
const mutationFn: MutationFunction<Awaited<ReturnType<typeof importTools>>, {data: BodyType<ToolImportBody>}> = (props) => {
const {data} = props ?? {};
return importTools(data,requestOptions)
}
return { mutationFn, ...mutationOptions }}
export type ImportToolsMutationResult = NonNullable<Awaited<ReturnType<typeof importTools>>>
export type ImportToolsMutationBody = BodyType<ToolImportBody>
export type ImportToolsMutationError = ErrorType<ErrorResponse>
/**
* @summary Import tools in bulk (premium feature, admin only)
*/
export const useImportTools = <TError = ErrorType<ErrorResponse>,
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof importTools>>, TError,{data: BodyType<ToolImportBody>}, TContext>, request?: SecondParameter<typeof customFetch>}
): UseMutationResult<
Awaited<ReturnType<typeof importTools>>,
TError,
{data: BodyType<ToolImportBody>},
TContext
> => {
return useMutation(getImportToolsMutationOptions(options));
}