Compare commits
4
Commits
test
...
c6643f2bc9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c6643f2bc9 | ||
|
|
8901118bd2 | ||
|
|
db17192e55 | ||
|
|
a1dd1ec861 |
@@ -1,6 +1,7 @@
|
|||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch: {}
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'main'
|
- 'main'
|
||||||
@@ -117,3 +118,20 @@ jobs:
|
|||||||
remote_path: releases/${{ steps.version.outputs.version }}.tar.gz
|
remote_path: releases/${{ steps.version.outputs.version }}.tar.gz
|
||||||
accelerate: true
|
accelerate: true
|
||||||
clean: false
|
clean: false
|
||||||
|
|
||||||
|
trigger-backend:
|
||||||
|
needs: build-publish
|
||||||
|
if: (github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/')) || github.event_name == 'workflow_dispatch'
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- name: Dispatch backend build
|
||||||
|
uses: peter-evans/repository-dispatch@v3
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.DISPATCH_PAT }}
|
||||||
|
repository: gpustack/gpustack
|
||||||
|
event-type: ui-built
|
||||||
|
client-payload: |
|
||||||
|
{
|
||||||
|
"ref": "${{ github.ref }}",
|
||||||
|
"sha": "${{ github.sha }}"
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,3 +3,47 @@
|
|||||||
This project keeps a single source of truth for agent/contributor conventions in [`CLAUDE.md`](./CLAUDE.md). **Read [`CLAUDE.md`](./CLAUDE.md) and follow it.**
|
This project keeps a single source of truth for agent/contributor conventions in [`CLAUDE.md`](./CLAUDE.md). **Read [`CLAUDE.md`](./CLAUDE.md) and follow it.**
|
||||||
|
|
||||||
@CLAUDE.md
|
@CLAUDE.md
|
||||||
|
|
||||||
|
## Downstream fork workflow
|
||||||
|
|
||||||
|
This repo is a **downstream fork** that customizes the product appearance on top of
|
||||||
|
upstream `gpustack/gpustack-ui`. The mirror chain is:
|
||||||
|
|
||||||
|
```
|
||||||
|
upstream https://github.com/gpustack/gpustack-ui.git
|
||||||
|
| (fetch)
|
||||||
|
origin ssh://git@192.168.0.23:11022/root/gpustack-ui.git (private registry, also https://git.digiman.live)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Goal
|
||||||
|
|
||||||
|
Track upstream releases. When upstream changes, we pull it in, then apply our own
|
||||||
|
appearance/customization changes on a dedicated branch so we keep our look-and-feel
|
||||||
|
on top of the latest upstream product.
|
||||||
|
|
||||||
|
### Branch naming
|
||||||
|
|
||||||
|
Customization work lives on `v<upstream-version>-lofyer` branches (e.g.
|
||||||
|
`v2.2.0-lofyer`). Each time upstream ships a new version we want to follow, create a
|
||||||
|
new `v<version>-lofyer` branch from the corresponding upstream tag/branch and re-apply
|
||||||
|
(or rebase) our customizations onto it.
|
||||||
|
|
||||||
|
### Syncing from upstream
|
||||||
|
|
||||||
|
`scripts/sync-github` mirrors upstream into the private `origin` (full mirror, force
|
||||||
|
push of all branches + tags). It auto-configures the `upstream` remote on first run.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# preview only, no push
|
||||||
|
DRY_RUN=1 ./scripts/sync-github
|
||||||
|
|
||||||
|
# real sync (force-pushes every upstream branch + tag to origin)
|
||||||
|
./scripts/sync-github
|
||||||
|
|
||||||
|
# also delete origin branches that no longer exist upstream (true mirror, destructive)
|
||||||
|
PRUNE_BRANCHES=1 ./scripts/sync-github
|
||||||
|
```
|
||||||
|
|
||||||
|
Env knobs: `UPSTREAM_URL`, `ORIGIN_REMOTE`, `UPSTREAM_REMOTE`, `PRUNE_BRANCHES`,
|
||||||
|
`DRY_RUN`. After syncing, branch a fresh `v<version>-lofyer` off the updated upstream
|
||||||
|
ref and apply the appearance changes there.
|
||||||
|
|||||||
Executable
+83
@@ -0,0 +1,83 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# sync-github: 从上游 GitHub 拉取最新源码,并全量镜像同步到 192.168.0.23 私有源。
|
||||||
|
#
|
||||||
|
# 默认行为(全量镜像 / 强制):
|
||||||
|
# 1. 确保存在 upstream remote 指向 GitHub(不存在则自动添加,URL 不一致则更新)。
|
||||||
|
# 2. 从 upstream 抓取所有分支与 tag(--prune 清理已删除的远端引用)。
|
||||||
|
# 3. 将上游每个分支强制推送到私有源同名分支(force push)。
|
||||||
|
# 4. 将上游所有 tag 强制推送到私有源。
|
||||||
|
#
|
||||||
|
# 可用环境变量:
|
||||||
|
# UPSTREAM_URL 上游 GitHub 仓库地址(默认 https://github.com/gpustack/gpustack-ui.git)
|
||||||
|
# ORIGIN_REMOTE 私有源 remote 名称(默认 origin)
|
||||||
|
# UPSTREAM_REMOTE 上游 remote 名称(默认 upstream)
|
||||||
|
# PRUNE_BRANCHES 设为 1 时,删除私有源上「上游已不存在」的分支(真·镜像,破坏性,默认关闭)
|
||||||
|
# DRY_RUN 设为 1 时,仅打印将要执行的推送动作,不实际推送
|
||||||
|
#
|
||||||
|
set -e
|
||||||
|
|
||||||
|
UPSTREAM_URL="${UPSTREAM_URL:-https://github.com/gpustack/gpustack-ui.git}"
|
||||||
|
ORIGIN_REMOTE="${ORIGIN_REMOTE:-origin}"
|
||||||
|
UPSTREAM_REMOTE="${UPSTREAM_REMOTE:-upstream}"
|
||||||
|
PRUNE_BRANCHES="${PRUNE_BRANCHES:-0}"
|
||||||
|
DRY_RUN="${DRY_RUN:-0}"
|
||||||
|
|
||||||
|
log() { echo -e "\033[1;34m[sync-github]\033[0m $*"; }
|
||||||
|
warn() { echo -e "\033[1;33m[sync-github]\033[0m $*" >&2; }
|
||||||
|
|
||||||
|
run() {
|
||||||
|
if [[ "${DRY_RUN}" == "1" ]]; then
|
||||||
|
echo " (dry-run) git $*"
|
||||||
|
else
|
||||||
|
git "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# 1. 确保 upstream remote 指向 GitHub。
|
||||||
|
if git remote get-url "${UPSTREAM_REMOTE}" >/dev/null 2>&1; then
|
||||||
|
current_url=$(git remote get-url "${UPSTREAM_REMOTE}")
|
||||||
|
if [[ "${current_url}" != "${UPSTREAM_URL}" ]]; then
|
||||||
|
log "更新 ${UPSTREAM_REMOTE} 地址: ${current_url} -> ${UPSTREAM_URL}"
|
||||||
|
git remote set-url "${UPSTREAM_REMOTE}" "${UPSTREAM_URL}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log "添加 upstream remote: ${UPSTREAM_REMOTE} -> ${UPSTREAM_URL}"
|
||||||
|
git remote add "${UPSTREAM_REMOTE}" "${UPSTREAM_URL}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
origin_url=$(git remote get-url "${ORIGIN_REMOTE}")
|
||||||
|
log "上游 (拉取): ${UPSTREAM_URL}"
|
||||||
|
log "私有源 (推送): ${origin_url}"
|
||||||
|
|
||||||
|
# 2. 抓取上游所有分支与 tag。
|
||||||
|
log "抓取上游分支与 tag..."
|
||||||
|
git fetch --prune --tags "${UPSTREAM_REMOTE}"
|
||||||
|
|
||||||
|
# 3. 逐个分支强制推送到私有源。
|
||||||
|
log "强制同步分支到私有源..."
|
||||||
|
upstream_branches=$(git for-each-ref --format='%(refname:strip=3)' "refs/remotes/${UPSTREAM_REMOTE}/" | grep -v '^HEAD$')
|
||||||
|
|
||||||
|
for branch in ${upstream_branches}; do
|
||||||
|
log " -> ${branch}"
|
||||||
|
run push --force "${ORIGIN_REMOTE}" \
|
||||||
|
"refs/remotes/${UPSTREAM_REMOTE}/${branch}:refs/heads/${branch}"
|
||||||
|
done
|
||||||
|
|
||||||
|
# 4. 强制同步所有 tag。
|
||||||
|
log "强制同步 tag 到私有源..."
|
||||||
|
run push --force --tags "${ORIGIN_REMOTE}"
|
||||||
|
|
||||||
|
# 5. 可选:删除私有源上、上游已不存在的分支(真·镜像)。
|
||||||
|
if [[ "${PRUNE_BRANCHES}" == "1" ]]; then
|
||||||
|
warn "PRUNE_BRANCHES=1:将删除私有源上上游已不存在的分支"
|
||||||
|
origin_branches=$(git ls-remote --heads "${ORIGIN_REMOTE}" | sed 's@.*refs/heads/@@')
|
||||||
|
for branch in ${origin_branches}; do
|
||||||
|
if ! echo "${upstream_branches}" | grep -qx "${branch}"; then
|
||||||
|
warn " 删除私有源分支: ${branch}"
|
||||||
|
run push "${ORIGIN_REMOTE}" --delete "${branch}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "同步完成。"
|
||||||
@@ -96,3 +96,12 @@ export interface UsageMeta {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type FilterOptionType = Omit<UsageFilterItem, 'label' | 'deleted'>;
|
export type FilterOptionType = Omit<UsageFilterItem, 'label' | 'deleted'>;
|
||||||
|
|
||||||
|
// The full breakdown filter set (route / user / api_key). Every breakdown
|
||||||
|
// table sends all active dimensions — matching the trend chart — so e.g. a
|
||||||
|
// user filter narrows the Models table too, not only the Users table.
|
||||||
|
export type BreakdownFilters = {
|
||||||
|
routes?: FilterOptionType[];
|
||||||
|
users?: FilterOptionType[];
|
||||||
|
api_keys?: FilterOptionType[];
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { exportJsonToExcel } from '@gpustack/core-ui/excel';
|
import { exportJsonToExcel } from '@gpustack/core-ui/excel';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import _ from 'lodash';
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { GroupOption } from '../config';
|
import { GroupOption } from '../config';
|
||||||
import { BreakdownItem, UsageFilterItem } from '../config/types';
|
import { BreakdownItem, UsageFilterItem } from '../config/types';
|
||||||
import useQueryTimeSeriesData from '../services/use-query-timeseries-data';
|
import useQueryTimeSeriesData from '../services/use-query-timeseries-data';
|
||||||
@@ -181,10 +182,17 @@ export const useUsageFilters = ({
|
|||||||
return filters;
|
return filters;
|
||||||
};
|
};
|
||||||
|
|
||||||
const filters = useMemo(
|
// Keep a stable reference while the content is unchanged. ``buildFilters``
|
||||||
() => buildFilters(commonFilters),
|
// returns a fresh object every render — and again when the meta options
|
||||||
[commonFilters, routeOptions, userOptions, apiKeyOptions]
|
// resolve after mount — which would otherwise retrigger every breakdown
|
||||||
);
|
// table's fetch effect a second time on first load. Only a real selection
|
||||||
|
// change (or options resolving a previously-selected id) should swap it.
|
||||||
|
const filtersRef = useRef<ReturnType<typeof buildFilters>>({});
|
||||||
|
const nextFilters = buildFilters(commonFilters);
|
||||||
|
if (!_.isEqual(nextFilters, filtersRef.current)) {
|
||||||
|
filtersRef.current = nextFilters;
|
||||||
|
}
|
||||||
|
const filters = filtersRef.current;
|
||||||
|
|
||||||
const fetchData = (
|
const fetchData = (
|
||||||
currentSelectedFilters = commonFilters,
|
currentSelectedFilters = commonFilters,
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ import { Col, Row } from 'antd';
|
|||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import ResourceFilterBar from '../components/resource-filter-bar';
|
import ResourceFilterBar from '../components/resource-filter-bar';
|
||||||
import useResourceMeta from '../hooks/use-resource-meta';
|
import { FilterOptionType } from '../config/types';
|
||||||
|
import useResourceMeta, { SelectOption } from '../hooks/use-resource-meta';
|
||||||
|
import useQueryUsageMetaData from '../services/use-query-meta-data';
|
||||||
import {
|
import {
|
||||||
bucketKey,
|
bucketKey,
|
||||||
generateBucketRange,
|
generateBucketRange,
|
||||||
@@ -231,7 +233,38 @@ const SummaryTab: React.FC = () => {
|
|||||||
selectedUsers: []
|
selectedUsers: []
|
||||||
});
|
});
|
||||||
const { start, end, selectedUsers } = queryParams;
|
const { start, end, selectedUsers } = queryParams;
|
||||||
const { creators: userOptions } = useResourceMeta(scope);
|
const { creators: resourceUsers } = useResourceMeta(scope);
|
||||||
|
const { detailData: tokenMeta, fetchData: fetchTokenMeta } =
|
||||||
|
useQueryUsageMetaData();
|
||||||
|
|
||||||
|
// The user filter unions two sources: resource creators (GPU / storage
|
||||||
|
// usage) and the token-usage users (/usage/meta) — a user may appear in only
|
||||||
|
// one. Deduped by user id. The token meta also carries the per-user identity
|
||||||
|
// the token-series endpoint filters on (see ``tokenUserById``).
|
||||||
|
const userOptions = useMemo<SelectOption[]>(() => {
|
||||||
|
const map = new Map<number, SelectOption>();
|
||||||
|
resourceUsers.forEach((u) =>
|
||||||
|
map.set(u.value, { value: u.value, label: u.label, deleted: u.deleted })
|
||||||
|
);
|
||||||
|
(tokenMeta?.users || []).forEach((u) => {
|
||||||
|
const id = u.identity.current?.user_id;
|
||||||
|
if (id != null && !map.has(id)) {
|
||||||
|
map.set(id, { value: id, label: u.label });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Array.from(map.values());
|
||||||
|
}, [resourceUsers, tokenMeta]);
|
||||||
|
|
||||||
|
// user id → the identity object the token series filters by. Built from the
|
||||||
|
// token meta so the trend's ``users`` filter carries the real identity.
|
||||||
|
const tokenUserById = useMemo(() => {
|
||||||
|
const map = new Map<number, FilterOptionType>();
|
||||||
|
(tokenMeta?.users || []).forEach((u) => {
|
||||||
|
const id = u.identity.current?.user_id;
|
||||||
|
if (id != null) map.set(id, { identity: u.identity });
|
||||||
|
});
|
||||||
|
return map;
|
||||||
|
}, [tokenMeta]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
detailData: summary,
|
detailData: summary,
|
||||||
@@ -285,6 +318,24 @@ const SummaryTab: React.FC = () => {
|
|||||||
? { creator_ids: currentParams.selectedUsers }
|
? { creator_ids: currentParams.selectedUsers }
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
// The token series hits /usage/breakdown, which filters users by identity
|
||||||
|
// rather than the creator_ids the resource endpoints take — so the token
|
||||||
|
// trend honors the user filter like the totals do. Resolve each id to its
|
||||||
|
// token-meta identity, falling back to a minimal current.user_id object for
|
||||||
|
// users present only in the resource meta.
|
||||||
|
const tokenUserFilter: { users?: FilterOptionType[] } = currentParams
|
||||||
|
.selectedUsers.length
|
||||||
|
? {
|
||||||
|
users: currentParams.selectedUsers.map(
|
||||||
|
(id) =>
|
||||||
|
tokenUserById.get(id) ??
|
||||||
|
({
|
||||||
|
identity: { current: { user_id: id } }
|
||||||
|
} as unknown as FilterOptionType)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fetchSummary({
|
fetchSummary({
|
||||||
...commonParams,
|
...commonParams,
|
||||||
@@ -308,7 +359,7 @@ const SummaryTab: React.FC = () => {
|
|||||||
group_by: ['date'],
|
group_by: ['date'],
|
||||||
granularity,
|
granularity,
|
||||||
page: -1,
|
page: -1,
|
||||||
filters: {}
|
filters: tokenUserFilter
|
||||||
}),
|
}),
|
||||||
|
|
||||||
fetchComputeBreakdown({
|
fetchComputeBreakdown({
|
||||||
@@ -418,6 +469,7 @@ const SummaryTab: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
fetchTokenMeta();
|
||||||
fetchAll();
|
fetchAll();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Tabs } from 'antd';
|
import { Tabs } from 'antd';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { UsageFilterItem } from '../../config/types';
|
import { BreakdownFilters } from '../../config/types';
|
||||||
import ApiKeysTable from '../tables/apikeys-table';
|
import ApiKeysTable from '../tables/apikeys-table';
|
||||||
import ModelsTable from '../tables/models-table';
|
import ModelsTable from '../tables/models-table';
|
||||||
import UsersTable from '../tables/users-table';
|
import UsersTable from '../tables/users-table';
|
||||||
|
|
||||||
type FilterOptionType = Omit<UsageFilterItem, 'label' | 'deleted'>;
|
|
||||||
const EMPTY_FILTERS: FilterOptionType[] = [];
|
|
||||||
|
|
||||||
const BreakdownTabs: React.FC<{
|
const BreakdownTabs: React.FC<{
|
||||||
dateRange: {
|
dateRange: {
|
||||||
start_date: string;
|
start_date: string;
|
||||||
@@ -17,16 +14,9 @@ const BreakdownTabs: React.FC<{
|
|||||||
scope: string;
|
scope: string;
|
||||||
pageResetKey?: number;
|
pageResetKey?: number;
|
||||||
refreshKey?: number;
|
refreshKey?: number;
|
||||||
filters: {
|
filters: BreakdownFilters;
|
||||||
routes?: FilterOptionType[];
|
|
||||||
users?: FilterOptionType[];
|
|
||||||
api_keys?: FilterOptionType[];
|
|
||||||
};
|
|
||||||
}> = ({ filters, dateRange, scope, pageResetKey = 0, refreshKey = 0 }) => {
|
}> = ({ filters, dateRange, scope, pageResetKey = 0, refreshKey = 0 }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const routes = filters.routes || EMPTY_FILTERS;
|
|
||||||
const users = filters.users || EMPTY_FILTERS;
|
|
||||||
const apiKeys = filters.api_keys || EMPTY_FILTERS;
|
|
||||||
|
|
||||||
const items = useMemo(() => {
|
const items = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
@@ -37,7 +27,7 @@ const BreakdownTabs: React.FC<{
|
|||||||
children: (
|
children: (
|
||||||
<ModelsTable
|
<ModelsTable
|
||||||
key="models"
|
key="models"
|
||||||
routes={routes}
|
filters={filters}
|
||||||
dateRange={dateRange}
|
dateRange={dateRange}
|
||||||
scope={scope}
|
scope={scope}
|
||||||
pageResetKey={pageResetKey}
|
pageResetKey={pageResetKey}
|
||||||
@@ -52,7 +42,7 @@ const BreakdownTabs: React.FC<{
|
|||||||
children: (
|
children: (
|
||||||
<UsersTable
|
<UsersTable
|
||||||
key="users"
|
key="users"
|
||||||
users={users}
|
filters={filters}
|
||||||
dateRange={dateRange}
|
dateRange={dateRange}
|
||||||
scope={scope}
|
scope={scope}
|
||||||
pageResetKey={pageResetKey}
|
pageResetKey={pageResetKey}
|
||||||
@@ -67,7 +57,7 @@ const BreakdownTabs: React.FC<{
|
|||||||
children: (
|
children: (
|
||||||
<ApiKeysTable
|
<ApiKeysTable
|
||||||
key="api_keys"
|
key="api_keys"
|
||||||
apiKeys={apiKeys}
|
filters={filters}
|
||||||
dateRange={dateRange}
|
dateRange={dateRange}
|
||||||
scope={scope}
|
scope={scope}
|
||||||
pageResetKey={pageResetKey}
|
pageResetKey={pageResetKey}
|
||||||
@@ -81,7 +71,7 @@ const BreakdownTabs: React.FC<{
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}, [apiKeys, dateRange, routes, pageResetKey, refreshKey, scope, users]);
|
}, [filters, dateRange, pageResetKey, refreshKey, scope]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ marginTop: 16 }}>
|
<div style={{ marginTop: 16 }}>
|
||||||
|
|||||||
@@ -3,18 +3,18 @@ import PageBox from '@/pages/_components/page-box';
|
|||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Table } from 'antd';
|
import { Table } from 'antd';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { FilterOptionType } from '../../config/types';
|
import { BreakdownFilters } from '../../config/types';
|
||||||
import useAPIKeys from '../../hooks/use-apikeys-columns';
|
import useAPIKeys from '../../hooks/use-apikeys-columns';
|
||||||
import useQueryBreakdownList from '../../services/use-query-breakdown-list';
|
import useQueryBreakdownList from '../../services/use-query-breakdown-list';
|
||||||
import getBreakdownRowKey from '../../utils/get-breakdown-row-key';
|
import getBreakdownRowKey from '../../utils/get-breakdown-row-key';
|
||||||
|
|
||||||
const APIKeys: React.FC<{
|
const APIKeys: React.FC<{
|
||||||
apiKeys: FilterOptionType[];
|
filters: BreakdownFilters;
|
||||||
dateRange: { start_date: string; end_date: string };
|
dateRange: { start_date: string; end_date: string };
|
||||||
scope: string;
|
scope: string;
|
||||||
pageResetKey?: number;
|
pageResetKey?: number;
|
||||||
refreshKey?: number;
|
refreshKey?: number;
|
||||||
}> = ({ apiKeys, dateRange, scope, pageResetKey = 0, refreshKey = 0 }) => {
|
}> = ({ filters, dateRange, scope, pageResetKey = 0, refreshKey = 0 }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const { loading, dataSource, fetchData } = useQueryBreakdownList({
|
const { loading, dataSource, fetchData } = useQueryBreakdownList({
|
||||||
@@ -69,16 +69,16 @@ const APIKeys: React.FC<{
|
|||||||
fetchData({
|
fetchData({
|
||||||
...queryParams,
|
...queryParams,
|
||||||
group_by: ['api_key'],
|
group_by: ['api_key'],
|
||||||
filters: {
|
// Send the full filter set (route / user / api_key), not just the
|
||||||
api_keys: apiKeys
|
// table's own dimension, so the breakdown matches the trend chart.
|
||||||
},
|
filters,
|
||||||
scope: scope,
|
scope: scope,
|
||||||
...dateRange
|
...dateRange
|
||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
apiKeys,
|
|
||||||
dateRange.end_date,
|
dateRange.end_date,
|
||||||
dateRange.start_date,
|
dateRange.start_date,
|
||||||
|
filters,
|
||||||
queryParams.page,
|
queryParams.page,
|
||||||
queryParams.perPage,
|
queryParams.perPage,
|
||||||
queryParams.sort_by,
|
queryParams.sort_by,
|
||||||
|
|||||||
@@ -4,18 +4,18 @@ import PageBox from '@/pages/_components/page-box';
|
|||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Table } from 'antd';
|
import { Table } from 'antd';
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { BreakdownItem, FilterOptionType } from '../../config/types';
|
import { BreakdownFilters, BreakdownItem } from '../../config/types';
|
||||||
import useModelsColumns from '../../hooks/use-models-columns';
|
import useModelsColumns from '../../hooks/use-models-columns';
|
||||||
import useQueryBreakdownList from '../../services/use-query-breakdown-list';
|
import useQueryBreakdownList from '../../services/use-query-breakdown-list';
|
||||||
import getBreakdownRowKey from '../../utils/get-breakdown-row-key';
|
import getBreakdownRowKey from '../../utils/get-breakdown-row-key';
|
||||||
|
|
||||||
const Models: React.FC<{
|
const Models: React.FC<{
|
||||||
routes: FilterOptionType[];
|
filters: BreakdownFilters;
|
||||||
dateRange: { start_date: string; end_date: string };
|
dateRange: { start_date: string; end_date: string };
|
||||||
scope: string;
|
scope: string;
|
||||||
pageResetKey?: number;
|
pageResetKey?: number;
|
||||||
refreshKey?: number;
|
refreshKey?: number;
|
||||||
}> = ({ routes, dateRange, scope, pageResetKey = 0, refreshKey = 0 }) => {
|
}> = ({ filters, dateRange, scope, pageResetKey = 0, refreshKey = 0 }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const { loading, dataSource, fetchData } = useQueryBreakdownList({
|
const { loading, dataSource, fetchData } = useQueryBreakdownList({
|
||||||
@@ -33,7 +33,6 @@ const Models: React.FC<{
|
|||||||
const pendingPageResetRef = useRef(false);
|
const pendingPageResetRef = useRef(false);
|
||||||
|
|
||||||
const handleTableChange = (pagination: any, filters: any, sorter: any) => {
|
const handleTableChange = (pagination: any, filters: any, sorter: any) => {
|
||||||
console.log('pagination, filters, sorter: ', pagination, filters, sorter);
|
|
||||||
const sort_by =
|
const sort_by =
|
||||||
sorter.order === 'descend' ? `-${sorter.field}` : sorter.field;
|
sorter.order === 'descend' ? `-${sorter.field}` : sorter.field;
|
||||||
setQueryParams((prev) => ({
|
setQueryParams((prev) => ({
|
||||||
@@ -72,16 +71,16 @@ const Models: React.FC<{
|
|||||||
fetchData({
|
fetchData({
|
||||||
...queryParams,
|
...queryParams,
|
||||||
group_by: ['route'],
|
group_by: ['route'],
|
||||||
filters: {
|
// Send the full filter set (route / user / api_key), not just the
|
||||||
routes
|
// table's own dimension, so the breakdown matches the trend chart.
|
||||||
},
|
filters,
|
||||||
scope: scope,
|
scope: scope,
|
||||||
...dateRange
|
...dateRange
|
||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
dateRange.end_date,
|
dateRange.end_date,
|
||||||
dateRange.start_date,
|
dateRange.start_date,
|
||||||
routes,
|
filters,
|
||||||
queryParams.page,
|
queryParams.page,
|
||||||
queryParams.perPage,
|
queryParams.perPage,
|
||||||
queryParams.sort_by,
|
queryParams.sort_by,
|
||||||
|
|||||||
@@ -3,18 +3,18 @@ import PageBox from '@/pages/_components/page-box';
|
|||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Table } from 'antd';
|
import { Table } from 'antd';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { FilterOptionType } from '../../config/types';
|
import { BreakdownFilters } from '../../config/types';
|
||||||
import useUsersColumns from '../../hooks/use-users-columns';
|
import useUsersColumns from '../../hooks/use-users-columns';
|
||||||
import useQueryBreakdownList from '../../services/use-query-breakdown-list';
|
import useQueryBreakdownList from '../../services/use-query-breakdown-list';
|
||||||
import getBreakdownRowKey from '../../utils/get-breakdown-row-key';
|
import getBreakdownRowKey from '../../utils/get-breakdown-row-key';
|
||||||
|
|
||||||
const Users: React.FC<{
|
const Users: React.FC<{
|
||||||
users: FilterOptionType[];
|
filters: BreakdownFilters;
|
||||||
dateRange: { start_date: string; end_date: string };
|
dateRange: { start_date: string; end_date: string };
|
||||||
scope: string;
|
scope: string;
|
||||||
pageResetKey?: number;
|
pageResetKey?: number;
|
||||||
refreshKey?: number;
|
refreshKey?: number;
|
||||||
}> = ({ users, dateRange, scope, pageResetKey = 0, refreshKey = 0 }) => {
|
}> = ({ filters, dateRange, scope, pageResetKey = 0, refreshKey = 0 }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const { loading, dataSource, fetchData } = useQueryBreakdownList({
|
const { loading, dataSource, fetchData } = useQueryBreakdownList({
|
||||||
@@ -71,9 +71,9 @@ const Users: React.FC<{
|
|||||||
fetchData({
|
fetchData({
|
||||||
...queryParams,
|
...queryParams,
|
||||||
group_by: ['user'],
|
group_by: ['user'],
|
||||||
filters: {
|
// Send the full filter set (route / user / api_key), not just the
|
||||||
users
|
// table's own dimension, so the breakdown matches the trend chart.
|
||||||
},
|
filters,
|
||||||
scope: scope,
|
scope: scope,
|
||||||
...dateRange
|
...dateRange
|
||||||
});
|
});
|
||||||
@@ -81,12 +81,12 @@ const Users: React.FC<{
|
|||||||
}, [
|
}, [
|
||||||
dateRange.end_date,
|
dateRange.end_date,
|
||||||
dateRange.start_date,
|
dateRange.start_date,
|
||||||
|
filters,
|
||||||
queryParams.page,
|
queryParams.page,
|
||||||
queryParams.perPage,
|
queryParams.perPage,
|
||||||
queryParams.sort_by,
|
queryParams.sort_by,
|
||||||
refreshKey,
|
refreshKey,
|
||||||
scope,
|
scope
|
||||||
users
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user