feat(import): bulk tool import (CSV/JSON/YAML) for admins; NetBox-style help button
Build & Push Docker Image / build (push) Successful in 2m49s
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:
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -924,6 +924,37 @@ paths:
|
||||
items:
|
||||
$ref: "#/components/schemas/AuditLog"
|
||||
|
||||
/admin/tools/import:
|
||||
post:
|
||||
operationId: importTools
|
||||
tags: [admin]
|
||||
summary: Import tools in bulk (premium feature, admin only)
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ToolImportBody"
|
||||
responses:
|
||||
"200":
|
||||
description: Import result
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ToolImportResponse"
|
||||
"400":
|
||||
description: Validation or parse error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
"403":
|
||||
description: Premium feature required
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
|
||||
components:
|
||||
schemas:
|
||||
HealthStatus:
|
||||
@@ -1191,6 +1222,39 @@ components:
|
||||
items:
|
||||
type: string
|
||||
|
||||
ToolImportBody:
|
||||
type: object
|
||||
required: [data]
|
||||
properties:
|
||||
format:
|
||||
type: string
|
||||
enum: [auto, csv, json, yaml]
|
||||
default: auto
|
||||
delimiter:
|
||||
type: string
|
||||
enum: [auto, comma, semicolon, tab]
|
||||
default: auto
|
||||
data:
|
||||
type: string
|
||||
|
||||
ToolImportResponse:
|
||||
type: object
|
||||
required: [imported, total, errors]
|
||||
properties:
|
||||
imported:
|
||||
type: integer
|
||||
total:
|
||||
type: integer
|
||||
errors:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
row:
|
||||
type: integer
|
||||
error:
|
||||
type: string
|
||||
|
||||
ToolUpdate:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@@ -719,3 +719,25 @@ export const ListAuditLogsResponseItem = zod.object({
|
||||
export const ListAuditLogsResponse = zod.array(ListAuditLogsResponseItem)
|
||||
|
||||
|
||||
/**
|
||||
* @summary Import tools in bulk (premium feature, admin only)
|
||||
*/
|
||||
export const importToolsBodyFormatDefault = `auto`;
|
||||
export const importToolsBodyDelimiterDefault = `auto`;
|
||||
|
||||
export const ImportToolsBody = zod.object({
|
||||
"format": zod.enum(['auto', 'csv', 'json', 'yaml']).default(importToolsBodyFormatDefault),
|
||||
"delimiter": zod.enum(['auto', 'comma', 'semicolon', 'tab']).default(importToolsBodyDelimiterDefault),
|
||||
"data": zod.string()
|
||||
})
|
||||
|
||||
export const ImportToolsResponse = zod.object({
|
||||
"imported": zod.int(),
|
||||
"total": zod.int(),
|
||||
"errors": zod.array(zod.object({
|
||||
"row": zod.int().optional(),
|
||||
"error": zod.string().optional()
|
||||
}))
|
||||
})
|
||||
|
||||
|
||||
|
||||
@@ -37,6 +37,11 @@ export * from './restoreTools200';
|
||||
export * from './scoreBucket';
|
||||
export * from './setPasswordInput';
|
||||
export * from './tool';
|
||||
export * from './toolImportBody';
|
||||
export * from './toolImportBodyDelimiter';
|
||||
export * from './toolImportBodyFormat';
|
||||
export * from './toolImportResponse';
|
||||
export * from './toolImportResponseErrorsItem';
|
||||
export * from './toolInput';
|
||||
export * from './toolUpdate';
|
||||
export * from './toolWithStats';
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Generated by orval v8.23.0 🍺
|
||||
* Do not edit manually.
|
||||
* Api
|
||||
* ToolRate API — Tool listing and rating platform
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { ToolImportBodyDelimiter } from './toolImportBodyDelimiter';
|
||||
import type { ToolImportBodyFormat } from './toolImportBodyFormat';
|
||||
|
||||
export interface ToolImportBody {
|
||||
format?: ToolImportBodyFormat;
|
||||
delimiter?: ToolImportBodyDelimiter;
|
||||
data: string;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Generated by orval v8.23.0 🍺
|
||||
* Do not edit manually.
|
||||
* Api
|
||||
* ToolRate API — Tool listing and rating platform
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ToolImportBodyDelimiter = typeof ToolImportBodyDelimiter[keyof typeof ToolImportBodyDelimiter];
|
||||
|
||||
|
||||
export const ToolImportBodyDelimiter = {
|
||||
auto: 'auto',
|
||||
comma: 'comma',
|
||||
semicolon: 'semicolon',
|
||||
tab: 'tab',
|
||||
} as const;
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Generated by orval v8.23.0 🍺
|
||||
* Do not edit manually.
|
||||
* Api
|
||||
* ToolRate API — Tool listing and rating platform
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ToolImportBodyFormat = typeof ToolImportBodyFormat[keyof typeof ToolImportBodyFormat];
|
||||
|
||||
|
||||
export const ToolImportBodyFormat = {
|
||||
auto: 'auto',
|
||||
csv: 'csv',
|
||||
json: 'json',
|
||||
yaml: 'yaml',
|
||||
} as const;
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Generated by orval v8.23.0 🍺
|
||||
* Do not edit manually.
|
||||
* Api
|
||||
* ToolRate API — Tool listing and rating platform
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { ToolImportResponseErrorsItem } from './toolImportResponseErrorsItem';
|
||||
|
||||
export interface ToolImportResponse {
|
||||
imported: number;
|
||||
total: number;
|
||||
errors: ToolImportResponseErrorsItem[];
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Generated by orval v8.23.0 🍺
|
||||
* Do not edit manually.
|
||||
* Api
|
||||
* ToolRate API — Tool listing and rating platform
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ToolImportResponseErrorsItem = {
|
||||
row?: number;
|
||||
error?: string;
|
||||
};
|
||||
Reference in New Issue
Block a user