support uploading csv or txt to sudo commands and groups

This commit is contained in:
miloschwartz
2026-06-08 15:30:03 -07:00
parent 3c8fea382f
commit d294bf8534
5 changed files with 479 additions and 3 deletions

View File

@@ -0,0 +1,110 @@
export type TextImportFileType = "txt" | "csv";
export type TextImportMode = "override" | "append";
export function getTextImportFileType(file: File): TextImportFileType | null {
const extension = file.name.split(".").pop()?.toLowerCase();
if (extension === "txt" || extension === "csv") {
return extension;
}
return null;
}
export function isSupportedTextImportFile(file: File): boolean {
return getTextImportFileType(file) !== null;
}
export function readFileAsText(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (event) => {
const result = event.target?.result;
if (typeof result === "string") {
resolve(result);
return;
}
reject(new Error("Failed to read file"));
};
reader.onerror = () => reject(reader.error);
reader.readAsText(file);
});
}
function parseCsvFirstCell(row: string): string {
const trimmed = row.trim();
if (!trimmed) return "";
if (trimmed.startsWith('"')) {
let index = 1;
let cell = "";
while (index < trimmed.length) {
if (trimmed[index] === '"') {
if (trimmed[index + 1] === '"') {
cell += '"';
index += 2;
continue;
}
break;
}
cell += trimmed[index];
index += 1;
}
return cell.trim();
}
const commaIndex = trimmed.indexOf(",");
return (commaIndex === -1 ? trimmed : trimmed.slice(0, commaIndex)).trim();
}
export function parseCsvFirstColumn(
content: string,
skipHeader: boolean
): string[] {
const rows = content.split(/\r?\n/).filter((line) => line.trim());
const dataRows = skipHeader ? rows.slice(1) : rows;
return dataRows.map(parseCsvFirstCell).filter(Boolean);
}
export function parseTextFileItems({
content,
fileType,
skipHeader,
parser
}: {
content: string;
fileType: TextImportFileType;
skipHeader: boolean;
parser: (value: string | undefined) => string[];
}): string[] {
if (fileType === "csv") {
return parseCsvFirstColumn(content, skipHeader);
}
return parser(content);
}
export function parsePreviewLines(value: string): string[] {
return value
.split(/\n/)
.map((line) => line.trim())
.filter(Boolean);
}
export function applyTextImport({
currentValue,
imported,
mode,
parser
}: {
currentValue: string;
imported: string[];
mode: TextImportMode;
parser: (value: string | undefined) => string[];
}): string {
if (mode === "override") {
return imported.join("\n");
}
const existing = parser(currentValue);
return [...existing, ...imported].join("\n");
}