Skip to content

fix(sources): refresh the list once the saved source is being served - #1414

Open
yxf0314 wants to merge 1 commit into
gpustack:mainfrom
yxf0314:issue/6214-refresh-list-after-source-save
Open

yxf0314 wants to merge 1 commit into
gpustack:mainfrom
yxf0314:issue/6214-refresh-list-after-source-save

Conversation

@yxf0314

@yxf0314 yxf0314 commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

ref gpustack/gpustack#6214

What changed

All of this is in the shared source-config drawer, so it applies to every page that opens it: Model Catalog, Backends (built-in and community) and KV Cache providers.

  • List refresh after Save. The list behind the drawer is now refetched 1s and 3s after a successful Save or Update Now, instead of immediately. A newer save cancels any refetches still pending, and so does unmounting the drawer.
  • Update Now while auto update is off. The button stays available when auto update is off, and while the only unsaved edit is to the update interval.
  • YAML File mode. The "Last updated" line is hidden under the YAML editor.
  • Copy. The Embedded card description now reads "Uses only the content bundled with the current version, and no other source." (zh: 仅使用当前版本自带内容,不使用其他来源。). Yaml is spelled YAML in every locale.

Why

  • Save needed a second click (#6214). PUT /ota-sources/{kind} returns as soon as the source rows are committed. The list does not read those rows. It reads a derived table (for example CatalogModelEntry) that the leader's source controller rebuilds asynchronously once it sees the change events. The first save of a custom source makes three commits, so the leader runs three full rebuilds, which takes about 0.5–1.5s. An immediate refetch therefore read the old list. The second Save changed nothing on the server, but by then the rebuild from the first one had finished, so it looked like the second click was what worked.
  • Update Now was disabled with auto update off. The frontend required a non-zero cadence before offering it. The server does not: a manual refresh skips the cadence gate on purpose (refresh_official_kind). The auto-update tooltip also already says the stored content stays in place "until you sync it yourself". An unsaved cadence edit also disabled the button, even though a refresh never reads the cadence.
  • "Last updated" under the YAML editor. Inline content never arrives on its own, so the timestamp only dated the last Save, or described a source that was not on screen.

Verification

  • Checked manually:
    • After one Save of a YAML catalog source, the catalog list updates within a few seconds.
    • With auto update unchecked, Update Now is clickable both before and after saving.
    • "Last updated" is not shown in YAML File mode.
  • eslint, prettier --check and src/locales/check.ts pass. tsc --noEmit reports no errors in the changed files.

Notes

  • The refetch delays are an estimate: the frontend has no signal for when the rebuild has finished. On an unusually slow server the 3s refetch can still read the old list, and reopening the page shows the new one.
  • ja-JP and ru-RU carry these source strings as English placeholders, as they already did. The new English wording is copied there for translators to pick up.

- refetch the list behind the source drawer after 1s and 3s instead of at
  once: the server rebuilds it from the saved source asynchronously, so an
  immediate refetch read the list as it was and Save looked like it needed
  a second click
- keep Update Now available while auto update is off, and while only the
  cadence has unsaved edits: a manual refresh skips the cadence server-side
- hide "Last updated" under the YAML file editor
- reword the Embedded card description and spell YAML consistently

ref gpustack/gpustack#6214
Copilot AI balanced review requested due to automatic review settings September 24, 2026 04:04

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a mechanism to delay list refetching after saving a source configuration, ensuring that the UI reflects the state after the server has processed the update. It also includes several improvements to the source configuration logic, such as renaming 'Yaml' to 'YAML' across locales and refining the 'dirty' state tracking for the refetch button. I have provided feedback regarding the use of useCallback and useRef to ensure stable function references and prevent stale closures in the new refetch logic.

import { Tabs } from 'antd';
import { createStyles } from 'antd-style';
import React, { useEffect, useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Import useCallback to memoize the cancelListRefetch function and prevent unnecessary re-registrations in the useEffect hook.

Suggested change
import React, { useEffect, useRef, useState } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';

Comment on lines +73 to +78
const listRefetchTimers = useRef<ReturnType<typeof setTimeout>[]>([]);

const cancelListRefetch = () => {
listRefetchTimers.current.forEach(clearTimeout);
listRefetchTimers.current = [];
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent stale closure issues when onSaved changes during the scheduled refetch delays, we should store the latest onSaved callback in a mutable ref. Additionally, wrap cancelListRefetch in useCallback to ensure its reference remains stable across renders, which avoids triggering lint warnings or unnecessary effect cleanups.

  const listRefetchTimers = useRef<ReturnType<typeof setTimeout>[]>([]);
  const onSavedRef = useRef(onSaved);
  onSavedRef.current = onSaved;

  const cancelListRefetch = useCallback(() => {
    listRefetchTimers.current.forEach(clearTimeout);
    listRefetchTimers.current = [];
  }, []);

Comment on lines +89 to +99
useEffect(() => cancelListRefetch, []);

// A tab saved: its merged content moved, so the probe and the list behind
// the drawer are both stale.
// the drawer are both stale. The probe reads the stored source and is current
// at once; the list is not (see `LIST_REFETCH_DELAYS_MS`).
const handleSlotSaved = () => {
loadProbe();
onSaved?.();
cancelListRefetch();
listRefetchTimers.current = LIST_REFETCH_DELAYS_MS.map((delay) =>
setTimeout(() => onSaved?.(), delay)
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Update the useEffect cleanup hook to depend on the memoized cancelListRefetch function to satisfy the react-hooks/exhaustive-deps rule. Also, use onSavedRef.current inside the setTimeout callback to ensure the latest version of the onSaved function is always executed.

Suggested change
useEffect(() => cancelListRefetch, []);
// A tab saved: its merged content moved, so the probe and the list behind
// the drawer are both stale.
// the drawer are both stale. The probe reads the stored source and is current
// at once; the list is not (see `LIST_REFETCH_DELAYS_MS`).
const handleSlotSaved = () => {
loadProbe();
onSaved?.();
cancelListRefetch();
listRefetchTimers.current = LIST_REFETCH_DELAYS_MS.map((delay) =>
setTimeout(() => onSaved?.(), delay)
);
useEffect(() => cancelListRefetch, [cancelListRefetch]);
// A tab saved: its merged content moved, so the probe and the list behind
// the drawer are both stale. The probe reads the stored source and is current
// at once; the list is not (see `LIST_REFETCH_DELAYS_MS`).
const handleSlotSaved = () => {
loadProbe();
cancelListRefetch();
listRefetchTimers.current = LIST_REFETCH_DELAYS_MS.map((delay) =>
setTimeout(() => onSavedRef.current?.(), delay)
);
};

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Delayed callbacks can use stale filters, and KV Cache provider data is not actually refreshed.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)
What changed in this PR

Fixes delayed source propagation for #6214 and improves source drawer behavior and copy.

Changes:

  • Schedules list refreshes 1s and 3s after saves.
  • Enables manual refresh independently of update cadence and hides YAML timestamps.
  • Updates YAML capitalization and Embedded descriptions.
File Description
src/​pages/​_components/​source-config/​drawer.tsx Adds delayed list refresh scheduling.
src/​pages/​_components/​source-config/​slot-form.tsx Refines refresh availability and timestamp visibility.
src/​pages/​llmodels/​components/​catalog/​catalog-source-entry.tsx Corrects YAML spelling.
src/​locales/​en-US/​common.ts Updates English copy.
src/​locales/​zh-CN/​common.ts Updates Chinese copy.
src/​locales/​ja-JP/​common.ts Updates Japanese locale placeholders.
src/​locales/​ru-RU/​common.ts Updates Russian locale placeholders.
src/​locales/​tr-TR/​common.ts Updates Turkish copy.

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 94 to +98
const handleSlotSaved = () => {
loadProbe();
onSaved?.();
cancelListRefetch();
listRefetchTimers.current = LIST_REFETCH_DELAYS_MS.map((delay) =>
setTimeout(() => onSaved?.(), delay)
Comment on lines +97 to +98
listRefetchTimers.current = LIST_REFETCH_DELAYS_MS.map((delay) =>
setTimeout(() => onSaved?.(), delay)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants