feat: admin controls
This commit is contained in:
@@ -68,55 +68,73 @@ const toggleProvider = async () => {
|
||||
});
|
||||
};
|
||||
|
||||
let apiKeyTimeout: NodeJS.Timeout | undefined;
|
||||
let apiKeyTimeout: ReturnType<typeof setTimeout> | undefined;
|
||||
let proxyUrlTimeout: ReturnType<typeof setTimeout> | undefined;
|
||||
const configError = ref("");
|
||||
|
||||
const updateApiKey = async (value: string) => {
|
||||
if (!provider.value) return;
|
||||
// Config saves run one after the other, and each reads the revision only when
|
||||
// it is sent. A save that was typed while another was still on its way would
|
||||
// otherwise carry the revision from before that one and be turned down.
|
||||
let configSaves: Promise<void> = Promise.resolve();
|
||||
|
||||
const key = await crypto.subtle.importKey(
|
||||
"jwk",
|
||||
JSON.parse(window.localStorage.getItem("encryptionKey")!),
|
||||
"AES-GCM",
|
||||
false,
|
||||
["encrypt", "decrypt"]
|
||||
);
|
||||
const encypted = await encryptData(key, value);
|
||||
const saveConfig = (
|
||||
id: string,
|
||||
failure: string,
|
||||
config: () => Promise<{ apiKey?: string; apiProxyUrl?: string }>,
|
||||
expectedEncryptionKey?: string,
|
||||
) => {
|
||||
configSaves = configSaves.then(async () => {
|
||||
// A queued edit must not pair an old encryption key with a new revision.
|
||||
if (expectedEncryptionKey !== undefined && localStorage.getItem("encryptionKey") !== expectedEncryptionKey) {
|
||||
configError.value = failure;
|
||||
return;
|
||||
}
|
||||
const current = providers.value.find(p => p.id === id);
|
||||
if (!current) {
|
||||
return;
|
||||
}
|
||||
configError.value = "";
|
||||
try {
|
||||
await updateProvider(id, { config: await config() }, new Date(current.updatedAt).toISOString());
|
||||
} catch {
|
||||
configError.value = failure;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (apiKeyTimeout) {
|
||||
clearTimeout(apiKeyTimeout);
|
||||
const updateApiKey = (value: string) => {
|
||||
const snapshot = provider.value;
|
||||
const storedKey = localStorage.getItem("encryptionKey");
|
||||
if (!snapshot || !storedKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
apiKeyTimeout = setTimeout(async () => {
|
||||
await updateProvider(provider.value!.id, {
|
||||
config: {
|
||||
...provider.value!.config,
|
||||
apiKey: uint8ArrayToBase64(encypted),
|
||||
},
|
||||
});
|
||||
clearTimeout(apiKeyTimeout);
|
||||
apiKeyTimeout = setTimeout(() => {
|
||||
saveConfig(snapshot.id, "The key could not be saved. Reload this page before trying again.", async () => {
|
||||
const key = await crypto.subtle.importKey("jwk", JSON.parse(storedKey), "AES-GCM", false, ["encrypt", "decrypt"]);
|
||||
const encrypted = await encryptData(key, value);
|
||||
return { apiKey: uint8ArrayToBase64(encrypted) };
|
||||
}, storedKey);
|
||||
}, 700);
|
||||
};
|
||||
|
||||
let proxyUrlTimeout: NodeJS.Timeout | undefined;
|
||||
|
||||
const updateProxyUrl = async (value: string) => {
|
||||
if (!provider.value) return;
|
||||
|
||||
const updateProxyUrl = (value: string) => {
|
||||
const snapshot = provider.value;
|
||||
if (!snapshot) {
|
||||
return;
|
||||
}
|
||||
apiProxyUrl.value = value;
|
||||
|
||||
if (proxyUrlTimeout) {
|
||||
clearTimeout(proxyUrlTimeout);
|
||||
}
|
||||
|
||||
proxyUrlTimeout = setTimeout(async () => {
|
||||
await updateProvider(provider.value!.id, {
|
||||
config: {
|
||||
...provider.value!.config,
|
||||
apiProxyUrl: value,
|
||||
},
|
||||
});
|
||||
clearTimeout(proxyUrlTimeout);
|
||||
proxyUrlTimeout = setTimeout(() => {
|
||||
saveConfig(snapshot.id, "The address could not be saved. Reload this page before trying again.", async () => ({ apiProxyUrl: value }));
|
||||
}, 700);
|
||||
};
|
||||
|
||||
onUnmounted(() => {
|
||||
clearTimeout(apiKeyTimeout);
|
||||
clearTimeout(proxyUrlTimeout);
|
||||
});
|
||||
|
||||
const fetchingModels = ref(false);
|
||||
|
||||
const fetchModels = async () => {
|
||||
@@ -407,6 +425,8 @@ const isFormValid = computed(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p v-if="configError" role="alert" class="text-sm text-red-400">{{ configError }}</p>
|
||||
|
||||
<div class="flex flex-row justify-center text-xs">
|
||||
<p class="text-[var(--text-secondary)]">
|
||||
<span class="i-mynaui-lock inline-block"></span> Your API key is encrypted using
|
||||
|
||||
Reference in New Issue
Block a user