mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-10-02 16:16:39 +08:00
commit
32ab9af2a2
@ -4,8 +4,11 @@ import { User } from "@prisma/client";
|
|||||||
import UsersTable from "../../components/users-table";
|
import UsersTable from "../../components/users-table";
|
||||||
|
|
||||||
async function getData() {
|
async function getData() {
|
||||||
const users = await prisma.user.findMany();
|
return await prisma.user.findMany({
|
||||||
return users;
|
orderBy: {
|
||||||
|
createdAt: "desc",
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
export default async function UsersPage() {
|
export default async function UsersPage() {
|
||||||
const users: User[] = await getData();
|
const users: User[] = await getData();
|
||||||
|
@ -36,21 +36,16 @@ const items: MenuItem[] = [
|
|||||||
getItem("使用分析", "/admin/ana"),
|
getItem("使用分析", "/admin/ana"),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
getItem("Navigation Two", "sub2", <AppstoreOutlined />, [
|
getItem("管理", "manage", <AppstoreOutlined />, [
|
||||||
getItem("Option 5", "5"),
|
getItem("用户管理", "/admin/users"),
|
||||||
getItem("Option 6", "6"),
|
|
||||||
getItem("Submenu", "sub3", null, [
|
|
||||||
getItem("Option 7", "7"),
|
|
||||||
getItem("Option 8", "8"),
|
|
||||||
]),
|
|
||||||
]),
|
]),
|
||||||
|
|
||||||
getItem("Navigation Three", "sub4", <SettingOutlined />, [
|
// getItem("Navigation Three", "sub4", <SettingOutlined />, [
|
||||||
getItem("Option 9", "9"),
|
// getItem("Option 9", "9"),
|
||||||
getItem("Option 10", "10"),
|
// getItem("Option 10", "10"),
|
||||||
getItem("Option 11", "11"),
|
// getItem("Option 11", "11"),
|
||||||
getItem("Option 12", "12"),
|
// getItem("Option 12", "12"),
|
||||||
]),
|
// ]),
|
||||||
];
|
];
|
||||||
|
|
||||||
const SideBar: React.FC = () => {
|
const SideBar: React.FC = () => {
|
||||||
@ -68,12 +63,12 @@ const SideBar: React.FC = () => {
|
|||||||
setCurrent(e.key);
|
setCurrent(e.key);
|
||||||
router.push(e.key);
|
router.push(e.key);
|
||||||
};
|
};
|
||||||
// useEffect(() => {
|
useEffect(() => {
|
||||||
// // 如果按钮和路径不相等,那其实应该跳转到按钮的网址
|
// 如果按钮和路径不相等,那其实应该跳转到按钮的网址
|
||||||
// if (current != pathname) {
|
if (current != pathname) {
|
||||||
// router.push(current);
|
router.push(current);
|
||||||
// }
|
}
|
||||||
// }, [current, pathname, router]);
|
}, [current, pathname, router]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -118,11 +118,13 @@ function UsersTable({ users }: UserInterface) {
|
|||||||
},
|
},
|
||||||
onFilterDropdownOpenChange: (visible) => {
|
onFilterDropdownOpenChange: (visible) => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
|
// @ts-ignore
|
||||||
setTimeout(() => searchInput.current?.select(), 100);
|
setTimeout(() => searchInput.current?.select(), 100);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
render: (text) =>
|
render: (text) =>
|
||||||
searchedColumn === dataIndex ? (
|
searchedColumn === dataIndex ? (
|
||||||
|
// @ts-ignore
|
||||||
<Highlighter
|
<Highlighter
|
||||||
highlightStyle={{ backgroundColor: "#ffc069", padding: 0 }}
|
highlightStyle={{ backgroundColor: "#ffc069", padding: 0 }}
|
||||||
searchWords={[searchText]}
|
searchWords={[searchText]}
|
||||||
|
87
lib/auth.ts
87
lib/auth.ts
@ -1,10 +1,11 @@
|
|||||||
import { getServerSession, type NextAuthOptions } from "next-auth";
|
import {getServerSession, type NextAuthOptions, Theme} from "next-auth";
|
||||||
import GitHubProvider from "next-auth/providers/github";
|
import GitHubProvider from "next-auth/providers/github";
|
||||||
import EmailProvider from "next-auth/providers/email";
|
import EmailProvider from "next-auth/providers/email";
|
||||||
import CredentialsProvider from "next-auth/providers/credentials";
|
import CredentialsProvider from "next-auth/providers/credentials";
|
||||||
import { PrismaAdapter } from "@next-auth/prisma-adapter";
|
import { PrismaAdapter } from "@next-auth/prisma-adapter";
|
||||||
import prisma from "@/lib/prisma";
|
import prisma from "@/lib/prisma";
|
||||||
import { isEmail, isName } from "@/lib/auth_list";
|
import { isEmail, isName } from "@/lib/auth_list";
|
||||||
|
import { createTransport } from "nodemailer";
|
||||||
|
|
||||||
const SECURE_COOKIES:boolean = !!process.env.SECURE_COOKIES;
|
const SECURE_COOKIES:boolean = !!process.env.SECURE_COOKIES;
|
||||||
|
|
||||||
@ -31,13 +32,36 @@ export const authOptions: NextAuthOptions = {
|
|||||||
EmailProvider({
|
EmailProvider({
|
||||||
server: {
|
server: {
|
||||||
host: process.env.EMAIL_SERVER_HOST,
|
host: process.env.EMAIL_SERVER_HOST,
|
||||||
port: process.env.EMAIL_SERVER_PORT,
|
port: parseInt(process.env.EMAIL_SERVER_PORT ?? "0"),
|
||||||
auth: {
|
auth: {
|
||||||
user: process.env.EMAIL_SERVER_USER,
|
user: process.env.EMAIL_SERVER_USER,
|
||||||
pass: process.env.EMAIL_SERVER_PASSWORD,
|
pass: process.env.EMAIL_SERVER_PASSWORD,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
from: process.env.EMAIL_FROM,
|
from: process.env.EMAIL_FROM,
|
||||||
|
async sendVerificationRequest({
|
||||||
|
identifier: email,
|
||||||
|
url,
|
||||||
|
provider: { server, from, name },
|
||||||
|
theme,
|
||||||
|
}) {
|
||||||
|
/* your function */
|
||||||
|
console.log('send mail,', email, url, server, from, )
|
||||||
|
const { host } = new URL(url)
|
||||||
|
const transport = createTransport(server)
|
||||||
|
const result = await transport.sendMail({
|
||||||
|
to: email,
|
||||||
|
from: from,
|
||||||
|
subject: `Sign in to ${host}`,
|
||||||
|
html: email_html({ url, host, theme }),
|
||||||
|
})
|
||||||
|
const failed = result.rejected.concat(result.pending).filter(Boolean)
|
||||||
|
console.log('[result],', result)
|
||||||
|
if (failed.length) {
|
||||||
|
throw new Error(`Email(s) (${failed.join(", ")}) could not be sent`)
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
CredentialsProvider({
|
CredentialsProvider({
|
||||||
// The name to display on the sign in form (e.g. "Sign in with...")
|
// The name to display on the sign in form (e.g. "Sign in with...")
|
||||||
@ -232,3 +256,62 @@ function cleanUpString(input: string): string {
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Email HTML body
|
||||||
|
* Insert invisible space into domains from being turned into a hyperlink by email
|
||||||
|
* clients like Outlook and Apple mail, as this is confusing because it seems
|
||||||
|
* like they are supposed to click on it to sign in.
|
||||||
|
*
|
||||||
|
* @note We don't add the email address to avoid needing to escape it, if you do, remember to sanitize it!
|
||||||
|
*/
|
||||||
|
function email_html(params: { url: string, host: string, theme: Theme }) {
|
||||||
|
const { url, host, theme } = params
|
||||||
|
|
||||||
|
const escapedHost = host.replace(/\./g, "​.")
|
||||||
|
|
||||||
|
const brandColor = theme.brandColor || "#346df1"
|
||||||
|
const color = {
|
||||||
|
background: "#f9f9f9",
|
||||||
|
text: "#444",
|
||||||
|
mainBackground: "#fff",
|
||||||
|
buttonBackground: brandColor,
|
||||||
|
buttonBorder: brandColor,
|
||||||
|
buttonText: theme.buttonText || "#fff",
|
||||||
|
}
|
||||||
|
|
||||||
|
return `
|
||||||
|
<body style="background: ${color.background};">
|
||||||
|
<table width="100%" border="0" cellspacing="20" cellpadding="0"
|
||||||
|
style="background: ${color.mainBackground}; max-width: 600px; margin: auto; border-radius: 10px;">
|
||||||
|
<tr>
|
||||||
|
<td align="center"
|
||||||
|
style="padding: 10px 0px; font-size: 22px; font-family: Helvetica, Arial, sans-serif; color: ${color.text};">
|
||||||
|
Sign in to <strong>${escapedHost}</strong>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center" style="padding: 20px 0;">
|
||||||
|
<table border="0" cellspacing="0" cellpadding="0">
|
||||||
|
<tr>
|
||||||
|
<td align="center" style="border-radius: 5px;" bgcolor="${color.buttonBackground}"><a href="${url}"
|
||||||
|
target="_blank"
|
||||||
|
style="font-size: 18px; font-family: Helvetica, Arial, sans-serif; color: ${color.buttonText}; text-decoration: none; border-radius: 5px; padding: 10px 20px; border: 1px solid ${color.buttonBorder}; display: inline-block; font-weight: bold;">Sign
|
||||||
|
in</a></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td align="center"
|
||||||
|
style="padding: 0px 0px 10px 0px; font-size: 16px; line-height: 22px; font-family: Helvetica, Arial, sans-serif; color: ${color.text};">
|
||||||
|
If you did not request this email you can safely ignore it.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
@ -60,6 +60,7 @@
|
|||||||
"@types/bcryptjs": "^2.4.6",
|
"@types/bcryptjs": "^2.4.6",
|
||||||
"@types/cookie": "^0.6.0",
|
"@types/cookie": "^0.6.0",
|
||||||
"@types/node": "^20.11.10",
|
"@types/node": "^20.11.10",
|
||||||
|
"@types/nodemailer": "^6.4.14",
|
||||||
"@types/react": "^18.2.48",
|
"@types/react": "^18.2.48",
|
||||||
"@types/react-dom": "^18.2.7",
|
"@types/react-dom": "^18.2.7",
|
||||||
"@types/react-highlight-words": "^0.16.7",
|
"@types/react-highlight-words": "^0.16.7",
|
||||||
|
Loading…
Reference in New Issue
Block a user