@@ -12,6 +12,9 @@ const PLUGIN_BASE =
1212 window . cockpit . manifest . path ) ||
1313 '/usr/share/cockpit/slurmcostmanager' ;
1414
15+ const HAS_COCKPIT =
16+ typeof window !== 'undefined' && window . cockpit && window . cockpit . spawn ;
17+
1518function getBillingPeriod ( ref = new Date ( ) ) {
1619 const today = new Date ( ) ;
1720 let year , month , end ;
@@ -50,19 +53,19 @@ function getYearPeriod(year = new Date().getFullYear()) {
5053function useBillingData ( period ) {
5154 const [ data , setData ] = useState ( null ) ;
5255 const [ error , setError ] = useState ( null ) ;
53- const requestIdRef = useRef ( 0 ) ;
56+ const [ loading , setLoading ] = useState ( false ) ;
57+ const abortRef = useRef ( null ) ;
5458
5559 const load = useCallback ( async ( ) => {
56- const id = ++ requestIdRef . current ;
57- // Clear out existing data while loading a new period so that views
58- // such as "Detailed Transactions" don't momentarily display data
59- // from the previously selected period (e.g. the fiscal year) when
60- // navigating directly between views.
61- setData ( null ) ;
60+ if ( abortRef . current ) {
61+ if ( abortRef . current . abort ) abortRef . current . abort ( ) ;
62+ if ( abortRef . current . close ) abortRef . current . close ( ) ;
63+ }
64+ setLoading ( true ) ;
6265 setError ( null ) ;
6366 try {
6467 let json ;
65- if ( window . cockpit && window . cockpit . spawn ) {
68+ if ( HAS_COCKPIT ) {
6669 let start , end ;
6770 if ( typeof period === 'string' ) {
6871 ( { start, end } = getBillingPeriod ( period ) ) ;
@@ -81,30 +84,39 @@ function useBillingData(period) {
8184 '--output' ,
8285 '-' ,
8386 ] ;
84- const output = await window . cockpit . spawn ( args , { err : 'message' } ) ;
87+ const proc = window . cockpit . spawn ( args , { err : 'message' } ) ;
88+ abortRef . current = proc ;
89+ const output = await proc ;
8590 json = JSON . parse ( output ) ;
8691 } else {
87- const resp = await fetch ( 'billing.json' ) ;
92+ const controller = new AbortController ( ) ;
93+ abortRef . current = controller ;
94+ const resp = await fetch ( 'billing.json' , { signal : controller . signal } ) ;
8895 if ( ! resp . ok ) throw new Error ( 'Failed to fetch billing data' ) ;
8996 json = await resp . json ( ) ;
9097 }
91- if ( requestIdRef . current === id ) {
92- setData ( json ) ;
93- setError ( null ) ;
94- }
98+ setData ( json ) ;
9599 } catch ( e ) {
96- console . error ( e ) ;
97- if ( requestIdRef . current === id ) {
100+ if ( e . name !== 'AbortError' ) {
101+ console . error ( e ) ;
98102 setError ( e . message || String ( e ) ) ;
99103 }
104+ } finally {
105+ setLoading ( false ) ;
100106 }
101107 } , [ period ] ) ;
102108
103109 useEffect ( ( ) => {
104110 load ( ) ;
111+ return ( ) => {
112+ if ( abortRef . current ) {
113+ if ( abortRef . current . abort ) abortRef . current . abort ( ) ;
114+ if ( abortRef . current . close ) abortRef . current . close ( ) ;
115+ }
116+ } ;
105117 } , [ load ] ) ;
106118
107- return { data, error, reload : load } ;
119+ return { data, error, loading , reload : load } ;
108120}
109121
110122function aggregateAccountDetails ( details = [ ] ) {
@@ -1928,7 +1940,7 @@ function App() {
19281940 const [ month , setMonth ] = useState ( defaultMonth ) ;
19291941 const yearPeriod = useMemo ( ( ) => getYearPeriod ( currentYear ) , [ currentYear ] ) ;
19301942 const period = view === 'year' ? yearPeriod : month ;
1931- const { data, error, reload } = useBillingData ( period ) ;
1943+ const { data, error, loading , reload } = useBillingData ( period ) ;
19321944 const details = useMemo ( ( ) => {
19331945 if ( ! data ) return [ ] ;
19341946 return view === 'year'
@@ -1985,7 +1997,7 @@ function App() {
19851997 )
19861998 )
19871999 ) ,
1988- view !== 'settings' && ! data && ! error && React . createElement ( 'p' , null , 'Loading...' ) ,
2000+ view !== 'settings' && loading && React . createElement ( 'p' , null , 'Loading...' ) ,
19892001 view !== 'settings' &&
19902002 error &&
19912003 React . createElement (
0 commit comments