Compare commits

...

6 Commits

Author SHA1 Message Date
bc44e40f56 支持删除 CookieTarget 2024-09-22 17:12:52 +08:00
bb63529fe8 支持添加 CookieTarget 2024-09-22 17:02:10 +08:00
75a55c009a 添加 CookieTarget 页面 2024-09-22 14:33:56 +08:00
4c20e47399 添加 Cookie 增删查 功能 2024-09-22 13:52:51 +08:00
db4b848e3f 添加 Cookie 界面 2024-09-22 13:09:16 +08:00
54c9020c36 添加cookie页面 2024-09-22 00:28:26 +08:00
11 changed files with 478 additions and 5 deletions

View File

@ -9,6 +9,8 @@ import SubscribeManager from './features/subsribeConfigManager/SubscribeManager'
import WeightConfig from './features/weightConfig/WeightManager';
import Home from './pages/Home';
import Unauthed from './pages/Unauthed';
import CookieManager from './features/cookieManager/CookieManager';
import CookieTargetManager from './features/cookieTargetManager/CookieTargetManager';
function App() {
const dispatch = useAppDispatch();
@ -46,6 +48,14 @@ function App() {
path: 'weight',
element: <WeightConfig />,
},
{
path: 'cookie',
element: <CookieManager />,
},
{
path: 'cookie/:cookieId',
element: <CookieTargetManager />,
},
],
},
], { basename: '/bison' });

View File

@ -17,6 +17,7 @@ import globalConfReducer from '../features/globalConf/globalConfSlice';
import { subscribeApi } from '../features/subsribeConfigManager/subscribeConfigSlice';
import { targetNameApi } from '../features/targetName/targetNameSlice';
import { weightApi } from '../features/weightConfig/weightConfigSlice';
import { cookieApi, cookieTargetApi } from '../features/cookieManager/cookieConfigSlice';
const rootReducer = combineReducers({
auth: authReducer,
@ -24,6 +25,8 @@ const rootReducer = combineReducers({
[subscribeApi.reducerPath]: subscribeApi.reducer,
[weightApi.reducerPath]: weightApi.reducer,
[targetNameApi.reducerPath]: targetNameApi.reducer,
[cookieApi.reducerPath]: cookieApi.reducer,
[cookieTargetApi.reducerPath]: cookieTargetApi.reducer,
});
const persistConfig = {
@ -43,7 +46,10 @@ export const store = configureStore({
})
.concat(subscribeApi.middleware)
.concat(weightApi.middleware)
.concat(targetNameApi.middleware),
.concat(targetNameApi.middleware)
.concat(cookieApi.middleware)
.concat(cookieTargetApi.middleware),
});
export const persistor = persistStore(store);

View File

@ -0,0 +1,115 @@
import React, { useState } from 'react';
import {
Button,
Card, Descriptions, Grid, List, Popconfirm, Popover, Typography,
} from '@arco-design/web-react';
import { Link } from 'react-router-dom';
import { selectSiteConf } from '../globalConf/globalConfSlice';
import { useAppSelector } from '../../app/hooks';
import { Cookie, SiteConfig } from '../../utils/type';
import { useGetCookiesQuery, useDeleteCookieMutation } from './cookieConfigSlice';
import CookieModal from './CookieModal';
interface CookieSite {
site: SiteConfig;
cookies: Cookie[];
}
export default function CookieManager() {
const siteConf = useAppSelector(selectSiteConf);
const { data: cookieDict } = useGetCookiesQuery();
const cookiesList = cookieDict ? Object.values(cookieDict) : [];
const cookieSite = Object.values(siteConf).filter((site) => site.enable_cookie);
const cookieSiteList: CookieSite[] = cookieSite.map((site) => ({
site,
cookies: cookiesList.filter((cookie) => cookie.site_name === site.name),
}));
const [showModal, setShowModal] = useState(false);
const [siteName, setSiteName] = useState('');
const [deleteCookie] = useDeleteCookieMutation();
const handleAddCookie = (newSiteName: string) => () => {
console.log(newSiteName);
setSiteName(newSiteName);
setShowModal(true);
};
const handleDelCookie = (cookieId: string) => () => {
console.log(cookieId);
deleteCookie({
cookieId,
});
};
return (
<>
<Typography.Title heading={4} style={{ margin: '15px' }}>Cookie </Typography.Title>
<Grid.Row gutter={20}>
{cookieSiteList && cookieSiteList.map(({ cookies, site }) => (
<Grid.Col xs={24} sm={12} md={8} lg={6} key={site.name} style={{ margin: '1em 0' }}>
<Card
title={site.name}
extra={(
<Button
type="primary"
onClick={handleAddCookie(site.name)}
>
</Button>
)}
>
{cookies.map((cookie) => (
<List>
<List.Item key={cookie.id}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<Popover
key={cookie.id}
title={cookie.friendly_name}
content={(
<Descriptions
column={1}
title="Cookie 详情"
data={Object.entries(cookie).map((entry) => ({
label: entry[0].toString(),
value: typeof (entry[1]) === 'object' ? JSON.stringify(entry[1]) : entry[1].toString(),
}))}
/>
)}
>
{cookie.friendly_name}
</Popover>
<div>
<Link to={`/home/cookie/${cookie.id}`}>
<Button
type="primary"
style={{ marginRight: '10px' }}
>
</Button>
</Link>
<Popconfirm
title={`确定删除 Cookie ${cookie.friendly_name} `}
onOk={handleDelCookie(cookie.id.toString())}
>
<Button type="primary" status="danger"></Button>
</Popconfirm>
</div>
</div>
</List.Item>
</List>
))}
</Card>
</Grid.Col>
))}
</Grid.Row>
<CookieModal visible={showModal} setVisible={setShowModal} siteName={siteName} />
</>
);
}

View File

@ -0,0 +1,54 @@
import React, { useState } from 'react';
import { Form, Input, Modal } from '@arco-design/web-react';
import { useNewCookieMutation } from './cookieConfigSlice';
interface CookieModalProps {
visible: boolean;
setVisible: (arg0: boolean) => void;
siteName: string;
}
function CookieModal({ visible, setVisible, siteName }: CookieModalProps) {
const FormItem = Form.Item;
const [content, setContent] = useState<string>('');
const [confirmLoading, setConfirmLoading] = useState(false);
const [newCoookie] = useNewCookieMutation();
const onSubmit = () => {
const postPromise: ReturnType<typeof newCoookie> = newCoookie({ siteName, content });
setConfirmLoading(true);
postPromise.then(() => {
setConfirmLoading(false);
setVisible(false);
setContent('');
});
};
return (
<Modal
title="添加 Cookie"
visible={visible}
onCancel={() => setVisible(false)}
confirmLoading={confirmLoading}
onOk={onSubmit}
style={{ maxWidth: '90vw' }}
>
<Form autoComplete="off">
<FormItem label="Site Name" required>
<Input placeholder="Please enter site name" value={siteName} disabled />
</FormItem>
<FormItem label="Content" required>
<Input.TextArea
placeholder="Please enter content"
value={content}
onChange={setContent}
/>
</FormItem>
</Form>
</Modal>
);
}
export default CookieModal;

View File

@ -0,0 +1,66 @@
import { createApi } from '@reduxjs/toolkit/query/react';
import {
StatusResp, Cookie, NewCookieParam,
DelCookieParam, CookieTarget, NewCookieTargetParam, DelCookieTargetParam,
} from '../../utils/type';
import { baseQueryWithAuth } from '../auth/authQuery';
export const cookieApi = createApi({
reducerPath: 'cookie',
baseQuery: baseQueryWithAuth,
tagTypes: ['Cookie'],
endpoints: (builder) => ({
getCookies: builder.query<Cookie, void>({
query: () => '/cookie',
providesTags: ['Cookie'],
}),
newCookie: builder.mutation<StatusResp, NewCookieParam>({
query: ({ siteName, content }) => ({
method: 'POST',
url: `/cookie?site_name=${siteName}&content=${content}`,
}),
invalidatesTags: ['Cookie'],
}),
deleteCookie: builder.mutation<StatusResp, DelCookieParam>({
query: ({ cookieId }) => ({
method: 'DELETE',
url: `/cookie/${cookieId}`,
}),
invalidatesTags: ['Cookie'],
}),
}),
});
export const {
useGetCookiesQuery, useNewCookieMutation, useDeleteCookieMutation,
} = cookieApi;
export const cookieTargetApi = createApi({
reducerPath: 'cookieTarget',
baseQuery: baseQueryWithAuth,
tagTypes: ['CookieTarget'],
endpoints: (builder) => ({
getCookieTargets: builder.query<CookieTarget[], {cookieId: number }>({
query: (cookieId) => `/cookie_target?cookie_id=${cookieId}`,
providesTags: ['CookieTarget'],
}),
newCookieTarget: builder.mutation<StatusResp, NewCookieTargetParam>({
query: ({ platformName, target, cookieId }) => ({
method: 'POST',
url: `/cookie_target?platform_name=${platformName}&target=${encodeURIComponent(target)}&cookie_id=${cookieId}`,
}),
invalidatesTags: ['CookieTarget'],
}),
deleteCookieTarget: builder.mutation<StatusResp, DelCookieTargetParam>({
query: ({ platformName, target, cookieId }) => ({
method: 'DELETE',
url: `/cookie_target?platform_name=${platformName}&target=${encodeURIComponent(target)}&cookie_id=${cookieId}`,
}),
invalidatesTags: ['CookieTarget'],
}),
}),
});
export const {
useGetCookieTargetsQuery, useNewCookieTargetMutation, useDeleteCookieTargetMutation,
} = cookieTargetApi;

View File

@ -0,0 +1,79 @@
import React, { useState } from 'react';
import { useParams } from 'react-router-dom';
import {
Button, Empty, Space, Table, Typography,
} from '@arco-design/web-react';
import { useDeleteCookieTargetMutation, useGetCookieTargetsQuery } from '../cookieManager/cookieConfigSlice';
import { CookieTarget } from '../../utils/type';
import CookieTargetModal from './CookieTargetModal';
export default function () {
const { cookieId } = useParams();
const { data: cookieTargets } = useGetCookieTargetsQuery(cookieId);
console.log(cookieTargets);
const [showModal, setShowModal] = useState(false);
const [deleteCookieTarget] = useDeleteCookieTargetMutation();
const handleAdd = () => {
setShowModal(true);
};
const handleDelete = (record: CookieTarget) => () => {
deleteCookieTarget({
cookieId,
target: record.target.target,
platformName: record.target.platform_name,
});
};
const columns = [
{
title: '平台名称',
dataIndex: 'target.platform_name',
},
{
title: '订阅名称',
dataIndex: 'target.target_name',
},
{
title: 'Cookie ID',
dataIndex: 'cookie_id',
},
{
title: '操作',
dataIndex: 'op',
render: (_: null, record: CookieTarget) => (
<Space size="small">
<Button type="text" status="danger" onClick={handleDelete(record)}></Button>
</Space>
),
},
];
if (cookieId) {
return (
<>
<span>
<Typography.Title heading={3}>{`Cookie ${cookieId}`}</Typography.Title>
</span>
<Button style={{ width: '90px', margin: '20px 10px' }} type="primary" onClick={handleAdd}></Button>
<Table
columns={columns}
data={cookieTargets}
rowKey={(record: CookieTarget) => `${record.target.platform_name}-${record.target.target}`}
scroll={{ x: true }}
/>
{
cookieTargets && cookieTargets.length > 0
&& (
<CookieTargetModal
visible={showModal}
setVisible={setShowModal}
cookieId={cookieId}
/>
)
}
</>
);
}
return <Empty />;
}

View File

@ -0,0 +1,59 @@
import React
from 'react';
import { Modal, Select } from '@arco-design/web-react';
import { SubscribeGroupDetail } from '../../utils/type';
import { useNewCookieTargetMutation } from '../cookieManager/cookieConfigSlice';
import { useGetSubsQuery } from '../subsribeConfigManager/subscribeConfigSlice';
interface SubscribeModalProp {
visible: boolean;
setVisible: (arg0: boolean) => void;
cookieId: number;
}
export default function ({ visible, setVisible, cookieId }: SubscribeModalProp) {
const [newCookieTarget] = useNewCookieTargetMutation();
const { data: subs } = useGetSubsQuery();
const pureSubs = subs ? Object.values(subs)
.reduce((pv:Array, cv:SubscribeGroupDetail) => pv.concat(cv.subscribes), []) : [];
const [index, setIndex] = React.useState(-1);
const handleSubmit = (idx:number) => {
const postPromise: ReturnType<typeof newCookieTarget> = newCookieTarget({
cookieId,
platformName: pureSubs[idx].platformName,
target: pureSubs[idx].target,
});
postPromise.then(() => {
setVisible(false);
});
};
const { Option } = Select;
return (
<Modal
title="关联 Cookie"
visible={visible}
onCancel={() => setVisible(false)}
onOk={() => handleSubmit(index)}
>
<Select
placeholder="选择要关联的 target"
style={{ width: '100%' }}
onChange={setIndex}
>
{
pureSubs.map((sub, idx) => (
<Option
key={JSON.stringify(sub)}
value={idx}
>
{JSON.stringify(sub)}
</Option>
))
}
</Select>
</Modal>
);
}

View File

@ -6,6 +6,7 @@ import { globalConfUrl } from '../../utils/urls';
const initialState = {
loaded: false,
platformConf: {},
siteConf: {},
} as GlobalConf;
export const loadGlobalConf = createAsyncThunk(
@ -24,6 +25,7 @@ export const globalConfSlice = createSlice({
builder
.addCase(loadGlobalConf.fulfilled, (state, payload) => {
state.platformConf = payload.payload.platformConf;
state.siteConf = payload.payload.siteConf;
state.loaded = true;
});
},
@ -33,3 +35,4 @@ export default globalConfSlice.reducer;
export const selectGlobalConfLoaded = (state: RootState) => state.globalConf.loaded;
export const selectPlatformConf = (state: RootState) => state.globalConf.platformConf;
export const selectSiteConf = (state: RootState) => state.globalConf.siteConf;

View File

@ -1,6 +1,6 @@
import React, { ReactNode, useEffect, useState } from 'react';
import { Breadcrumb, Layout, Menu } from '@arco-design/web-react';
import { IconRobot, IconDashboard } from '@arco-design/web-react/icon';
import { IconRobot, IconDashboard, IconUser } from '@arco-design/web-react/icon';
import './Home.css';
// import SubscribeManager from '../features/subsribeConfigManager/SubscribeManager';
import {
@ -23,6 +23,12 @@ export default function Home() {
if (path !== '/home/groups' && !path.startsWith('/home/groups/') && path !== '/home/weight') {
navigate('/home/groups');
}
if (path === '/home/cookie') {
navigate('/home/cookie');
}
if (path.startsWith('/home/cookie/')) {
navigate(path);
}
}, [path]);
let currentKey = '';
@ -30,6 +36,8 @@ export default function Home() {
currentKey = 'groups';
} else if (path.startsWith('/home/groups/')) {
currentKey = 'subs';
} else if (path.startsWith('/home/cookie/')) {
currentKey = 'cookie';
}
const [selectedTab, changeSelectTab] = useState(currentKey);
@ -40,6 +48,8 @@ export default function Home() {
navigate('/home/groups');
} else if (tab === 'weight') {
navigate('/home/weight');
} else if (tab === 'cookie') {
navigate('/home/cookie');
}
};
@ -80,6 +90,17 @@ export default function Home() {
</Breadcrumb.Item>
</Breadcrumb>
);
} else if (path.startsWith('/home/cookie')) {
breadcrumbContent = (
<Breadcrumb style={{ margin: '16px 0' }}>
<Breadcrumb.Item>
<Link to="/home/cookie">
<IconUser />
Cookie
</Link>
</Breadcrumb.Item>
</Breadcrumb>
);
}
return (
<Layout className="layout-collapse-demo">
@ -105,6 +126,10 @@ export default function Home() {
<IconDashboard />
</Menu.Item>
<Menu.Item key="cookie">
<IconUser />
Cookie
</Menu.Item>
</Menu>
</Layout.Sider>
<Layout.Content style={{ padding: '0 1em' }}>

View File

@ -4,8 +4,10 @@ export interface TokenResp {
id: number;
name: string;
}
export interface GlobalConf {
platformConf: AllPlatformConf;
siteConf: AllSiteConf;
loaded: boolean;
}
@ -13,6 +15,10 @@ export interface AllPlatformConf {
[idx: string]: PlatformConfig;
}
export interface AllSiteConf {
[idx: string]: SiteConfig;
}
export interface CategoryConfig {
[idx: number]: string;
}
@ -25,6 +31,11 @@ export interface PlatformConfig {
hasTarget: boolean;
}
export interface SiteConfig {
name: string
enable_cookie: string
}
export interface SubscribeConfig {
platformName: string;
target: string;
@ -69,3 +80,47 @@ export interface PlatformWeightConfigResp {
platform_name: string;
weight: WeightConfig;
}
export interface Target {
platform_name: string;
target_name: string;
target: string;
}
export interface Cookie {
id: number;
site_name: string;
friendly_name: string;
last_usage: Date;
status: string;
cd_milliseconds: number;
is_universal: boolean;
is_anonymous: boolean;
tags: { [key: string]: string };
}
export interface CookieTarget {
target: Target;
cookieId: number;
}
export interface NewCookieParam {
siteName: string
content: string
}
export interface DelCookieParam {
cookieId: string
}
export interface NewCookieTargetParam {
platformName: string;
target: string;
cookieId: number;
}
export interface DelCookieTargetParam {
platformName: string;
target: string;
cookieId: number;
}

View File

@ -210,10 +210,11 @@ async def update_weigth_config(platformName: str, target: str, weight_config: We
@router.get("/cookie", dependencies=[Depends(check_is_superuser)])
async def get_cookie(site_name: str = None, target: str = None) -> list[Cookie]:
async def get_cookie(site_name: str | None = None, target: str | None = None) -> list[Cookie]:
cookies_in_db = await config.get_cookie(site_name, is_anonymous=False)
client_mgr = cast(CookieClientManager, site_manager[site_name].client_mgr)
friendly_names = [await client_mgr.get_cookie_friendly_name(x) for x in cookies_in_db]
# client_mgr = cast(CookieClientManager, site_manager[site_name].client_mgr)
# friendly_names = [await client_mgr.get_cookie_friendly_name(x) for x in cookies_in_db]
friendly_names = [x.content[:10] for x in cookies_in_db]
return [
Cookie(
id=cookies_in_db[i].id,