Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 37 additions & 10 deletions packages/web/projects/vgpu/views/card/admin/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
clearable
:placeholder="$t('card.allCardTypes')"
:options="cardTypeOptions"
@change="applyFilters"
@change="onTypeChange"
/>
<t-select
v-model="filters.nodeName"
Expand Down Expand Up @@ -104,12 +104,18 @@ const filters = reactive({
nodeName: props.filters?.nodeName,
type: props.filters?.type ?? parseTypeFromQuery(route.query.type),
});
const rawNodeNames = ref([]);
const rawNodes = ref([]);
const rawCardTypes = ref([]);
const nodeOptions = computed(() => [
{ label: t('card.allNodes'), value: undefined },
...rawNodeNames.value.map((name) => ({ label: name, value: name })),
]);
const nodeOptions = computed(() => {
// When a card type is selected, only offer nodes that actually carry that type.
const nodes = filters.type
? rawNodes.value.filter((node) => node.types.includes(filters.type))
: rawNodes.value;
return [
{ label: t('card.allNodes'), value: undefined },
...nodes.map((node) => ({ label: node.name, value: node.name })),
];
});
const cardTypeOptions = computed(() => [
{ label: t('card.allCardTypes'), value: undefined },
...rawCardTypes.value.map((type) => ({ label: type, value: type })),
Expand All @@ -121,18 +127,37 @@ const fetchFilterOptions = async () => {
request(nodeApi.getNodeList({ filters: {} })),
request(cardApi.getCardType()),
]);
rawNodeNames.value = nodeList
.map((item) => item?.name)
.filter(Boolean);
rawNodes.value = nodeList
.filter((item) => item?.name)
.map((item) => ({ name: item.name, types: Array.isArray(item.type) ? item.type : [] }));
rawCardTypes.value = typeList
.map((item) => item?.type)
.filter(Boolean);
} catch {
rawNodeNames.value = [];
rawNodes.value = [];
rawCardTypes.value = [];
}
};

// When the selected type no longer includes the chosen node, drop the node so
// the table isn't filtered by an out-of-scope node. Call synchronously before
// applyFilters at every type-mutation site to avoid querying with a stale node.
const pruneNodeScopeByType = () => {
if (!filters.type || !filters.nodeName) {
return;
}
const node = rawNodes.value.find((item) => item.name === filters.nodeName);
if (!node || !node.types.includes(filters.type)) {
filters.nodeName = undefined;
hasManualNodeScope.value = true;
}
};

const onTypeChange = () => {
pruneNodeScopeByType();
applyFilters();
};

const handleClick = (params) => {
router.push({
path: `/admin/vgpu/card/admin/${params.data.name}`,
Expand Down Expand Up @@ -336,6 +361,7 @@ const handlePieClick = (params, echarts) => {
dataIndex: params.dataIndex,
});
filters.type = name;
pruneNodeScopeByType();
applyFilters();
};

Expand Down Expand Up @@ -400,6 +426,7 @@ watch(
const next = parseTypeFromQuery(value);
if (filters.type === next) return;
filters.type = next;
pruneNodeScopeByType();
applyFilters();
},
);
Expand Down
43 changes: 42 additions & 1 deletion packages/web/projects/vgpu/views/task/admin/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@
:options="statusOptions"
@change="applyFilters"
/>
<t-select
v-model="filters.deviceId"
clearable
:placeholder="$t('task.allCards')"
:options="cardOptions"
@change="applyFilters"
/>
<t-input
v-model="filters.name"
clearable
Expand Down Expand Up @@ -74,6 +81,7 @@
<script setup lang="jsx">
import taskApi from '~/vgpu/api/task';
import nodeApi from '~/vgpu/api/node';
import cardApi from '~/vgpu/api/card';
import Toolbar from '@/components/TablePlus/Toolbar.vue';
import TablePagination from '@/components/TablePlus/Pagination.vue';
import { roundToDecimal, timeParse } from '@/utils';
Expand All @@ -95,12 +103,24 @@ const filters = reactive({
name: props.filters?.name || '',
nodeName: props.filters?.nodeName,
status: props.filters?.status,
deviceId: props.filters?.deviceId,
});
const rawNodeNames = ref([]);
const rawCards = ref([]);
const nodeOptions = computed(() => [
{ label: t('task.allNodes'), value: undefined },
...rawNodeNames.value.map((name) => ({ label: name, value: name })),
]);
const cardOptions = computed(() => {
// When a node is selected, only offer the cards that live on that node.
const cards = filters.nodeName
? rawCards.value.filter((card) => card.nodeName === filters.nodeName)
: rawCards.value;
return [
{ label: t('task.allCards'), value: undefined },
...cards.map((card) => ({ label: card.uuid, value: card.uuid })),
];
});
const statusOptions = computed(() => [
{ label: t('task.allStatus'), value: undefined },
{ label: t('task.statusCompleted'), value: 'closed' },
Expand All @@ -119,12 +139,19 @@ const state = reactive({

const fetchFilterOptions = async () => {
try {
const { list: nodeList = [] } = await request(nodeApi.getNodeList({ filters: {} }));
const [{ list: nodeList = [] }, { list: cardList = [] }] = await Promise.all([
request(nodeApi.getNodeList({ filters: {} })),
request(cardApi.getCardList({ filters: {} })),
]);
rawNodeNames.value = nodeList
.map((item) => item?.name)
.filter(Boolean);
rawCards.value = cardList
.filter((item) => item?.uuid)
.map((item) => ({ uuid: item.uuid, nodeName: item.nodeName }));
} catch {
rawNodeNames.value = [];
rawCards.value = [];
}
};

Expand Down Expand Up @@ -251,6 +278,7 @@ const fetchTableData = async () => {
...(nodeName ? { nodeName } : {}),
...(nodeUid ? { nodeUid } : {}),
...(filters.status ? { status: filters.status } : {}),
...(filters.deviceId ? { deviceId: filters.deviceId } : {}),
},
};
const { items = [] } = await taskApi.getTaskListReq(payload);
Expand All @@ -266,6 +294,17 @@ const { getTrimValue, applyFilters, refreshTable } = useTableFilters({
});
const onNodeNameChange = () => {
hasManualNodeScope.value = true;
// The card dropdown is scoped to the selected node; drop a previously chosen
// card if it doesn't belong to that node so the table isn't filtered by an
// out-of-scope device.
if (filters.nodeName && filters.deviceId) {
const stillValid = rawCards.value.some(
(card) => card.nodeName === filters.nodeName && card.uuid === filters.deviceId,
);
if (!stillValid) {
filters.deviceId = undefined;
}
}
applyFilters();
};

Expand All @@ -279,12 +318,14 @@ watch(
props.filters?.nodeName,
props.filters?.nodeUid,
props.filters?.status,
props.filters?.deviceId,
],
() => {
hasManualNodeScope.value = false;
filters.name = props.filters?.name || '';
filters.nodeName = props.filters?.nodeName;
filters.status = props.filters?.status;
filters.deviceId = props.filters?.deviceId;
applyFilters();
},
{ immediate: true },
Expand Down
Loading