feat: import/export config

This commit is contained in:
Timothy J. Baek 2024-09-03 21:16:07 +02:00
parent aec7cd572c
commit 6bbb755997
3 changed files with 200 additions and 34 deletions

View File

@ -3,9 +3,37 @@ from fastapi import APIRouter, Depends, Request
from pydantic import BaseModel from pydantic import BaseModel
from utils.utils import get_admin_user, get_verified_user from utils.utils import get_admin_user, get_verified_user
from config import get_config, save_config
router = APIRouter() router = APIRouter()
############################
# ImportConfig
############################
class ImportConfigForm(BaseModel):
config: dict
@router.post("/import", response_model=dict)
async def import_config(form_data: ImportConfigForm, user=Depends(get_admin_user)):
save_config(form_data.config)
return get_config()
############################
# ExportConfig
############################
@router.get("/export", response_model=dict)
async def export_config(user=Depends(get_admin_user)):
return get_config()
class SetDefaultModelsForm(BaseModel): class SetDefaultModelsForm(BaseModel):
models: str models: str

View File

@ -1,6 +1,63 @@
import { WEBUI_API_BASE_URL } from '$lib/constants'; import { WEBUI_API_BASE_URL } from '$lib/constants';
import type { Banner } from '$lib/types'; import type { Banner } from '$lib/types';
export const importConfig = async (token: string, config) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/configs/import`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
body: JSON.stringify({
config: config
})
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
console.log(err);
error = err.detail;
return null;
});
if (error) {
throw error;
}
return res;
};
export const exportConfig = async (token: string) => {
let error = null;
const res = await fetch(`${WEBUI_API_BASE_URL}/configs/export`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
}
})
.then(async (res) => {
if (!res.ok) throw await res.json();
return res.json();
})
.catch((err) => {
console.log(err);
error = err.detail;
return null;
});
if (error) {
throw error;
}
return res;
};
export const setDefaultModels = async (token: string, models: string) => { export const setDefaultModels = async (token: string, models: string) => {
let error = null; let error = null;

View File

@ -7,6 +7,7 @@
import { config, user } from '$lib/stores'; import { config, user } from '$lib/stores';
import { toast } from 'svelte-sonner'; import { toast } from 'svelte-sonner';
import { getAllUserChats } from '$lib/apis/chats'; import { getAllUserChats } from '$lib/apis/chats';
import { exportConfig, importConfig } from '$lib/apis/configs';
const i18n = getContext('i18n'); const i18n = getContext('i18n');
@ -34,6 +35,92 @@
<div> <div>
<div class=" mb-2 text-sm font-medium">{$i18n.t('Database')}</div> <div class=" mb-2 text-sm font-medium">{$i18n.t('Database')}</div>
<input
id="config-json-input"
hidden
type="file"
accept=".json"
on:change={(e) => {
const file = e.target.files[0];
const reader = new FileReader();
reader.onload = async (e) => {
const res = await importConfig(localStorage.token, JSON.parse(e.target.result)).catch(
(error) => {
toast.error(error);
}
);
if (res) {
toast.success('Config imported successfully');
}
e.target.value = null;
};
reader.readAsText(file);
}}
/>
<button
type="button"
class=" flex rounded-md py-2 px-3 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
on:click={async () => {
document.getElementById('config-json-input').click();
}}
>
<div class=" self-center mr-3">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
<path
fill-rule="evenodd"
d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM8.75 7.75a.75.75 0 0 0-1.5 0v2.69L6.03 9.22a.75.75 0 0 0-1.06 1.06l2.5 2.5a.75.75 0 0 0 1.06 0l2.5-2.5a.75.75 0 1 0-1.06-1.06l-1.22 1.22V7.75Z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class=" self-center text-sm font-medium">
{$i18n.t('Import Config from JSON File')}
</div>
</button>
<button
type="button"
class=" flex rounded-md py-2 px-3 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
on:click={async () => {
const config = await exportConfig(localStorage.token);
const blob = new Blob([JSON.stringify(config)], {
type: 'application/json'
});
saveAs(blob, `config-${Date.now()}.json`);
}}
>
<div class=" self-center mr-3">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4"
>
<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
<path
fill-rule="evenodd"
d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM8.75 7.75a.75.75 0 0 0-1.5 0v2.69L6.03 9.22a.75.75 0 0 0-1.06 1.06l2.5 2.5a.75.75 0 0 0 1.06 0l2.5-2.5a.75.75 0 1 0-1.06-1.06l-1.22 1.22V7.75Z"
clip-rule="evenodd"
/>
</svg>
</div>
<div class=" self-center text-sm font-medium">
{$i18n.t('Export Config to JSON File')}
</div>
</button>
<hr class=" dark:border-gray-850 my-1" />
{#if $config?.features.enable_admin_export ?? true} {#if $config?.features.enable_admin_export ?? true}
<div class=" flex w-full justify-between"> <div class=" flex w-full justify-between">
<!-- <div class=" self-center text-xs font-medium">{$i18n.t('Allow Chat Deletion')}</div> --> <!-- <div class=" self-center text-xs font-medium">{$i18n.t('Allow Chat Deletion')}</div> -->
@ -97,12 +184,9 @@
<hr class=" dark:border-gray-850 my-1" /> <hr class=" dark:border-gray-850 my-1" />
<div class=" flex w-full justify-between">
<!-- <div class=" self-center text-xs font-medium">{$i18n.t('Allow Chat Deletion')}</div> -->
<button <button
class=" flex rounded-md py-1.5 px-3 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
type="button" type="button"
class=" flex rounded-md py-2 px-3 w-full hover:bg-gray-200 dark:hover:bg-gray-800 transition"
on:click={() => { on:click={() => {
downloadLiteLLMConfig(localStorage.token).catch((error) => { downloadLiteLLMConfig(localStorage.token).catch((error) => {
toast.error(error); toast.error(error);
@ -112,18 +196,16 @@
<div class=" self-center mr-3"> <div class=" self-center mr-3">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24" viewBox="0 0 16 16"
fill="currentColor" fill="currentColor"
class="size-4" class="w-4 h-4"
> >
<path d="M2 3a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3Z" />
<path <path
fill-rule="evenodd" fill-rule="evenodd"
d="M5.625 1.5H9a3.75 3.75 0 0 1 3.75 3.75v1.875c0 1.036.84 1.875 1.875 1.875H16.5a3.75 3.75 0 0 1 3.75 3.75v7.875c0 1.035-.84 1.875-1.875 1.875H5.625a1.875 1.875 0 0 1-1.875-1.875V3.375c0-1.036.84-1.875 1.875-1.875Zm5.845 17.03a.75.75 0 0 0 1.06 0l3-3a.75.75 0 1 0-1.06-1.06l-1.72 1.72V12a.75.75 0 0 0-1.5 0v4.19l-1.72-1.72a.75.75 0 0 0-1.06 1.06l3 3Z" d="M13 6H3v6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V6ZM8.75 7.75a.75.75 0 0 0-1.5 0v2.69L6.03 9.22a.75.75 0 0 0-1.06 1.06l2.5 2.5a.75.75 0 0 0 1.06 0l2.5-2.5a.75.75 0 1 0-1.06-1.06l-1.22 1.22V7.75Z"
clip-rule="evenodd" clip-rule="evenodd"
/> />
<path
d="M14.25 5.25a5.23 5.23 0 0 0-1.279-3.434 9.768 9.768 0 0 1 6.963 6.963A5.23 5.23 0 0 0 16.5 7.5h-1.875a.375.375 0 0 1-.375-.375V5.25Z"
/>
</svg> </svg>
</div> </div>
<div class=" self-center text-sm font-medium"> <div class=" self-center text-sm font-medium">
@ -132,7 +214,6 @@
</button> </button>
</div> </div>
</div> </div>
</div>
<!-- <div class="flex justify-end pt-3 text-sm font-medium"> <!-- <div class="flex justify-end pt-3 text-sm font-medium">
<button <button