forked from open-webui/open-webui
feat: csv bulk import
This commit is contained in:
parent
bd3b5f1edb
commit
20a6dbbe65
2 changed files with 108 additions and 83 deletions
|
@ -253,76 +253,6 @@ async def add_user(form_data: AddUserForm, user=Depends(get_admin_user)):
|
||||||
raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err))
|
raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err))
|
||||||
|
|
||||||
|
|
||||||
@router.post("/add/import", response_model=SigninResponse)
|
|
||||||
async def add_user_csv_import(
|
|
||||||
file: UploadFile = File(...), user=Depends(get_admin_user)
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
|
|
||||||
# Check if the file is a CSV file
|
|
||||||
if file.filename.endswith(".csv"):
|
|
||||||
# Read the contents of the CSV file
|
|
||||||
contents = await file.read()
|
|
||||||
|
|
||||||
# Decode the contents from bytes to string
|
|
||||||
decoded_content = contents.decode("utf-8")
|
|
||||||
|
|
||||||
# Split the CSV content into lines
|
|
||||||
csv_lines = decoded_content.split("\n")
|
|
||||||
|
|
||||||
# Parse CSV
|
|
||||||
csv_data = []
|
|
||||||
csv_reader = csv.reader(csv_lines)
|
|
||||||
for row in csv_reader:
|
|
||||||
csv_data.append(row)
|
|
||||||
|
|
||||||
# Print the CSV data
|
|
||||||
for row in csv_data:
|
|
||||||
print(row)
|
|
||||||
|
|
||||||
return {"message": "CSV file uploaded successfully."}
|
|
||||||
else:
|
|
||||||
raise "File must be a CSV."
|
|
||||||
except Exception as err:
|
|
||||||
raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err))
|
|
||||||
|
|
||||||
# if not validate_email_format(form_data.email.lower()):
|
|
||||||
# raise HTTPException(
|
|
||||||
# status.HTTP_400_BAD_REQUEST, detail=ERROR_MESSAGES.INVALID_EMAIL_FORMAT
|
|
||||||
# )
|
|
||||||
|
|
||||||
# if Users.get_user_by_email(form_data.email.lower()):
|
|
||||||
# raise HTTPException(400, detail=ERROR_MESSAGES.EMAIL_TAKEN)
|
|
||||||
|
|
||||||
# try:
|
|
||||||
|
|
||||||
# print(form_data)
|
|
||||||
# hashed = get_password_hash(form_data.password)
|
|
||||||
# user = Auths.insert_new_auth(
|
|
||||||
# form_data.email.lower(),
|
|
||||||
# hashed,
|
|
||||||
# form_data.name,
|
|
||||||
# form_data.profile_image_url,
|
|
||||||
# form_data.role,
|
|
||||||
# )
|
|
||||||
|
|
||||||
# if user:
|
|
||||||
# token = create_token(data={"id": user.id})
|
|
||||||
# return {
|
|
||||||
# "token": token,
|
|
||||||
# "token_type": "Bearer",
|
|
||||||
# "id": user.id,
|
|
||||||
# "email": user.email,
|
|
||||||
# "name": user.name,
|
|
||||||
# "role": user.role,
|
|
||||||
# "profile_image_url": user.profile_image_url,
|
|
||||||
# }
|
|
||||||
# else:
|
|
||||||
# raise HTTPException(500, detail=ERROR_MESSAGES.CREATE_USER_ERROR)
|
|
||||||
# except Exception as err:
|
|
||||||
# raise HTTPException(500, detail=ERROR_MESSAGES.DEFAULT(err))
|
|
||||||
|
|
||||||
|
|
||||||
############################
|
############################
|
||||||
# ToggleSignUp
|
# ToggleSignUp
|
||||||
############################
|
############################
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
export let show = false;
|
export let show = false;
|
||||||
|
|
||||||
|
let loading = false;
|
||||||
let tab = '';
|
let tab = '';
|
||||||
let inputFiles;
|
let inputFiles;
|
||||||
|
|
||||||
|
@ -32,6 +33,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const submitHandler = async () => {
|
const submitHandler = async () => {
|
||||||
|
const stopLoading = () => {
|
||||||
|
dispatch('save');
|
||||||
|
loading = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (tab === '') {
|
||||||
|
loading = true;
|
||||||
|
|
||||||
const res = await addUser(
|
const res = await addUser(
|
||||||
localStorage.token,
|
localStorage.token,
|
||||||
_user.name,
|
_user.name,
|
||||||
|
@ -43,9 +52,64 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
dispatch('save');
|
stopLoading();
|
||||||
show = false;
|
show = false;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (inputFiles) {
|
||||||
|
loading = true;
|
||||||
|
|
||||||
|
const file = inputFiles[0];
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = async (e) => {
|
||||||
|
const csv = e.target.result;
|
||||||
|
const rows = csv.split('\n');
|
||||||
|
|
||||||
|
let userCount = 0;
|
||||||
|
|
||||||
|
for (const [idx, row] of rows.entries()) {
|
||||||
|
const columns = row.split(',').map((col) => col.trim());
|
||||||
|
console.log(idx, columns);
|
||||||
|
|
||||||
|
if (idx > 0) {
|
||||||
|
if (columns.length === 4 && ['admin', 'user', 'pending'].includes(columns[3])) {
|
||||||
|
const res = await addUser(
|
||||||
|
localStorage.token,
|
||||||
|
columns[0],
|
||||||
|
columns[1],
|
||||||
|
columns[2],
|
||||||
|
columns[3]
|
||||||
|
).catch((error) => {
|
||||||
|
toast.error(`Row ${idx + 1}: ${error}`);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res) {
|
||||||
|
userCount = userCount + 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toast.error(`Row ${idx + 1}: invalid format.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success(`Successfully imported ${userCount} users`);
|
||||||
|
inputFiles = null;
|
||||||
|
const uploadInputElement = document.getElementById('upload-user-csv-input');
|
||||||
|
|
||||||
|
if (uploadInputElement) {
|
||||||
|
uploadInputElement.value = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
stopLoading();
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsText(file);
|
||||||
|
} else {
|
||||||
|
toast.error(`File not found.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -204,10 +268,41 @@
|
||||||
|
|
||||||
<div class="flex justify-end pt-3 text-sm font-medium">
|
<div class="flex justify-end pt-3 text-sm font-medium">
|
||||||
<button
|
<button
|
||||||
class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg"
|
class=" px-4 py-2 bg-emerald-700 hover:bg-emerald-800 text-gray-100 transition rounded-lg flex flex-row space-x-1 items-center {loading
|
||||||
|
? ' cursor-not-allowed'
|
||||||
|
: ''}"
|
||||||
type="submit"
|
type="submit"
|
||||||
|
disabled={loading}
|
||||||
>
|
>
|
||||||
{$i18n.t('Submit')}
|
{$i18n.t('Submit')}
|
||||||
|
|
||||||
|
{#if loading}
|
||||||
|
<div class="ml-2 self-center">
|
||||||
|
<svg
|
||||||
|
class=" w-4 h-4"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
><style>
|
||||||
|
.spinner_ajPY {
|
||||||
|
transform-origin: center;
|
||||||
|
animation: spinner_AtaB 0.75s infinite linear;
|
||||||
|
}
|
||||||
|
@keyframes spinner_AtaB {
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style><path
|
||||||
|
d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z"
|
||||||
|
opacity=".25"
|
||||||
|
/><path
|
||||||
|
d="M10.14,1.16a11,11,0,0,0-9,8.92A1.59,1.59,0,0,0,2.46,12,1.52,1.52,0,0,0,4.11,10.7a8,8,0,0,1,6.66-6.61A1.42,1.42,0,0,0,12,2.69h0A1.57,1.57,0,0,0,10.14,1.16Z"
|
||||||
|
class="spinner_ajPY"
|
||||||
|
/></svg
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
Loading…
Reference in a new issue