choizhang
commited on
Commit
·
11b4cc9
1
Parent(s):
3f238f2
Added loginPage
Browse files- lightrag/api/lightrag_server.py +1 -1
- lightrag_webui/bun.lock +13 -0
- lightrag_webui/package.json +1 -0
- lightrag_webui/src/App.tsx +0 -2
- lightrag_webui/src/AppRouter.tsx +40 -0
- lightrag_webui/src/api/lightrag.ts +39 -0
- lightrag_webui/src/features/LoginPage.tsx +100 -0
- lightrag_webui/src/features/SiteHeader.tsx +21 -2
- lightrag_webui/src/main.tsx +2 -2
- lightrag_webui/src/stores/state.ts +22 -0
lightrag/api/lightrag_server.py
CHANGED
@@ -373,7 +373,7 @@ def create_app(args):
|
|
373 |
ollama_api = OllamaAPI(rag, top_k=args.top_k)
|
374 |
app.include_router(ollama_api.router, prefix="/api")
|
375 |
|
376 |
-
@app.post("/login")
|
377 |
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
|
378 |
username = os.getenv("AUTH_USERNAME")
|
379 |
password = os.getenv("AUTH_PASSWORD")
|
|
|
373 |
ollama_api = OllamaAPI(rag, top_k=args.top_k)
|
374 |
app.include_router(ollama_api.router, prefix="/api")
|
375 |
|
376 |
+
@app.post("/login", dependencies=[Depends(optional_api_key)])
|
377 |
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
|
378 |
username = os.getenv("AUTH_USERNAME")
|
379 |
password = os.getenv("AUTH_PASSWORD")
|
lightrag_webui/bun.lock
CHANGED
@@ -41,6 +41,7 @@
|
|
41 |
"react-dropzone": "^14.3.6",
|
42 |
"react-markdown": "^9.1.0",
|
43 |
"react-number-format": "^5.4.3",
|
|
|
44 |
"react-syntax-highlighter": "^15.6.1",
|
45 |
"rehype-react": "^8.0.0",
|
46 |
"remark-gfm": "^4.0.1",
|
@@ -415,6 +416,8 @@
|
|
415 |
|
416 |
"@types/bun": ["@types/[email protected]", "", { "dependencies": { "bun-types": "1.2.3" } }, "sha512-054h79ipETRfjtsCW9qJK8Ipof67Pw9bodFWmkfkaUaRiIQ1dIV2VTlheshlBx3mpKr0KeK8VqnMMCtgN9rQtw=="],
|
417 |
|
|
|
|
|
418 |
"@types/debug": ["@types/[email protected]", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
|
419 |
|
420 |
"@types/estree": ["@types/[email protected]", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="],
|
@@ -561,6 +564,8 @@
|
|
561 |
|
562 |
"convert-source-map": ["[email protected]", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="],
|
563 |
|
|
|
|
|
564 |
"cosmiconfig": ["[email protected]", "", { "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", "yaml": "^1.10.0" } }, "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA=="],
|
565 |
|
566 |
"cross-spawn": ["[email protected]", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
@@ -1103,6 +1108,10 @@
|
|
1103 |
|
1104 |
"react-remove-scroll-bar": ["[email protected]", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
|
1105 |
|
|
|
|
|
|
|
|
|
1106 |
"react-select": ["[email protected]", "", { "dependencies": { "@babel/runtime": "^7.12.0", "@emotion/cache": "^11.4.0", "@emotion/react": "^11.8.1", "@floating-ui/dom": "^1.0.1", "@types/react-transition-group": "^4.4.0", "memoize-one": "^6.0.0", "prop-types": "^15.6.0", "react-transition-group": "^4.3.0", "use-isomorphic-layout-effect": "^1.2.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-k96gw+i6N3ExgDwPIg0lUPmexl1ygPe6u5BdQFNBhkpbwroIgCNXdubtIzHfThYXYYTubwOBafoMnn7ruEP1xA=="],
|
1107 |
|
1108 |
"react-style-singleton": ["[email protected]", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
|
@@ -1153,6 +1162,8 @@
|
|
1153 |
|
1154 |
"semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
1155 |
|
|
|
|
|
1156 |
"set-function-length": ["[email protected]", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
|
1157 |
|
1158 |
"set-function-name": ["[email protected]", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
|
@@ -1223,6 +1234,8 @@
|
|
1223 |
|
1224 |
"tslib": ["[email protected]", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
1225 |
|
|
|
|
|
1226 |
"type-check": ["[email protected]", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
1227 |
|
1228 |
"typed-array-buffer": ["[email protected]", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
|
|
|
41 |
"react-dropzone": "^14.3.6",
|
42 |
"react-markdown": "^9.1.0",
|
43 |
"react-number-format": "^5.4.3",
|
44 |
+
"react-router-dom": "^7.3.0",
|
45 |
"react-syntax-highlighter": "^15.6.1",
|
46 |
"rehype-react": "^8.0.0",
|
47 |
"remark-gfm": "^4.0.1",
|
|
|
416 |
|
417 |
"@types/bun": ["@types/[email protected]", "", { "dependencies": { "bun-types": "1.2.3" } }, "sha512-054h79ipETRfjtsCW9qJK8Ipof67Pw9bodFWmkfkaUaRiIQ1dIV2VTlheshlBx3mpKr0KeK8VqnMMCtgN9rQtw=="],
|
418 |
|
419 |
+
"@types/cookie": ["@types/[email protected]", "https://registry.npmmirror.com/@types/cookie/-/cookie-0.6.0.tgz", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
|
420 |
+
|
421 |
"@types/debug": ["@types/[email protected]", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
|
422 |
|
423 |
"@types/estree": ["@types/[email protected]", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="],
|
|
|
564 |
|
565 |
"convert-source-map": ["[email protected]", "", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="],
|
566 |
|
567 |
+
"cookie": ["[email protected]", "https://registry.npmmirror.com/cookie/-/cookie-1.0.2.tgz", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="],
|
568 |
+
|
569 |
"cosmiconfig": ["[email protected]", "", { "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", "yaml": "^1.10.0" } }, "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA=="],
|
570 |
|
571 |
"cross-spawn": ["[email protected]", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
|
|
1108 |
|
1109 |
"react-remove-scroll-bar": ["[email protected]", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
|
1110 |
|
1111 |
+
"react-router": ["[email protected]", "https://registry.npmmirror.com/react-router/-/react-router-7.3.0.tgz", { "dependencies": { "@types/cookie": "^0.6.0", "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0", "turbo-stream": "2.4.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-466f2W7HIWaNXTKM5nHTqNxLrHTyXybm7R0eBlVSt0k/u55tTCDO194OIx/NrYD4TS5SXKTNekXfT37kMKUjgw=="],
|
1112 |
+
|
1113 |
+
"react-router-dom": ["[email protected]", "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-7.3.0.tgz", { "dependencies": { "react-router": "7.3.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-z7Q5FTiHGgQfEurX/FBinkOXhWREJIAB2RiU24lvcBa82PxUpwqvs/PAXb9lJyPjTs2jrl6UkLvCZVGJPeNuuQ=="],
|
1114 |
+
|
1115 |
"react-select": ["[email protected]", "", { "dependencies": { "@babel/runtime": "^7.12.0", "@emotion/cache": "^11.4.0", "@emotion/react": "^11.8.1", "@floating-ui/dom": "^1.0.1", "@types/react-transition-group": "^4.4.0", "memoize-one": "^6.0.0", "prop-types": "^15.6.0", "react-transition-group": "^4.3.0", "use-isomorphic-layout-effect": "^1.2.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-k96gw+i6N3ExgDwPIg0lUPmexl1ygPe6u5BdQFNBhkpbwroIgCNXdubtIzHfThYXYYTubwOBafoMnn7ruEP1xA=="],
|
1116 |
|
1117 |
"react-style-singleton": ["[email protected]", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
|
|
|
1162 |
|
1163 |
"semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
|
1164 |
|
1165 |
+
"set-cookie-parser": ["[email protected]", "https://registry.npmmirror.com/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="],
|
1166 |
+
|
1167 |
"set-function-length": ["[email protected]", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
|
1168 |
|
1169 |
"set-function-name": ["[email protected]", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
|
|
|
1234 |
|
1235 |
"tslib": ["[email protected]", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
1236 |
|
1237 |
+
"turbo-stream": ["[email protected]", "https://registry.npmmirror.com/turbo-stream/-/turbo-stream-2.4.0.tgz", {}, "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g=="],
|
1238 |
+
|
1239 |
"type-check": ["[email protected]", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
|
1240 |
|
1241 |
"typed-array-buffer": ["[email protected]", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
|
lightrag_webui/package.json
CHANGED
@@ -50,6 +50,7 @@
|
|
50 |
"react-dropzone": "^14.3.6",
|
51 |
"react-markdown": "^9.1.0",
|
52 |
"react-number-format": "^5.4.3",
|
|
|
53 |
"react-syntax-highlighter": "^15.6.1",
|
54 |
"rehype-react": "^8.0.0",
|
55 |
"remark-gfm": "^4.0.1",
|
|
|
50 |
"react-dropzone": "^14.3.6",
|
51 |
"react-markdown": "^9.1.0",
|
52 |
"react-number-format": "^5.4.3",
|
53 |
+
"react-router-dom": "^7.3.0",
|
54 |
"react-syntax-highlighter": "^15.6.1",
|
55 |
"rehype-react": "^8.0.0",
|
56 |
"remark-gfm": "^4.0.1",
|
lightrag_webui/src/App.tsx
CHANGED
@@ -7,7 +7,6 @@ import { healthCheckInterval } from '@/lib/constants'
|
|
7 |
import { useBackendState } from '@/stores/state'
|
8 |
import { useSettingsStore } from '@/stores/settings'
|
9 |
import { useEffect } from 'react'
|
10 |
-
import { Toaster } from 'sonner'
|
11 |
import SiteHeader from '@/features/SiteHeader'
|
12 |
import { InvalidApiKeyError, RequireApiKeError } from '@/api/lightrag'
|
13 |
|
@@ -79,7 +78,6 @@ function App() {
|
|
79 |
{enableHealthCheck && <StatusIndicator />}
|
80 |
{message !== null && !apiKeyInvalid && <MessageAlert />}
|
81 |
{apiKeyInvalid && <ApiKeyAlert />}
|
82 |
-
<Toaster />
|
83 |
</main>
|
84 |
</ThemeProvider>
|
85 |
)
|
|
|
7 |
import { useBackendState } from '@/stores/state'
|
8 |
import { useSettingsStore } from '@/stores/settings'
|
9 |
import { useEffect } from 'react'
|
|
|
10 |
import SiteHeader from '@/features/SiteHeader'
|
11 |
import { InvalidApiKeyError, RequireApiKeError } from '@/api/lightrag'
|
12 |
|
|
|
78 |
{enableHealthCheck && <StatusIndicator />}
|
79 |
{message !== null && !apiKeyInvalid && <MessageAlert />}
|
80 |
{apiKeyInvalid && <ApiKeyAlert />}
|
|
|
81 |
</main>
|
82 |
</ThemeProvider>
|
83 |
)
|
lightrag_webui/src/AppRouter.tsx
ADDED
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
|
2 |
+
// import { useAuthStore } from '@/stores/state'
|
3 |
+
import { Toaster } from 'sonner'
|
4 |
+
import App from './App'
|
5 |
+
import LoginPage from '@/features/LoginPage'
|
6 |
+
|
7 |
+
interface ProtectedRouteProps {
|
8 |
+
children: React.ReactNode
|
9 |
+
}
|
10 |
+
|
11 |
+
const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
|
12 |
+
// const { isAuthenticated } = useAuthStore()
|
13 |
+
|
14 |
+
// if (!isAuthenticated) {
|
15 |
+
// return <Navigate to="/login" replace />
|
16 |
+
// }
|
17 |
+
|
18 |
+
return <>{children}</>
|
19 |
+
}
|
20 |
+
|
21 |
+
const AppRouter = () => {
|
22 |
+
return (
|
23 |
+
<BrowserRouter>
|
24 |
+
<Routes>
|
25 |
+
<Route path="/login" element={<LoginPage />} />
|
26 |
+
<Route
|
27 |
+
path="/*"
|
28 |
+
element={
|
29 |
+
<ProtectedRoute>
|
30 |
+
<App />
|
31 |
+
</ProtectedRoute>
|
32 |
+
}
|
33 |
+
/>
|
34 |
+
</Routes>
|
35 |
+
<Toaster position="top-center" />
|
36 |
+
</BrowserRouter>
|
37 |
+
)
|
38 |
+
}
|
39 |
+
|
40 |
+
export default AppRouter
|
lightrag_webui/src/api/lightrag.ts
CHANGED
@@ -2,6 +2,7 @@ import axios, { AxiosError } from 'axios'
|
|
2 |
import { backendBaseUrl } from '@/lib/constants'
|
3 |
import { errorMessage } from '@/lib/utils'
|
4 |
import { useSettingsStore } from '@/stores/settings'
|
|
|
5 |
|
6 |
// Types
|
7 |
export type LightragNodeType = {
|
@@ -125,6 +126,11 @@ export type DocsStatusesResponse = {
|
|
125 |
statuses: Record<DocStatus, DocStatusResponse[]>
|
126 |
}
|
127 |
|
|
|
|
|
|
|
|
|
|
|
128 |
export const InvalidApiKeyError = 'Invalid API Key'
|
129 |
export const RequireApiKeError = 'API Key required'
|
130 |
|
@@ -139,9 +145,13 @@ const axiosInstance = axios.create({
|
|
139 |
// Interceptor:add api key
|
140 |
axiosInstance.interceptors.request.use((config) => {
|
141 |
const apiKey = useSettingsStore.getState().apiKey
|
|
|
142 |
if (apiKey) {
|
143 |
config.headers['X-API-Key'] = apiKey
|
144 |
}
|
|
|
|
|
|
|
145 |
return config
|
146 |
})
|
147 |
|
@@ -150,6 +160,21 @@ axiosInstance.interceptors.response.use(
|
|
150 |
(response) => response,
|
151 |
(error: AxiosError) => {
|
152 |
if (error.response) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
153 |
throw new Error(
|
154 |
`${error.response.status} ${error.response.statusText}\n${JSON.stringify(
|
155 |
error.response.data
|
@@ -324,3 +349,17 @@ export const clearDocuments = async (): Promise<DocActionResponse> => {
|
|
324 |
const response = await axiosInstance.delete('/documents')
|
325 |
return response.data
|
326 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
import { backendBaseUrl } from '@/lib/constants'
|
3 |
import { errorMessage } from '@/lib/utils'
|
4 |
import { useSettingsStore } from '@/stores/settings'
|
5 |
+
import { useAuthStore } from '@/stores/state'
|
6 |
|
7 |
// Types
|
8 |
export type LightragNodeType = {
|
|
|
126 |
statuses: Record<DocStatus, DocStatusResponse[]>
|
127 |
}
|
128 |
|
129 |
+
export type LoginResponse = {
|
130 |
+
access_token: string
|
131 |
+
token_type: string
|
132 |
+
}
|
133 |
+
|
134 |
export const InvalidApiKeyError = 'Invalid API Key'
|
135 |
export const RequireApiKeError = 'API Key required'
|
136 |
|
|
|
145 |
// Interceptor:add api key
|
146 |
axiosInstance.interceptors.request.use((config) => {
|
147 |
const apiKey = useSettingsStore.getState().apiKey
|
148 |
+
const token = localStorage.getItem('LIGHTRAG-API-TOKEN');
|
149 |
if (apiKey) {
|
150 |
config.headers['X-API-Key'] = apiKey
|
151 |
}
|
152 |
+
if (token) {
|
153 |
+
config.headers['Authorization'] = `Bearer ${token}`
|
154 |
+
}
|
155 |
return config
|
156 |
})
|
157 |
|
|
|
160 |
(response) => response,
|
161 |
(error: AxiosError) => {
|
162 |
if (error.response) {
|
163 |
+
interface ErrorResponse {
|
164 |
+
detail: string;
|
165 |
+
}
|
166 |
+
|
167 |
+
if (error.response?.status === 401) {
|
168 |
+
localStorage.removeItem('LIGHTRAG-API-TOKEN');
|
169 |
+
sessionStorage.clear();
|
170 |
+
useAuthStore.getState().logout();
|
171 |
+
|
172 |
+
if (window.location.pathname !== '/login') {
|
173 |
+
window.location.href = '/login';
|
174 |
+
}
|
175 |
+
|
176 |
+
return Promise.reject(error);
|
177 |
+
}
|
178 |
throw new Error(
|
179 |
`${error.response.status} ${error.response.statusText}\n${JSON.stringify(
|
180 |
error.response.data
|
|
|
349 |
const response = await axiosInstance.delete('/documents')
|
350 |
return response.data
|
351 |
}
|
352 |
+
|
353 |
+
export const loginToServer = async (username: string, password: string): Promise<LoginResponse> => {
|
354 |
+
const formData = new FormData();
|
355 |
+
formData.append('username', username);
|
356 |
+
formData.append('password', password);
|
357 |
+
|
358 |
+
const response = await axiosInstance.post('/login', formData, {
|
359 |
+
headers: {
|
360 |
+
'Content-Type': 'multipart/form-data'
|
361 |
+
}
|
362 |
+
});
|
363 |
+
|
364 |
+
return response.data;
|
365 |
+
}
|
lightrag_webui/src/features/LoginPage.tsx
ADDED
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useState } from 'react'
|
2 |
+
import { useNavigate } from 'react-router-dom'
|
3 |
+
import { useAuthStore } from '@/stores/state'
|
4 |
+
import { loginToServer } from '@/api/lightrag'
|
5 |
+
import { toast } from 'sonner'
|
6 |
+
|
7 |
+
import { Card, CardContent, CardHeader } from '@/components/ui/Card'
|
8 |
+
import Input from '@/components/ui/Input'
|
9 |
+
import Button from '@/components/ui/Button'
|
10 |
+
import { ZapIcon } from 'lucide-react'
|
11 |
+
|
12 |
+
const LoginPage = () => {
|
13 |
+
const navigate = useNavigate()
|
14 |
+
const { login } = useAuthStore()
|
15 |
+
const [loading, setLoading] = useState(false)
|
16 |
+
const [username, setUsername] = useState('')
|
17 |
+
const [password, setPassword] = useState('')
|
18 |
+
|
19 |
+
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
20 |
+
e.preventDefault()
|
21 |
+
if (!username || !password) {
|
22 |
+
toast.error('Please enter your username and password')
|
23 |
+
return
|
24 |
+
}
|
25 |
+
|
26 |
+
try {
|
27 |
+
setLoading(true)
|
28 |
+
const response = await loginToServer(username, password)
|
29 |
+
login(response.access_token)
|
30 |
+
navigate('/')
|
31 |
+
toast.success('Login succeeded')
|
32 |
+
} catch (error) {
|
33 |
+
console.error('Login failed...', error)
|
34 |
+
toast.error('Login failed, please check username and password')
|
35 |
+
} finally {
|
36 |
+
setLoading(false)
|
37 |
+
}
|
38 |
+
}
|
39 |
+
|
40 |
+
return (
|
41 |
+
<div className="flex h-screen w-screen items-center justify-center bg-gradient-to-br from-emerald-50 to-teal-100 dark:from-gray-900 dark:to-gray-800">
|
42 |
+
<Card className="w-full max-w-[480px] shadow-lg mx-4">
|
43 |
+
<CardHeader className="flex items-center justify-center space-y-2 pb-8 pt-6">
|
44 |
+
<div className="flex flex-col items-center space-y-4">
|
45 |
+
<div className="flex items-center gap-3">
|
46 |
+
<img src="/logo.png" alt="LightRAG Logo" className="h-12 w-12" />
|
47 |
+
<ZapIcon className="size-10 text-emerald-400" aria-hidden="true" />
|
48 |
+
</div>
|
49 |
+
<div className="text-center space-y-2">
|
50 |
+
<h1 className="text-3xl font-bold tracking-tight">LightRAG</h1>
|
51 |
+
<p className="text-muted-foreground text-sm">
|
52 |
+
Please enter your account and password to log in to the system
|
53 |
+
</p>
|
54 |
+
</div>
|
55 |
+
</div>
|
56 |
+
</CardHeader>
|
57 |
+
<CardContent className="px-8 pb-8">
|
58 |
+
<form onSubmit={handleSubmit} className="space-y-6">
|
59 |
+
<div className="flex items-center gap-4">
|
60 |
+
<label htmlFor="username" className="text-sm font-medium w-16 shrink-0">
|
61 |
+
username
|
62 |
+
</label>
|
63 |
+
<Input
|
64 |
+
id="username"
|
65 |
+
placeholder="Please input a username"
|
66 |
+
value={username}
|
67 |
+
onChange={(e) => setUsername(e.target.value)}
|
68 |
+
required
|
69 |
+
className="h-11 flex-1"
|
70 |
+
/>
|
71 |
+
</div>
|
72 |
+
<div className="flex items-center gap-4">
|
73 |
+
<label htmlFor="password" className="text-sm font-medium w-16 shrink-0">
|
74 |
+
password
|
75 |
+
</label>
|
76 |
+
<Input
|
77 |
+
id="password"
|
78 |
+
type="password"
|
79 |
+
placeholder="Please input a password"
|
80 |
+
value={password}
|
81 |
+
onChange={(e) => setPassword(e.target.value)}
|
82 |
+
required
|
83 |
+
className="h-11 flex-1"
|
84 |
+
/>
|
85 |
+
</div>
|
86 |
+
<Button
|
87 |
+
type="submit"
|
88 |
+
className="w-full h-11 text-base font-medium mt-2"
|
89 |
+
disabled={loading}
|
90 |
+
>
|
91 |
+
{loading ? 'Logging in...' : 'Login'}
|
92 |
+
</Button>
|
93 |
+
</form>
|
94 |
+
</CardContent>
|
95 |
+
</Card>
|
96 |
+
</div>
|
97 |
+
)
|
98 |
+
}
|
99 |
+
|
100 |
+
export default LoginPage
|
lightrag_webui/src/features/SiteHeader.tsx
CHANGED
@@ -3,9 +3,11 @@ import { SiteInfo } from '@/lib/constants'
|
|
3 |
import ThemeToggle from '@/components/ThemeToggle'
|
4 |
import { TabsList, TabsTrigger } from '@/components/ui/Tabs'
|
5 |
import { useSettingsStore } from '@/stores/settings'
|
|
|
6 |
import { cn } from '@/lib/utils'
|
|
|
7 |
|
8 |
-
import { ZapIcon, GithubIcon } from 'lucide-react'
|
9 |
|
10 |
interface NavigationTabProps {
|
11 |
value: string
|
@@ -51,6 +53,14 @@ function TabsNavigation() {
|
|
51 |
}
|
52 |
|
53 |
export default function SiteHeader() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
return (
|
55 |
<header className="border-border/40 bg-background/95 supports-[backdrop-filter]:bg-background/60 sticky top-0 z-50 flex h-10 w-full border-b px-4 backdrop-blur">
|
56 |
<a href="/" className="mr-6 flex items-center gap-2">
|
@@ -63,13 +73,22 @@ export default function SiteHeader() {
|
|
63 |
<TabsNavigation />
|
64 |
</div>
|
65 |
|
66 |
-
<nav className="flex items-center">
|
67 |
<Button variant="ghost" size="icon" side="bottom" tooltip="Project Repository">
|
68 |
<a href={SiteInfo.github} target="_blank" rel="noopener noreferrer">
|
69 |
<GithubIcon className="size-4" aria-hidden="true" />
|
70 |
</a>
|
71 |
</Button>
|
72 |
<ThemeToggle />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
</nav>
|
74 |
</header>
|
75 |
)
|
|
|
3 |
import ThemeToggle from '@/components/ThemeToggle'
|
4 |
import { TabsList, TabsTrigger } from '@/components/ui/Tabs'
|
5 |
import { useSettingsStore } from '@/stores/settings'
|
6 |
+
import { useAuthStore } from '@/stores/state'
|
7 |
import { cn } from '@/lib/utils'
|
8 |
+
import { useNavigate } from 'react-router-dom'
|
9 |
|
10 |
+
import { ZapIcon, GithubIcon, LogOutIcon } from 'lucide-react'
|
11 |
|
12 |
interface NavigationTabProps {
|
13 |
value: string
|
|
|
53 |
}
|
54 |
|
55 |
export default function SiteHeader() {
|
56 |
+
const navigate = useNavigate()
|
57 |
+
const { logout } = useAuthStore()
|
58 |
+
|
59 |
+
const handleLogout = () => {
|
60 |
+
logout()
|
61 |
+
navigate('/login')
|
62 |
+
}
|
63 |
+
|
64 |
return (
|
65 |
<header className="border-border/40 bg-background/95 supports-[backdrop-filter]:bg-background/60 sticky top-0 z-50 flex h-10 w-full border-b px-4 backdrop-blur">
|
66 |
<a href="/" className="mr-6 flex items-center gap-2">
|
|
|
73 |
<TabsNavigation />
|
74 |
</div>
|
75 |
|
76 |
+
<nav className="flex items-center gap-2">
|
77 |
<Button variant="ghost" size="icon" side="bottom" tooltip="Project Repository">
|
78 |
<a href={SiteInfo.github} target="_blank" rel="noopener noreferrer">
|
79 |
<GithubIcon className="size-4" aria-hidden="true" />
|
80 |
</a>
|
81 |
</Button>
|
82 |
<ThemeToggle />
|
83 |
+
<Button
|
84 |
+
variant="ghost"
|
85 |
+
size="icon"
|
86 |
+
side="bottom"
|
87 |
+
tooltip="Log Out"
|
88 |
+
onClick={handleLogout}
|
89 |
+
>
|
90 |
+
<LogOutIcon className="size-4" aria-hidden="true" />
|
91 |
+
</Button>
|
92 |
</nav>
|
93 |
</header>
|
94 |
)
|
lightrag_webui/src/main.tsx
CHANGED
@@ -1,10 +1,10 @@
|
|
1 |
import { StrictMode } from 'react'
|
2 |
import { createRoot } from 'react-dom/client'
|
3 |
import './index.css'
|
4 |
-
import
|
5 |
|
6 |
createRoot(document.getElementById('root')!).render(
|
7 |
<StrictMode>
|
8 |
-
<
|
9 |
</StrictMode>
|
10 |
)
|
|
|
1 |
import { StrictMode } from 'react'
|
2 |
import { createRoot } from 'react-dom/client'
|
3 |
import './index.css'
|
4 |
+
import AppRouter from './AppRouter'
|
5 |
|
6 |
createRoot(document.getElementById('root')!).render(
|
7 |
<StrictMode>
|
8 |
+
<AppRouter />
|
9 |
</StrictMode>
|
10 |
)
|
lightrag_webui/src/stores/state.ts
CHANGED
@@ -16,6 +16,14 @@ interface BackendState {
|
|
16 |
setErrorMessage: (message: string, messageTitle: string) => void
|
17 |
}
|
18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
const useBackendStateStoreBase = create<BackendState>()((set) => ({
|
20 |
health: true,
|
21 |
message: null,
|
@@ -57,3 +65,17 @@ const useBackendStateStoreBase = create<BackendState>()((set) => ({
|
|
57 |
const useBackendState = createSelectors(useBackendStateStoreBase)
|
58 |
|
59 |
export { useBackendState }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
setErrorMessage: (message: string, messageTitle: string) => void
|
17 |
}
|
18 |
|
19 |
+
interface AuthState {
|
20 |
+
isAuthenticated: boolean;
|
21 |
+
showLoginModal: boolean;
|
22 |
+
login: (token: string) => void;
|
23 |
+
logout: () => void;
|
24 |
+
setShowLoginModal: (show: boolean) => void;
|
25 |
+
}
|
26 |
+
|
27 |
const useBackendStateStoreBase = create<BackendState>()((set) => ({
|
28 |
health: true,
|
29 |
message: null,
|
|
|
65 |
const useBackendState = createSelectors(useBackendStateStoreBase)
|
66 |
|
67 |
export { useBackendState }
|
68 |
+
|
69 |
+
export const useAuthStore = create<AuthState>(set => ({
|
70 |
+
isAuthenticated: !!localStorage.getItem('LIGHTRAG-API-TOKEN'),
|
71 |
+
showLoginModal: false,
|
72 |
+
login: (token) => {
|
73 |
+
localStorage.setItem('LIGHTRAG-API-TOKEN', token);
|
74 |
+
set({ isAuthenticated: true, showLoginModal: false });
|
75 |
+
},
|
76 |
+
logout: () => {
|
77 |
+
localStorage.removeItem('LIGHTRAG-API-TOKEN');
|
78 |
+
set({ isAuthenticated: false });
|
79 |
+
},
|
80 |
+
setShowLoginModal: (show) => set({ showLoginModal: show })
|
81 |
+
}));
|