Skip to content

Commit

Permalink
feat: IAM - user management
Browse files Browse the repository at this point in the history
  • Loading branch information
sugarforever committed Apr 16, 2024
1 parent 853f110 commit cbea8c9
Show file tree
Hide file tree
Showing 17 changed files with 909 additions and 59 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ MILVUS_URL=http://localhost:19530

# Cohere API Key - Reranking
COHERE_API_KEY=

# Super admin name
SUPER_ADMIN_NAME=
36 changes: 36 additions & 0 deletions components/Auth.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<script setup lang="ts">
const { signOut, data, status } = useAuth()
const onClickSignOut = async () => {
await signOut({ callbackUrl: '/' })
}
const items = [
[{
label: 'Sign Out',
icon: 'i-heroicons-arrow-right-start-on-rectangle-16-solid',
click: async () => {
await onClickSignOut()
}
}]
]
const buttonColor = computed(() => {
return data?.value?.role === 'superadmin' ? 'red' : 'white'
})
</script>
<template>
<ClientOnly>
<div>
<UButton
icon="i-heroicons-user-circle-16-solid"
to="/login"
variant="outline"
v-if="status === 'unauthenticated'">Log In</UButton>
<div v-else>
<UDropdown :items="items" :popper="{ placement: 'bottom-end' }">
<UButton :color="buttonColor" :label="data?.name" trailing-icon="i-heroicons-chevron-down-20-solid" />
</UDropdown>
</div>
</div>
</ClientOnly>
</template>
50 changes: 25 additions & 25 deletions components/Download.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,46 +7,46 @@ const emit = defineEmits(["modelDownloaded"])
const toast = useToast()
const state = reactive({
modelName: undefined
});
const downloading = ref(false);
const progresses = ref<ProgressResponse[]>([]);
})
const downloading = ref(false)
const progresses = ref<ProgressResponse[]>([])
const fetchStream = async (url: string, options: RequestInit) => {
const response = await fetch(url, options);
const response = await fetch(url, options)
if (response.body) {
const reader = response.body.getReader();
const reader = response.body.getReader()
while (true) {
const { done, value } = await reader.read();
if (done) break;
const { done, value } = await reader.read()
if (done) break
const chunk = new TextDecoder().decode(value);
const chunk = new TextDecoder().decode(value)
chunk.split("\n\n").forEach((line) => {
if (line) {
const progress = JSON.parse(line);
const progress = JSON.parse(line)
if (progress.error) {
throw new Error(progress.error);
throw new Error(progress.error)
}
const existing = progresses.value.find((p) => p.status === progress.status);
const existing = progresses.value.find((p) => p.status === progress.status)
if (existing) {
Object.assign(existing, progress);
Object.assign(existing, progress)
} else {
progresses.value.push(progress);
progresses.value.push(progress)
}
}
});
})
}
} else {
console.log("The browser doesn't support streaming responses.");
console.log("The browser doesn't support streaming responses.")
}
}
const onDownload = async () => {
downloading.value = true;
progresses.value = [];
const { modelName } = state;
downloading.value = true
progresses.value = []
const { modelName } = state
try {
await fetchStream('/api/models/pull', {
Expand All @@ -59,23 +59,23 @@ const onDownload = async () => {
...fetchHeadersOllama.value,
'Content-Type': 'application/json',
},
});
emit("modelDownloaded", modelName);
})
emit("modelDownloaded", modelName)
} catch (error: any) {
progresses.value = [];
toast.add({ color: 'red', title: "Failed to download model", description: error.message });
progresses.value = []
toast.add({ color: 'red', title: "Failed to download model", description: error.message })
}
downloading.value = false;
};
downloading.value = false
}
</script>

<template>
<UForm :state="state" @submit="onDownload">
<div class="flex flex-col md:flex-row items-center">
<div class="flex grow w-full gap-2 md:max-w-lg">
<UInput class="flex-1" size="lg" v-model="state.modelName" placeholder="Enter the model name to download"
required />
required />
<UButton type="submit" :loading="downloading">
Download
</UButton>
Expand Down
7 changes: 5 additions & 2 deletions layouts/default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,18 @@ const logoSrc = computed(() => {
</h1>
</ClientOnly>
<div>
<UHorizontalNavigation :links="links" />
<ClientOnly>
<UHorizontalNavigation :links="links" />
</ClientOnly>
</div>
<div class="flex items-center">
<div class="mx-2">
<ColorMode />
</div>
<ULink to="https://github.com/sugarforever/chat-ollama"
target="_blank"
class="i-mdi-github text-2xl ml-2"></ULink>
class="i-mdi-github text-2xl ml-2 mr-4"></ULink>
<Auth />
</div>
</div>
</div>
Expand Down
59 changes: 40 additions & 19 deletions nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,47 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
auth: {
provider: {
type: 'local',
endpoints: {
getSession: { path: '/user' }
},
pages: {
login: '/'
},
token: {
signInResponseTokenPointer: '/token/accessToken'
},
sessionDataType: { id: 'string', email: 'string', name: 'string' }
},
session: {
// Whether to refresh the session every time the browser window is refocused.
enableRefreshOnWindowFocus: true,

// Whether to refresh the session every `X` milliseconds. Set this to `false` to turn it off. The session will only be refreshed if a session already exists.
enableRefreshPeriodically: 5000
},
globalAppMiddleware: {
isEnabled: false
}
},
devtools: { enabled: true },
modules: [
'@nuxt/ui',
'@vueuse/nuxt',
['@nuxtjs/google-fonts', {
families: {
'Noto Sans': true,
'Josefin+Sans': true,
Lato: [100, 300],
Raleway: {
wght: [100, 400],
ital: [100]
},
Inter: '200..700',
'Crimson Pro': {
wght: '200..900',
ital: '200..700',
}
modules: ['@nuxt/ui', '@vueuse/nuxt', ['@nuxtjs/google-fonts', {
families: {
'Noto Sans': true,
'Josefin+Sans': true,
Lato: [100, 300],
Raleway: {
wght: [100, 400],
ital: [100]
},
Inter: '200..700',
'Crimson Pro': {
wght: '200..900',
ital: '200..700',
}
}]
],
}
}], "@sidebase/nuxt-auth"],
nitro: {
experimental: {
openAPI: true
Expand Down
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
},
"devDependencies": {
"@nuxtjs/google-fonts": "^3.2.0",
"@sidebase/nuxt-auth": "^0.7.1",
"@types/markdown-it": "^13.0.7",
"@types/node": "^20.12.5",
"@types/ws": "^8.5.10",
Expand Down Expand Up @@ -44,11 +45,13 @@
"@vueuse/nuxt": "^10.9.0",
"@zilliz/milvus2-sdk-node": "^2.3.5",
"ai": "^2.2.37",
"bcrypt": "^5.1.1",
"cheerio": "1.0.0-rc.12",
"chromadb": "^1.8.1",
"dexie": "^4.0.1",
"highlight.js": "^11.9.0",
"ioredis": "^5.3.2",
"jsonwebtoken": "^9.0.2",
"langchain": "^0.1.31",
"langsmith": "^0.1.14",
"mammoth": "^1.7.1",
Expand All @@ -61,10 +64,12 @@
"markdown-it-sup": "^2.0.0",
"markdown-it-task-lists": "^2.1.1",
"markdown-it-toc-done-right": "^4.2.0",
"next-auth": "4.21.1",
"ollama": "^0.5.0",
"openai": "^4.33.0",
"pdf-parse": "^1.1.1",
"prisma": "^5.12.1",
"ws": "^8.16.0"
"ws": "^8.16.0",
"yup": "^1.4.0"
}
}
11 changes: 10 additions & 1 deletion pages/knowledgebases/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { useStorage } from '@vueuse/core'
import { type KnowledgeBase } from '@prisma/client'
import { KnowledgeBaseForm } from '#components'
// definePageMeta({ middleware: 'auth' })
const auth = useAuth()
const router = useRouter()
const modal = useModal()
const confirm = useDialog('confirm')
Expand Down Expand Up @@ -65,7 +68,13 @@ function onShowUpdate(data: KnowledgeBase) {
<div class="max-w-6xl mx-auto">
<div class="flex items-center mb-4">
<h2 class="font-bold text-xl mr-auto">Knowledge Bases</h2>
<UButton icon="i-material-symbols-add" @click="onShowCreate">Create</UButton>
<ClientOnly>
<UButton
icon="i-material-symbols-add"
@click="onShowCreate">
Create
</UButton>
</ClientOnly>
</div>
<ClientOnly>
<UTable :columns="columns" :rows="knowledgeBases" class="table-list">
Expand Down
67 changes: 67 additions & 0 deletions pages/login/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<script setup lang="ts">
import { object, string, type InferType } from 'yup'
definePageMeta({
auth: {
unauthenticatedOnly: true,
navigateAuthenticatedTo: '/'
}
})
const { signIn } = useAuth()
const loading = ref(false)
const toast = useToast()
const schema = object({
name: string().min(1).required('Required'),
password: string().min(8, 'Must be at least 8 characters').required('Required')
})
type Schema = InferType<typeof schema>
const state = reactive({
name: undefined,
password: undefined
})
async function onSubmit() {
loading.value = true
try {
await signIn({
username: state.name,
password: state.password
}, {
callbackUrl: '/'
})
} catch (error) {
toast.add({
title: 'Failed to log in',
description: error.statusMessage,
color: 'red'
})
}
loading.value = false
}
</script>
<template>
<ClientOnly>
<UCard class="w-[400px] mx-auto">
<template #header>
<h1>Log in</h1>
</template>

<UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
<UFormGroup label="Name" name="name">
<UInput v-model="state.name" />
</UFormGroup>

<UFormGroup label="Password" name="password">
<UInput v-model="state.password" type="password" />
</UFormGroup>

<UButton type="submit" :loading="loading">
Continue
</UButton>
</UForm>
</UCard>
</ClientOnly>
</template>
11 changes: 11 additions & 0 deletions pages/logout/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script setup lang="ts">
const { signOut } = useAuth()
</script>
<template>
<ClientOnly>
<div>
<p>Sign Out</p>
<button @click="signOut">Sign Out</button>
</div>
</ClientOnly>
</template>
Loading

0 comments on commit cbea8c9

Please sign in to comment.