6666</template >
6767
6868<script setup>
69- import { computed , onMounted , ref , watch } from " vue" ;
69+ import { computed , onBeforeUnmount , onMounted , ref , watch } from " vue" ;
7070import LoginPage from " ./components/LoginPage.vue" ;
7171import VmList from " ./components/VmList.vue" ;
7272import { api , clearAuth , loadAuthFromStorage , setBasicAuth } from " ./services/apiClient" ;
@@ -77,6 +77,11 @@ const loginLoading = ref(false);
7777const isAuthenticated = ref (false );
7878const vmListRef = ref (null );
7979const hostSummary = ref (null );
80+ const uiConfig = ref ({
81+ enabled: true ,
82+ session_timeout_seconds: 0 ,
83+ });
84+ let sessionExpiryHandle = null ;
8085
8186const updateTimestamp = (value ) => {
8287 lastUpdated .value = value;
@@ -94,6 +99,7 @@ const handleAuthRequired = (message) => {
9499 clearAuth ();
95100 setAuthState (false );
96101 loginLoading .value = false ;
102+ clearSessionExpiry ();
97103 hostSummary .value = null ;
98104 if (typeof message === " string" && message .trim ()) {
99105 loginError .value = message;
@@ -119,6 +125,7 @@ const handleLoginSubmit = async ({ username, password }) => {
119125 setBasicAuth (username, password);
120126 await api .get (" /v1/vms" );
121127 setAuthState (true );
128+ scheduleSessionExpiry ();
122129 await fetchHostSummary ();
123130 } catch (error) {
124131 clearAuth ();
@@ -134,6 +141,7 @@ const handleLoginSubmit = async ({ username, password }) => {
134141};
135142
136143const handleLogout = () => {
144+ clearSessionExpiry ();
137145 clearAuth ();
138146 setAuthState (false );
139147 loginError .value = " " ;
@@ -146,15 +154,6 @@ const lastUpdatedLabel = computed(() => {
146154 return new Date (lastUpdated .value ).toLocaleString ();
147155});
148156
149- const primaryAddress = computed (() => {
150- const addresses = hostSummary .value ? .ip_addresses ;
151- if (! Array .isArray (addresses) || addresses .length === 0 ) {
152- return null ;
153- }
154- const primary = addresses .find ((entry ) => entry .family === " IPv4" && ! entry .is_loopback );
155- return primary || addresses[0 ];
156- });
157-
158157const cpuLabel = computed (() => {
159158 const cpu = hostSummary .value ? .cpu ;
160159 if (! cpu) return " -" ;
@@ -193,6 +192,11 @@ const diskLabel = computed(() => {
193192 return ` ${ gib .toFixed (1 )} GiB` ;
194193});
195194
195+ const sessionTimeoutSeconds = computed (() => {
196+ const raw = Number (uiConfig .value ? .session_timeout_seconds ?? 0 );
197+ return Number .isFinite (raw) && raw > 0 ? raw : 0 ;
198+ });
199+
196200const isBridgeInterface = (name ) => {
197201 if (! name) {
198202 return false ;
@@ -255,7 +259,48 @@ const hostInterfaces = computed(() => {
255259 .sort ((a , b ) => a .name .localeCompare (b .name ));
256260});
257261
262+ const loadUiConfig = async () => {
263+ try {
264+ const { data } = await api .get (" /v1/ui/config" );
265+ const payload = (data && data .config ) || data || {};
266+ const timeoutSeconds = Number (payload .session_timeout_seconds ?? 0 );
267+ uiConfig .value = {
268+ enabled: payload .enabled !== false ,
269+ session_timeout_seconds: Number .isFinite (timeoutSeconds) && timeoutSeconds > 0 ? timeoutSeconds : 0 ,
270+ };
271+ } catch (error) {
272+ console .warn (" Failed to load UI configuration" , error);
273+ }
274+ };
275+
276+ const clearSessionExpiry = () => {
277+ if (sessionExpiryHandle !== null ) {
278+ window .clearTimeout (sessionExpiryHandle);
279+ sessionExpiryHandle = null ;
280+ }
281+ };
282+
283+ const handleSessionExpired = () => {
284+ clearSessionExpiry ();
285+ loginError .value = " Session expired. Please sign in again." ;
286+ clearAuth ();
287+ setAuthState (false );
288+ };
289+
290+ const scheduleSessionExpiry = () => {
291+ clearSessionExpiry ();
292+ const seconds = sessionTimeoutSeconds .value ;
293+ if (! seconds) {
294+ return ;
295+ }
296+ sessionExpiryHandle = window .setTimeout (() => {
297+ handleSessionExpired ();
298+ }, seconds * 1000 );
299+ };
258300onMounted (() => {
301+ loadUiConfig ().catch (() => {
302+ /* optional */
303+ });
259304 const restored = loadAuthFromStorage ();
260305 setAuthState (restored);
261306 if (! restored) {
@@ -271,16 +316,35 @@ watch(
271316 () => isAuthenticated .value ,
272317 (value ) => {
273318 if (value) {
319+ scheduleSessionExpiry ();
274320 if (! hostSummary .value ) {
275321 fetchHostSummary ().catch (() => {
276322 /* non-fatal */
277323 });
278324 }
279325 } else {
326+ clearSessionExpiry ();
280327 hostSummary .value = null ;
281328 }
282329 }
283330);
331+
332+ watch (
333+ () => sessionTimeoutSeconds .value ,
334+ (seconds ) => {
335+ if (! seconds) {
336+ clearSessionExpiry ();
337+ return ;
338+ }
339+ if (isAuthenticated .value ) {
340+ scheduleSessionExpiry ();
341+ }
342+ }
343+ );
344+
345+ onBeforeUnmount (() => {
346+ clearSessionExpiry ();
347+ });
284348< / script>
285349
286350< style scoped>
0 commit comments