@optifye/dashboard-core 6.4.2 → 6.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.css +294 -0
- package/dist/index.d.mts +242 -77
- package/dist/index.d.ts +242 -77
- package/dist/index.js +1806 -665
- package/dist/index.mjs +1796 -669
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,7 +7,6 @@ var dateFnsTz = require('date-fns-tz');
|
|
|
7
7
|
var dateFns = require('date-fns');
|
|
8
8
|
var mixpanel = require('mixpanel-browser');
|
|
9
9
|
var events = require('events');
|
|
10
|
-
var clientS3 = require('@aws-sdk/client-s3');
|
|
11
10
|
var supabaseJs = require('@supabase/supabase-js');
|
|
12
11
|
var Hls2 = require('hls.js');
|
|
13
12
|
var useSWR = require('swr');
|
|
@@ -25,6 +24,7 @@ var SelectPrimitive = require('@radix-ui/react-select');
|
|
|
25
24
|
var videojs = require('video.js');
|
|
26
25
|
require('video.js/dist/video-js.css');
|
|
27
26
|
var sonner = require('sonner');
|
|
27
|
+
var clientS3 = require('@aws-sdk/client-s3');
|
|
28
28
|
var s3RequestPresigner = require('@aws-sdk/s3-request-presigner');
|
|
29
29
|
var stream = require('stream');
|
|
30
30
|
|
|
@@ -1704,6 +1704,25 @@ var workspaceService = {
|
|
|
1704
1704
|
this._workspaceDisplayNamesCache.clear();
|
|
1705
1705
|
this._cacheTimestamp = 0;
|
|
1706
1706
|
},
|
|
1707
|
+
/**
|
|
1708
|
+
* Updates the display name for a workspace
|
|
1709
|
+
* @param workspaceId - The workspace UUID
|
|
1710
|
+
* @param displayName - The new display name
|
|
1711
|
+
* @returns Promise<void>
|
|
1712
|
+
*/
|
|
1713
|
+
async updateWorkspaceDisplayName(workspaceId, displayName) {
|
|
1714
|
+
const supabase = _getSupabaseInstance();
|
|
1715
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
1716
|
+
const config = _getDashboardConfigInstance();
|
|
1717
|
+
const dbConfig = config?.databaseConfig;
|
|
1718
|
+
const workspacesTable = getTable3(dbConfig, "workspaces");
|
|
1719
|
+
const { error } = await supabase.from(workspacesTable).update({ display_name: displayName.trim() }).eq("id", workspaceId);
|
|
1720
|
+
if (error) {
|
|
1721
|
+
console.error(`Error updating workspace display name for ${workspaceId}:`, error);
|
|
1722
|
+
throw error;
|
|
1723
|
+
}
|
|
1724
|
+
this.clearWorkspaceDisplayNamesCache();
|
|
1725
|
+
},
|
|
1707
1726
|
async updateWorkspaceAction(updates) {
|
|
1708
1727
|
const supabase = _getSupabaseInstance();
|
|
1709
1728
|
if (!supabase) throw new Error("Supabase client not initialized");
|
|
@@ -1847,6 +1866,209 @@ var workspaceService = {
|
|
|
1847
1866
|
}
|
|
1848
1867
|
}
|
|
1849
1868
|
};
|
|
1869
|
+
var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
1870
|
+
constructor() {
|
|
1871
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
1872
|
+
this.cacheExpiryMs = 30 * 1e3;
|
|
1873
|
+
}
|
|
1874
|
+
// 30 seconds cache
|
|
1875
|
+
static getInstance() {
|
|
1876
|
+
if (!_WorkspaceHealthService.instance) {
|
|
1877
|
+
_WorkspaceHealthService.instance = new _WorkspaceHealthService();
|
|
1878
|
+
}
|
|
1879
|
+
return _WorkspaceHealthService.instance;
|
|
1880
|
+
}
|
|
1881
|
+
getFromCache(key) {
|
|
1882
|
+
const cached = this.cache.get(key);
|
|
1883
|
+
if (cached && Date.now() - cached.timestamp < this.cacheExpiryMs) {
|
|
1884
|
+
return cached.data;
|
|
1885
|
+
}
|
|
1886
|
+
this.cache.delete(key);
|
|
1887
|
+
return null;
|
|
1888
|
+
}
|
|
1889
|
+
setCache(key, data) {
|
|
1890
|
+
this.cache.set(key, { data, timestamp: Date.now() });
|
|
1891
|
+
}
|
|
1892
|
+
async getWorkspaceHealthStatus(options = {}) {
|
|
1893
|
+
const supabase = _getSupabaseInstance();
|
|
1894
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
1895
|
+
let query = supabase.from("workspace_health_status").select("*").order("workspace_display_name", { ascending: true });
|
|
1896
|
+
if (options.lineId) {
|
|
1897
|
+
query = query.eq("line_id", options.lineId);
|
|
1898
|
+
}
|
|
1899
|
+
if (options.companyId) {
|
|
1900
|
+
query = query.eq("company_id", options.companyId);
|
|
1901
|
+
}
|
|
1902
|
+
const { data, error } = await query;
|
|
1903
|
+
if (error) {
|
|
1904
|
+
console.error("Error fetching workspace health status:", error);
|
|
1905
|
+
throw error;
|
|
1906
|
+
}
|
|
1907
|
+
const processedData = (data || []).map((item) => this.processHealthStatus(item));
|
|
1908
|
+
let filteredData = processedData;
|
|
1909
|
+
try {
|
|
1910
|
+
const { data: enabledWorkspaces, error: workspaceError } = await supabase.from("workspaces").select("workspace_id, display_name").eq("enable", true);
|
|
1911
|
+
if (!workspaceError && enabledWorkspaces && enabledWorkspaces.length > 0) {
|
|
1912
|
+
const enabledWorkspaceNames = /* @__PURE__ */ new Set();
|
|
1913
|
+
enabledWorkspaces.forEach((w) => {
|
|
1914
|
+
if (w.workspace_id) enabledWorkspaceNames.add(w.workspace_id);
|
|
1915
|
+
if (w.display_name) enabledWorkspaceNames.add(w.display_name);
|
|
1916
|
+
});
|
|
1917
|
+
filteredData = filteredData.filter((item) => {
|
|
1918
|
+
const displayName = item.workspace_display_name || "";
|
|
1919
|
+
return enabledWorkspaceNames.has(displayName);
|
|
1920
|
+
});
|
|
1921
|
+
} else if (!workspaceError && enabledWorkspaces && enabledWorkspaces.length === 0) {
|
|
1922
|
+
return [];
|
|
1923
|
+
} else if (workspaceError) {
|
|
1924
|
+
console.error("Error fetching enabled workspaces:", workspaceError);
|
|
1925
|
+
}
|
|
1926
|
+
} catch (e) {
|
|
1927
|
+
console.error("Error filtering workspaces:", e);
|
|
1928
|
+
}
|
|
1929
|
+
if (options.status) {
|
|
1930
|
+
filteredData = filteredData.filter((item) => item.status === options.status);
|
|
1931
|
+
}
|
|
1932
|
+
if (options.searchTerm) {
|
|
1933
|
+
const searchLower = options.searchTerm.toLowerCase();
|
|
1934
|
+
filteredData = filteredData.filter(
|
|
1935
|
+
(item) => item.workspace_display_name?.toLowerCase().includes(searchLower) || item.line_name?.toLowerCase().includes(searchLower) || item.company_name?.toLowerCase().includes(searchLower)
|
|
1936
|
+
);
|
|
1937
|
+
}
|
|
1938
|
+
if (options.sortBy) {
|
|
1939
|
+
filteredData.sort((a, b) => {
|
|
1940
|
+
let compareValue = 0;
|
|
1941
|
+
switch (options.sortBy) {
|
|
1942
|
+
case "name":
|
|
1943
|
+
compareValue = (a.workspace_display_name || "").localeCompare(b.workspace_display_name || "");
|
|
1944
|
+
break;
|
|
1945
|
+
case "status":
|
|
1946
|
+
compareValue = this.getStatusPriority(a.status) - this.getStatusPriority(b.status);
|
|
1947
|
+
break;
|
|
1948
|
+
case "lastUpdate":
|
|
1949
|
+
compareValue = new Date(b.last_heartbeat).getTime() - new Date(a.last_heartbeat).getTime();
|
|
1950
|
+
break;
|
|
1951
|
+
}
|
|
1952
|
+
return options.sortOrder === "desc" ? -compareValue : compareValue;
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
return filteredData;
|
|
1956
|
+
}
|
|
1957
|
+
async getWorkspaceHealthById(workspaceId) {
|
|
1958
|
+
const cacheKey = `health-${workspaceId}`;
|
|
1959
|
+
const cached = this.getFromCache(cacheKey);
|
|
1960
|
+
if (cached) return cached;
|
|
1961
|
+
const supabase = _getSupabaseInstance();
|
|
1962
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
1963
|
+
const { data, error } = await supabase.from("workspace_health_status").select("*").eq("workspace_id", workspaceId).single();
|
|
1964
|
+
if (error) {
|
|
1965
|
+
if (error.code === "PGRST116") {
|
|
1966
|
+
return null;
|
|
1967
|
+
}
|
|
1968
|
+
console.error("Error fetching workspace health:", error);
|
|
1969
|
+
throw error;
|
|
1970
|
+
}
|
|
1971
|
+
const processedData = data ? this.processHealthStatus(data) : null;
|
|
1972
|
+
if (processedData) {
|
|
1973
|
+
this.setCache(cacheKey, processedData);
|
|
1974
|
+
}
|
|
1975
|
+
return processedData;
|
|
1976
|
+
}
|
|
1977
|
+
async getHealthSummary(lineId, companyId) {
|
|
1978
|
+
this.clearCache();
|
|
1979
|
+
const workspaces = await this.getWorkspaceHealthStatus({ lineId, companyId });
|
|
1980
|
+
const totalWorkspaces = workspaces.length;
|
|
1981
|
+
const healthyWorkspaces = workspaces.filter((w) => w.status === "healthy").length;
|
|
1982
|
+
const unhealthyWorkspaces = workspaces.filter((w) => w.status === "unhealthy").length;
|
|
1983
|
+
const warningWorkspaces = workspaces.filter((w) => w.status === "warning").length;
|
|
1984
|
+
const uptimePercentage = totalWorkspaces > 0 ? healthyWorkspaces / totalWorkspaces * 100 : 0;
|
|
1985
|
+
return {
|
|
1986
|
+
totalWorkspaces,
|
|
1987
|
+
healthyWorkspaces,
|
|
1988
|
+
unhealthyWorkspaces,
|
|
1989
|
+
warningWorkspaces,
|
|
1990
|
+
uptimePercentage,
|
|
1991
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
1992
|
+
};
|
|
1993
|
+
}
|
|
1994
|
+
async getHealthMetrics(workspaceId, startDate, endDate) {
|
|
1995
|
+
const supabase = _getSupabaseInstance();
|
|
1996
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
1997
|
+
return {
|
|
1998
|
+
avgResponseTime: 250,
|
|
1999
|
+
totalDowntime: 0,
|
|
2000
|
+
incidentCount: 0,
|
|
2001
|
+
mttr: 0
|
|
2002
|
+
};
|
|
2003
|
+
}
|
|
2004
|
+
processHealthStatus(data) {
|
|
2005
|
+
const now2 = /* @__PURE__ */ new Date();
|
|
2006
|
+
const lastHeartbeat = new Date(data.last_heartbeat);
|
|
2007
|
+
const minutesSinceUpdate = Math.floor((now2.getTime() - lastHeartbeat.getTime()) / (1e3 * 60));
|
|
2008
|
+
let status = "unknown";
|
|
2009
|
+
let isStale = false;
|
|
2010
|
+
if (data.is_healthy) {
|
|
2011
|
+
if (minutesSinceUpdate < 3) {
|
|
2012
|
+
status = "healthy";
|
|
2013
|
+
} else if (minutesSinceUpdate < 5) {
|
|
2014
|
+
status = "warning";
|
|
2015
|
+
isStale = true;
|
|
2016
|
+
} else {
|
|
2017
|
+
status = "unhealthy";
|
|
2018
|
+
isStale = true;
|
|
2019
|
+
}
|
|
2020
|
+
} else {
|
|
2021
|
+
status = "unhealthy";
|
|
2022
|
+
if (minutesSinceUpdate > 5) {
|
|
2023
|
+
isStale = true;
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
const timeSinceLastUpdate = dateFns.formatDistanceToNow(lastHeartbeat, { addSuffix: true });
|
|
2027
|
+
return {
|
|
2028
|
+
...data,
|
|
2029
|
+
status,
|
|
2030
|
+
timeSinceLastUpdate,
|
|
2031
|
+
isStale
|
|
2032
|
+
};
|
|
2033
|
+
}
|
|
2034
|
+
getStatusPriority(status) {
|
|
2035
|
+
const priorities = {
|
|
2036
|
+
unhealthy: 0,
|
|
2037
|
+
warning: 1,
|
|
2038
|
+
unknown: 2,
|
|
2039
|
+
healthy: 3
|
|
2040
|
+
};
|
|
2041
|
+
return priorities[status];
|
|
2042
|
+
}
|
|
2043
|
+
subscribeToHealthUpdates(callback, filters) {
|
|
2044
|
+
const supabase = _getSupabaseInstance();
|
|
2045
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
2046
|
+
let subscription = supabase.channel("workspace-health-updates").on(
|
|
2047
|
+
"postgres_changes",
|
|
2048
|
+
{
|
|
2049
|
+
event: "*",
|
|
2050
|
+
schema: "public",
|
|
2051
|
+
table: "workspace_health_status"
|
|
2052
|
+
},
|
|
2053
|
+
(payload) => {
|
|
2054
|
+
if (payload.new) {
|
|
2055
|
+
const newData = payload.new;
|
|
2056
|
+
if (filters?.lineId && newData.line_id !== filters.lineId) return;
|
|
2057
|
+
if (filters?.companyId && newData.company_id !== filters.companyId) return;
|
|
2058
|
+
this.clearCache();
|
|
2059
|
+
callback(newData);
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
).subscribe();
|
|
2063
|
+
return () => {
|
|
2064
|
+
subscription.unsubscribe();
|
|
2065
|
+
};
|
|
2066
|
+
}
|
|
2067
|
+
clearCache() {
|
|
2068
|
+
this.cache.clear();
|
|
2069
|
+
}
|
|
2070
|
+
};
|
|
2071
|
+
var workspaceHealthService = WorkspaceHealthService.getInstance();
|
|
1850
2072
|
|
|
1851
2073
|
// src/lib/services/skuService.ts
|
|
1852
2074
|
var getTable4 = (dbConfig, tableName) => {
|
|
@@ -3064,35 +3286,6 @@ var useAudioService = () => {
|
|
|
3064
3286
|
};
|
|
3065
3287
|
|
|
3066
3288
|
// src/lib/utils/dateShiftUtils.ts
|
|
3067
|
-
function getOperationalDate2(timezone = "Asia/Kolkata") {
|
|
3068
|
-
const now2 = /* @__PURE__ */ new Date();
|
|
3069
|
-
const localTime = new Date(now2.toLocaleString("en-US", { timeZone: timezone }));
|
|
3070
|
-
const hour = localTime.getHours();
|
|
3071
|
-
if (hour < 6) {
|
|
3072
|
-
localTime.setDate(localTime.getDate() - 1);
|
|
3073
|
-
}
|
|
3074
|
-
return localTime.toISOString().split("T")[0];
|
|
3075
|
-
}
|
|
3076
|
-
function getCurrentShift2(timezone = "Asia/Kolkata") {
|
|
3077
|
-
const now2 = /* @__PURE__ */ new Date();
|
|
3078
|
-
const localTime = new Date(now2.toLocaleString("en-US", { timeZone: timezone }));
|
|
3079
|
-
const hour = localTime.getHours();
|
|
3080
|
-
if (hour >= 6 && hour < 18) {
|
|
3081
|
-
return {
|
|
3082
|
-
shiftId: 0,
|
|
3083
|
-
shiftName: "Day Shift",
|
|
3084
|
-
startTime: "06:00",
|
|
3085
|
-
endTime: "18:00"
|
|
3086
|
-
};
|
|
3087
|
-
} else {
|
|
3088
|
-
return {
|
|
3089
|
-
shiftId: 1,
|
|
3090
|
-
shiftName: "Night Shift",
|
|
3091
|
-
startTime: "18:00",
|
|
3092
|
-
endTime: "06:00"
|
|
3093
|
-
};
|
|
3094
|
-
}
|
|
3095
|
-
}
|
|
3096
3289
|
function isValidDateFormat(date) {
|
|
3097
3290
|
return /^\d{4}-\d{2}-\d{2}$/.test(date);
|
|
3098
3291
|
}
|
|
@@ -3241,6 +3434,14 @@ function parseS3Uri(s3Uri, sopCategories) {
|
|
|
3241
3434
|
return null;
|
|
3242
3435
|
}
|
|
3243
3436
|
}
|
|
3437
|
+
function shuffleArray(array) {
|
|
3438
|
+
const shuffled = [...array];
|
|
3439
|
+
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
3440
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
3441
|
+
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
3442
|
+
}
|
|
3443
|
+
return shuffled;
|
|
3444
|
+
}
|
|
3244
3445
|
|
|
3245
3446
|
// src/lib/cache/clipsCache.ts
|
|
3246
3447
|
var LRUCache = class _LRUCache {
|
|
@@ -3753,300 +3954,321 @@ if (typeof window !== "undefined") {
|
|
|
3753
3954
|
});
|
|
3754
3955
|
});
|
|
3755
3956
|
}
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3957
|
+
var getSupabaseClient = () => {
|
|
3958
|
+
const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
3959
|
+
const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
|
3960
|
+
if (!url || !key) {
|
|
3961
|
+
throw new Error("Supabase configuration missing");
|
|
3962
|
+
}
|
|
3963
|
+
return supabaseJs.createClient(url, key);
|
|
3964
|
+
};
|
|
3965
|
+
var getAuthToken = async () => {
|
|
3966
|
+
try {
|
|
3967
|
+
const supabase = getSupabaseClient();
|
|
3968
|
+
const { data: { session } } = await supabase.auth.getSession();
|
|
3969
|
+
return session?.access_token || null;
|
|
3970
|
+
} catch (error) {
|
|
3971
|
+
console.error("[S3ClipsAPIClient] Error getting auth token:", error);
|
|
3972
|
+
return null;
|
|
3973
|
+
}
|
|
3974
|
+
};
|
|
3975
|
+
var S3ClipsAPIClient = class {
|
|
3976
|
+
constructor(sopCategories) {
|
|
3977
|
+
this.baseUrl = "/api/clips";
|
|
3978
|
+
this.requestCache = /* @__PURE__ */ new Map();
|
|
3979
|
+
this.sopCategories = sopCategories;
|
|
3980
|
+
console.log("[S3ClipsAPIClient] \u2705 Initialized - Using secure API routes (no direct S3 access)");
|
|
3765
3981
|
}
|
|
3766
3982
|
/**
|
|
3767
|
-
*
|
|
3983
|
+
* Fetch with authentication and error handling
|
|
3768
3984
|
*/
|
|
3769
|
-
async
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3985
|
+
async fetchWithAuth(endpoint, body) {
|
|
3986
|
+
const token = await getAuthToken();
|
|
3987
|
+
if (!token) {
|
|
3988
|
+
throw new Error("Authentication required");
|
|
3989
|
+
}
|
|
3990
|
+
const response = await fetch(endpoint, {
|
|
3991
|
+
method: "POST",
|
|
3992
|
+
headers: {
|
|
3993
|
+
"Authorization": `Bearer ${token}`,
|
|
3994
|
+
"Content-Type": "application/json"
|
|
3995
|
+
},
|
|
3996
|
+
body: JSON.stringify(body)
|
|
3997
|
+
});
|
|
3998
|
+
if (!response.ok) {
|
|
3999
|
+
const error = await response.json().catch(() => ({ error: "Request failed" }));
|
|
4000
|
+
throw new Error(error.error || `API error: ${response.status}`);
|
|
4001
|
+
}
|
|
4002
|
+
return response.json();
|
|
4003
|
+
}
|
|
4004
|
+
/**
|
|
4005
|
+
* Deduplicate requests to prevent multiple API calls
|
|
4006
|
+
*/
|
|
4007
|
+
async deduplicate(key, factory) {
|
|
4008
|
+
if (this.requestCache.has(key)) {
|
|
4009
|
+
console.log(`[S3ClipsAPIClient] Deduplicating request: ${key}`);
|
|
4010
|
+
return this.requestCache.get(key);
|
|
3775
4011
|
}
|
|
3776
|
-
console.log(`[${logPrefix}] Creating new request for key: ${key}`);
|
|
3777
4012
|
const promise = factory().finally(() => {
|
|
3778
|
-
this.
|
|
3779
|
-
console.log(`[${logPrefix}] Completed and cleaned up request: ${key}`);
|
|
4013
|
+
this.requestCache.delete(key);
|
|
3780
4014
|
});
|
|
3781
|
-
this.
|
|
4015
|
+
this.requestCache.set(key, promise);
|
|
3782
4016
|
return promise;
|
|
3783
4017
|
}
|
|
3784
4018
|
/**
|
|
3785
|
-
*
|
|
4019
|
+
* List S3 clips
|
|
3786
4020
|
*/
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
this.
|
|
4021
|
+
async listS3Clips(params) {
|
|
4022
|
+
const cacheKey = `list:${JSON.stringify(params)}`;
|
|
4023
|
+
return this.deduplicate(cacheKey, async () => {
|
|
4024
|
+
const response = await this.fetchWithAuth(this.baseUrl, {
|
|
4025
|
+
action: "list",
|
|
4026
|
+
workspaceId: params.workspaceId,
|
|
4027
|
+
date: params.date,
|
|
4028
|
+
shift: params.shiftId,
|
|
4029
|
+
maxKeys: params.maxKeys
|
|
4030
|
+
});
|
|
4031
|
+
return response.clips.map((clip) => clip.originalUri);
|
|
4032
|
+
});
|
|
3790
4033
|
}
|
|
3791
4034
|
/**
|
|
3792
|
-
* Get
|
|
4035
|
+
* Get clip counts
|
|
3793
4036
|
*/
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
4037
|
+
async getClipCounts(workspaceId, date, shiftId) {
|
|
4038
|
+
const cacheKey = `counts:${workspaceId}:${date}:${shiftId}`;
|
|
4039
|
+
return this.deduplicate(cacheKey, async () => {
|
|
4040
|
+
const response = await this.fetchWithAuth(this.baseUrl, {
|
|
4041
|
+
action: "count",
|
|
4042
|
+
workspaceId,
|
|
4043
|
+
date,
|
|
4044
|
+
shift: shiftId.toString()
|
|
4045
|
+
});
|
|
4046
|
+
return response.counts;
|
|
4047
|
+
});
|
|
4048
|
+
}
|
|
4049
|
+
/**
|
|
4050
|
+
* Get clip counts with index (for compatibility)
|
|
4051
|
+
*/
|
|
4052
|
+
async getClipCountsWithIndex(workspaceId, date, shiftId) {
|
|
4053
|
+
const counts = await this.getClipCounts(workspaceId, date, shiftId.toString());
|
|
4054
|
+
const videoIndex = {
|
|
4055
|
+
byCategory: /* @__PURE__ */ new Map(),
|
|
4056
|
+
allVideos: [],
|
|
4057
|
+
counts,
|
|
4058
|
+
workspaceId,
|
|
4059
|
+
date,
|
|
4060
|
+
shiftId: shiftId.toString(),
|
|
4061
|
+
lastUpdated: /* @__PURE__ */ new Date()
|
|
3798
4062
|
};
|
|
4063
|
+
return { counts, videoIndex };
|
|
3799
4064
|
}
|
|
3800
4065
|
/**
|
|
3801
|
-
*
|
|
4066
|
+
* Get metadata for a video
|
|
3802
4067
|
*/
|
|
3803
|
-
|
|
3804
|
-
const
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
4068
|
+
async getMetadata(playlistUri) {
|
|
4069
|
+
const cacheKey = `metadata:${playlistUri}`;
|
|
4070
|
+
return this.deduplicate(cacheKey, async () => {
|
|
4071
|
+
const response = await this.fetchWithAuth(this.baseUrl, {
|
|
4072
|
+
action: "metadata",
|
|
4073
|
+
playlistUri
|
|
4074
|
+
});
|
|
4075
|
+
return response.metadata;
|
|
4076
|
+
});
|
|
4077
|
+
}
|
|
4078
|
+
/**
|
|
4079
|
+
* Get metadata cycle time
|
|
4080
|
+
*/
|
|
4081
|
+
async getMetadataCycleTime(playlistUri) {
|
|
4082
|
+
const metadata = await this.getMetadata(playlistUri);
|
|
4083
|
+
return metadata?.cycle_time_seconds || null;
|
|
4084
|
+
}
|
|
4085
|
+
/**
|
|
4086
|
+
* Get first clip for category
|
|
4087
|
+
*/
|
|
4088
|
+
async getFirstClipForCategory(workspaceId, date, shiftId, category) {
|
|
4089
|
+
const cacheKey = `first:${workspaceId}:${date}:${shiftId}:${category}`;
|
|
4090
|
+
return this.deduplicate(cacheKey, async () => {
|
|
4091
|
+
const response = await this.fetchWithAuth(this.baseUrl, {
|
|
4092
|
+
action: "first",
|
|
4093
|
+
workspaceId,
|
|
4094
|
+
date,
|
|
4095
|
+
shift: shiftId.toString(),
|
|
4096
|
+
category,
|
|
4097
|
+
sopCategories: this.sopCategories
|
|
4098
|
+
});
|
|
4099
|
+
return response.video;
|
|
4100
|
+
});
|
|
4101
|
+
}
|
|
4102
|
+
/**
|
|
4103
|
+
* Get clip by index
|
|
4104
|
+
*/
|
|
4105
|
+
async getClipByIndex(workspaceId, date, shiftId, category, index, includeCycleTime = true, includeMetadata = false) {
|
|
4106
|
+
const cacheKey = `by-index:${workspaceId}:${date}:${shiftId}:${category}:${index}`;
|
|
4107
|
+
return this.deduplicate(cacheKey, async () => {
|
|
4108
|
+
const response = await this.fetchWithAuth(this.baseUrl, {
|
|
4109
|
+
action: "by-index",
|
|
4110
|
+
workspaceId,
|
|
4111
|
+
date,
|
|
4112
|
+
shift: shiftId.toString(),
|
|
4113
|
+
category,
|
|
4114
|
+
index,
|
|
4115
|
+
sopCategories: this.sopCategories
|
|
4116
|
+
});
|
|
4117
|
+
const video = response.video;
|
|
4118
|
+
if (video && includeMetadata && video.originalUri) {
|
|
4119
|
+
try {
|
|
4120
|
+
const metadata = await this.getMetadata(video.originalUri);
|
|
4121
|
+
if (metadata) {
|
|
4122
|
+
video.cycle_time_seconds = metadata.cycle_time_seconds;
|
|
4123
|
+
video.creation_timestamp = metadata.creation_timestamp;
|
|
4124
|
+
}
|
|
4125
|
+
} catch (error) {
|
|
4126
|
+
console.warn("[S3ClipsAPIClient] Failed to fetch metadata:", error);
|
|
4127
|
+
}
|
|
3813
4128
|
}
|
|
3814
|
-
|
|
3815
|
-
}
|
|
4129
|
+
return video;
|
|
4130
|
+
});
|
|
4131
|
+
}
|
|
4132
|
+
/**
|
|
4133
|
+
* Get videos page with pagination
|
|
4134
|
+
*/
|
|
4135
|
+
async getVideosPage(workspaceId, date, shiftId, category, pageSize = 5, startAfter) {
|
|
4136
|
+
const cacheKey = `page:${workspaceId}:${date}:${shiftId}:${category}:${pageSize}:${startAfter || "first"}`;
|
|
4137
|
+
return this.deduplicate(cacheKey, async () => {
|
|
4138
|
+
const response = await this.fetchWithAuth(this.baseUrl, {
|
|
4139
|
+
action: "page",
|
|
4140
|
+
workspaceId,
|
|
4141
|
+
date,
|
|
4142
|
+
shift: shiftId.toString(),
|
|
4143
|
+
category,
|
|
4144
|
+
pageSize,
|
|
4145
|
+
startAfter,
|
|
4146
|
+
sopCategories: this.sopCategories
|
|
4147
|
+
});
|
|
4148
|
+
return {
|
|
4149
|
+
videos: response.videos,
|
|
4150
|
+
nextToken: response.nextToken,
|
|
4151
|
+
hasMore: response.hasMore
|
|
4152
|
+
};
|
|
4153
|
+
});
|
|
4154
|
+
}
|
|
4155
|
+
/**
|
|
4156
|
+
* Convert S3 URI to CloudFront URL
|
|
4157
|
+
* In the API client, URLs are already signed from the server
|
|
4158
|
+
*/
|
|
4159
|
+
s3UriToCloudfront(s3Uri) {
|
|
4160
|
+
return s3Uri;
|
|
4161
|
+
}
|
|
4162
|
+
/**
|
|
4163
|
+
* Clean up resources
|
|
4164
|
+
*/
|
|
4165
|
+
dispose() {
|
|
4166
|
+
this.requestCache.clear();
|
|
4167
|
+
}
|
|
4168
|
+
/**
|
|
4169
|
+
* Get service statistics
|
|
4170
|
+
*/
|
|
4171
|
+
getStats() {
|
|
4172
|
+
return {
|
|
4173
|
+
requestCache: {
|
|
4174
|
+
pendingCount: this.requestCache.size,
|
|
4175
|
+
maxSize: 1e3
|
|
4176
|
+
}
|
|
4177
|
+
};
|
|
3816
4178
|
}
|
|
3817
4179
|
};
|
|
4180
|
+
|
|
4181
|
+
// src/lib/api/s3-clips-secure.ts
|
|
3818
4182
|
var S3ClipsService = class {
|
|
3819
4183
|
constructor(config) {
|
|
3820
|
-
//
|
|
3821
|
-
this.requestCache = new RequestDeduplicationCache();
|
|
3822
|
-
// Flag to prevent metadata fetching during index building
|
|
4184
|
+
// Flags for compatibility
|
|
3823
4185
|
this.isIndexBuilding = false;
|
|
3824
|
-
// Flag to prevent metadata fetching during entire prefetch operation
|
|
3825
4186
|
this.isPrefetching = false;
|
|
3826
|
-
// Global safeguard: limit concurrent metadata fetches to prevent flooding
|
|
3827
4187
|
this.currentMetadataFetches = 0;
|
|
3828
4188
|
this.MAX_CONCURRENT_METADATA = 3;
|
|
3829
4189
|
this.config = config;
|
|
3830
4190
|
if (!config.s3Config) {
|
|
3831
4191
|
throw new Error("S3 configuration is required");
|
|
3832
4192
|
}
|
|
4193
|
+
const sopCategories = config.s3Config.sopCategories?.default;
|
|
4194
|
+
this.apiClient = new S3ClipsAPIClient(sopCategories);
|
|
3833
4195
|
const processing = config.s3Config.processing || {};
|
|
3834
4196
|
this.defaultLimitPerCategory = processing.defaultLimitPerCategory || 30;
|
|
3835
4197
|
this.maxLimitPerCategory = processing.maxLimitPerCategory || 1e3;
|
|
3836
4198
|
this.concurrencyLimit = processing.concurrencyLimit || 10;
|
|
3837
4199
|
this.maxInitialFetch = processing.maxInitialFetch || 60;
|
|
3838
|
-
|
|
3839
|
-
console.log(`S3ClipsService: Using AWS region: ${region}`);
|
|
3840
|
-
this.s3Client = new clientS3.S3Client({
|
|
3841
|
-
region,
|
|
3842
|
-
credentials: config.s3Config.credentials ? {
|
|
3843
|
-
accessKeyId: config.s3Config.credentials.accessKeyId,
|
|
3844
|
-
secretAccessKey: config.s3Config.credentials.secretAccessKey
|
|
3845
|
-
} : void 0
|
|
3846
|
-
});
|
|
3847
|
-
}
|
|
3848
|
-
/**
|
|
3849
|
-
* Validates and sanitizes the AWS region
|
|
3850
|
-
*/
|
|
3851
|
-
validateAndSanitizeRegion(region) {
|
|
3852
|
-
const defaultRegion = "us-east-1";
|
|
3853
|
-
if (!region || typeof region !== "string") {
|
|
3854
|
-
console.warn(`S3ClipsService: Invalid region provided (${region}), using default: ${defaultRegion}`);
|
|
3855
|
-
return defaultRegion;
|
|
3856
|
-
}
|
|
3857
|
-
const sanitizedRegion = region.trim().toLowerCase();
|
|
3858
|
-
if (!sanitizedRegion) {
|
|
3859
|
-
console.warn(`S3ClipsService: Empty region provided, using default: ${defaultRegion}`);
|
|
3860
|
-
return defaultRegion;
|
|
3861
|
-
}
|
|
3862
|
-
const regionPattern = /^[a-z]{2,3}-[a-z]+-\d+$/;
|
|
3863
|
-
if (!regionPattern.test(sanitizedRegion)) {
|
|
3864
|
-
console.warn(`S3ClipsService: Invalid region format (${sanitizedRegion}), using default: ${defaultRegion}`);
|
|
3865
|
-
return defaultRegion;
|
|
3866
|
-
}
|
|
3867
|
-
return sanitizedRegion;
|
|
4200
|
+
console.log("[S3ClipsService] \u2705 Initialized with secure API client - AWS credentials are now server-side only!");
|
|
3868
4201
|
}
|
|
3869
4202
|
/**
|
|
3870
|
-
* Lists S3 clips
|
|
4203
|
+
* Lists S3 clips using API
|
|
3871
4204
|
*/
|
|
3872
4205
|
async listS3Clips(params) {
|
|
3873
|
-
const { workspaceId, date, shiftId
|
|
4206
|
+
const { workspaceId, date, shiftId } = params;
|
|
3874
4207
|
if (!isValidShiftId(shiftId)) {
|
|
3875
|
-
console.error(`[S3ClipsService] Invalid shift ID: ${shiftId}
|
|
4208
|
+
console.error(`[S3ClipsService] Invalid shift ID: ${shiftId}`);
|
|
3876
4209
|
return [];
|
|
3877
4210
|
}
|
|
3878
|
-
|
|
3879
|
-
console.log(`[S3ClipsService] Listing clips for workspace: ${workspaceId}, date: ${date}, shift: ${shiftId}`);
|
|
3880
|
-
const deduplicationKey = `list-s3-clips:${prefix}:${maxKeys || "all"}`;
|
|
3881
|
-
return this.requestCache.deduplicate(
|
|
3882
|
-
deduplicationKey,
|
|
3883
|
-
() => this.executeListS3Clips(params),
|
|
3884
|
-
"ListS3Clips"
|
|
3885
|
-
);
|
|
3886
|
-
}
|
|
3887
|
-
/**
|
|
3888
|
-
* Internal implementation of S3 listing (called through deduplication)
|
|
3889
|
-
*/
|
|
3890
|
-
async executeListS3Clips(params) {
|
|
3891
|
-
const { workspaceId, date, shiftId, maxKeys } = params;
|
|
3892
|
-
const prefix = `sop_violations/${workspaceId}/${date}/${shiftId}/`;
|
|
3893
|
-
console.log(`[S3ClipsService] Executing S3 list for prefix: ${prefix}`);
|
|
4211
|
+
console.log(`[S3ClipsService] Listing clips via API for workspace: ${workspaceId}`);
|
|
3894
4212
|
try {
|
|
3895
|
-
|
|
3896
|
-
let continuationToken = void 0;
|
|
3897
|
-
do {
|
|
3898
|
-
const command = new clientS3.ListObjectsV2Command({
|
|
3899
|
-
Bucket: this.config.s3Config.bucketName,
|
|
3900
|
-
Prefix: prefix,
|
|
3901
|
-
ContinuationToken: continuationToken,
|
|
3902
|
-
MaxKeys: maxKeys ?? 1e3
|
|
3903
|
-
});
|
|
3904
|
-
const response = await this.s3Client.send(command);
|
|
3905
|
-
console.log(`Got S3 response for ${prefix}:`, {
|
|
3906
|
-
keyCount: response.KeyCount,
|
|
3907
|
-
contentsLength: response.Contents?.length || 0,
|
|
3908
|
-
hasContinuation: !!response.NextContinuationToken
|
|
3909
|
-
});
|
|
3910
|
-
if (response.Contents) {
|
|
3911
|
-
if (response.Contents.length > 0) {
|
|
3912
|
-
console.log("Sample Keys:", response.Contents.slice(0, 3).map((obj) => obj.Key));
|
|
3913
|
-
}
|
|
3914
|
-
for (const obj of response.Contents) {
|
|
3915
|
-
if (obj.Key && obj.Key.endsWith("playlist.m3u8")) {
|
|
3916
|
-
if (obj.Key.includes("missed_qchecks")) {
|
|
3917
|
-
console.log(`Skipping missed_qchecks path: ${obj.Key}`);
|
|
3918
|
-
continue;
|
|
3919
|
-
}
|
|
3920
|
-
playlists.push(`s3://${this.config.s3Config.bucketName}/${obj.Key}`);
|
|
3921
|
-
}
|
|
3922
|
-
}
|
|
3923
|
-
}
|
|
3924
|
-
continuationToken = response.NextContinuationToken;
|
|
3925
|
-
if (maxKeys && playlists.length >= maxKeys) {
|
|
3926
|
-
break;
|
|
3927
|
-
}
|
|
3928
|
-
} while (continuationToken && (!maxKeys || playlists.length < maxKeys));
|
|
3929
|
-
console.log(`Found ${playlists.length} HLS playlists in ${prefix}`);
|
|
3930
|
-
if (playlists.length > 0) {
|
|
3931
|
-
console.log("First playlist URI:", playlists[0]);
|
|
3932
|
-
}
|
|
3933
|
-
return playlists;
|
|
4213
|
+
return await this.apiClient.listS3Clips(params);
|
|
3934
4214
|
} catch (error) {
|
|
3935
|
-
console.error(
|
|
4215
|
+
console.error("[S3ClipsService] Error listing clips:", error);
|
|
3936
4216
|
return [];
|
|
3937
4217
|
}
|
|
3938
4218
|
}
|
|
3939
4219
|
/**
|
|
3940
|
-
*
|
|
4220
|
+
* Get metadata cycle time
|
|
3941
4221
|
*/
|
|
3942
4222
|
async getMetadataCycleTime(playlistUri) {
|
|
3943
|
-
const deduplicationKey = `metadata-cycle-time:${playlistUri}`;
|
|
3944
|
-
return this.requestCache.deduplicate(
|
|
3945
|
-
deduplicationKey,
|
|
3946
|
-
() => this.executeGetMetadataCycleTime(playlistUri),
|
|
3947
|
-
"MetadataCycleTime"
|
|
3948
|
-
);
|
|
3949
|
-
}
|
|
3950
|
-
/**
|
|
3951
|
-
* Internal implementation of metadata cycle time fetching
|
|
3952
|
-
*/
|
|
3953
|
-
async executeGetMetadataCycleTime(playlistUri) {
|
|
3954
4223
|
try {
|
|
3955
|
-
|
|
3956
|
-
const url = new URL(metadataUri);
|
|
3957
|
-
const bucket = url.hostname;
|
|
3958
|
-
const key = url.pathname.substring(1);
|
|
3959
|
-
console.log(`[S3ClipsService] Fetching metadata cycle time for: ${key}`);
|
|
3960
|
-
const command = new clientS3.GetObjectCommand({
|
|
3961
|
-
Bucket: bucket,
|
|
3962
|
-
Key: key
|
|
3963
|
-
});
|
|
3964
|
-
const response = await this.s3Client.send(command);
|
|
3965
|
-
if (!response.Body) {
|
|
3966
|
-
console.warn(`Empty response body for metadata file: ${key}`);
|
|
3967
|
-
return null;
|
|
3968
|
-
}
|
|
3969
|
-
const metadataContent = await response.Body.transformToString();
|
|
3970
|
-
const metadata = JSON.parse(metadataContent);
|
|
3971
|
-
const cycleTimeFrames = metadata?.original_task_metadata?.cycle_time;
|
|
3972
|
-
if (typeof cycleTimeFrames === "number") {
|
|
3973
|
-
return cycleTimeFrames;
|
|
3974
|
-
}
|
|
3975
|
-
return null;
|
|
4224
|
+
return await this.apiClient.getMetadataCycleTime(playlistUri);
|
|
3976
4225
|
} catch (error) {
|
|
3977
|
-
console.error(
|
|
4226
|
+
console.error("[S3ClipsService] Error fetching metadata cycle time:", error);
|
|
3978
4227
|
return null;
|
|
3979
4228
|
}
|
|
3980
4229
|
}
|
|
3981
4230
|
/**
|
|
3982
|
-
* Control prefetch mode
|
|
4231
|
+
* Control prefetch mode
|
|
3983
4232
|
*/
|
|
3984
4233
|
setPrefetchMode(enabled) {
|
|
3985
4234
|
this.isPrefetching = enabled;
|
|
3986
|
-
console.log(`[S3ClipsService] Prefetch mode ${enabled ? "enabled" : "disabled"}
|
|
4235
|
+
console.log(`[S3ClipsService] Prefetch mode ${enabled ? "enabled" : "disabled"}`);
|
|
3987
4236
|
}
|
|
3988
4237
|
/**
|
|
3989
|
-
*
|
|
4238
|
+
* Get full metadata
|
|
3990
4239
|
*/
|
|
3991
4240
|
async getFullMetadata(playlistUri) {
|
|
3992
4241
|
if (this.isIndexBuilding || this.isPrefetching) {
|
|
3993
|
-
console.warn(
|
|
4242
|
+
console.warn("[S3ClipsService] Skipping metadata - operation in progress");
|
|
3994
4243
|
return null;
|
|
3995
4244
|
}
|
|
3996
4245
|
if (this.currentMetadataFetches >= this.MAX_CONCURRENT_METADATA) {
|
|
3997
|
-
console.warn(
|
|
4246
|
+
console.warn("[S3ClipsService] Skipping metadata - max concurrent fetches reached");
|
|
3998
4247
|
return null;
|
|
3999
4248
|
}
|
|
4000
4249
|
this.currentMetadataFetches++;
|
|
4001
4250
|
try {
|
|
4002
|
-
|
|
4003
|
-
return await this.requestCache.deduplicate(
|
|
4004
|
-
deduplicationKey,
|
|
4005
|
-
() => this.executeGetFullMetadata(playlistUri),
|
|
4006
|
-
"FullMetadata"
|
|
4007
|
-
);
|
|
4008
|
-
} finally {
|
|
4009
|
-
this.currentMetadataFetches--;
|
|
4010
|
-
}
|
|
4011
|
-
}
|
|
4012
|
-
/**
|
|
4013
|
-
* Internal implementation of full metadata fetching
|
|
4014
|
-
*/
|
|
4015
|
-
async executeGetFullMetadata(playlistUri) {
|
|
4016
|
-
try {
|
|
4017
|
-
const metadataUri = playlistUri.replace(/playlist\.m3u8$/, "metadata.json");
|
|
4018
|
-
const url = new URL(metadataUri);
|
|
4019
|
-
const bucket = url.hostname;
|
|
4020
|
-
const key = url.pathname.substring(1);
|
|
4021
|
-
console.log(`[S3ClipsService] Fetching full metadata for: ${key}`);
|
|
4022
|
-
const command = new clientS3.GetObjectCommand({
|
|
4023
|
-
Bucket: bucket,
|
|
4024
|
-
Key: key
|
|
4025
|
-
});
|
|
4026
|
-
const response = await this.s3Client.send(command);
|
|
4027
|
-
if (!response.Body) {
|
|
4028
|
-
console.warn(`Empty response body for metadata file: ${key}`);
|
|
4029
|
-
return null;
|
|
4030
|
-
}
|
|
4031
|
-
const metadataContent = await response.Body.transformToString();
|
|
4032
|
-
const metadata = JSON.parse(metadataContent);
|
|
4033
|
-
return metadata;
|
|
4251
|
+
return await this.apiClient.getMetadata(playlistUri);
|
|
4034
4252
|
} catch (error) {
|
|
4035
|
-
console.error(
|
|
4253
|
+
console.error("[S3ClipsService] Error fetching metadata:", error);
|
|
4036
4254
|
return null;
|
|
4255
|
+
} finally {
|
|
4256
|
+
this.currentMetadataFetches--;
|
|
4037
4257
|
}
|
|
4038
4258
|
}
|
|
4039
4259
|
/**
|
|
4040
|
-
*
|
|
4260
|
+
* Convert S3 URI to CloudFront URL
|
|
4261
|
+
* URLs from API are already signed
|
|
4041
4262
|
*/
|
|
4042
4263
|
s3UriToCloudfront(s3Uri) {
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4264
|
+
if (s3Uri.startsWith("http")) {
|
|
4265
|
+
return s3Uri;
|
|
4266
|
+
}
|
|
4267
|
+
console.warn("[S3ClipsService] Unexpected S3 URI in secure mode:", s3Uri);
|
|
4268
|
+
return s3Uri;
|
|
4047
4269
|
}
|
|
4048
4270
|
/**
|
|
4049
|
-
*
|
|
4271
|
+
* Get SOP categories for workspace
|
|
4050
4272
|
*/
|
|
4051
4273
|
getSOPCategories(workspaceId) {
|
|
4052
4274
|
const sopConfig = this.config.s3Config?.sopCategories;
|
|
@@ -4058,299 +4280,85 @@ var S3ClipsService = class {
|
|
|
4058
4280
|
}
|
|
4059
4281
|
async getClipCounts(workspaceId, date, shiftId, buildIndex) {
|
|
4060
4282
|
if (!isValidShiftId(shiftId)) {
|
|
4061
|
-
console.error(`[S3ClipsService]
|
|
4062
|
-
return buildIndex ? { counts: {}, videoIndex:
|
|
4063
|
-
}
|
|
4064
|
-
const deduplicationKey = `clip-counts:${workspaceId}:${date}:${shiftId}:${buildIndex ? "with-index" : "counts-only"}`;
|
|
4065
|
-
return this.requestCache.deduplicate(
|
|
4066
|
-
deduplicationKey,
|
|
4067
|
-
() => this.executeGetClipCounts(workspaceId, date, shiftId, buildIndex),
|
|
4068
|
-
"ClipCounts"
|
|
4069
|
-
);
|
|
4070
|
-
}
|
|
4071
|
-
/**
|
|
4072
|
-
* Internal implementation of clip counts fetching
|
|
4073
|
-
*/
|
|
4074
|
-
async executeGetClipCounts(workspaceId, date, shiftId, buildIndex) {
|
|
4075
|
-
const effectiveBuildIndex = false;
|
|
4283
|
+
console.error(`[S3ClipsService] Invalid shift ID: ${shiftId}`);
|
|
4284
|
+
return buildIndex ? { counts: {}, videoIndex: this.createEmptyVideoIndex(workspaceId, date, shiftId.toString()) } : {};
|
|
4285
|
+
}
|
|
4076
4286
|
try {
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
];
|
|
4090
|
-
const shiftName = shiftId === 0 || shiftId === "0" ? "Day" : "Night";
|
|
4091
|
-
console.log(`[S3ClipsService] Fast counting clips for ${workspaceId} on ${date}, shift ${shiftId} (${shiftName} Shift)`);
|
|
4092
|
-
const startTime = performance.now();
|
|
4093
|
-
const videoIndex = effectiveBuildIndex ? {
|
|
4094
|
-
byCategory: /* @__PURE__ */ new Map(),
|
|
4095
|
-
allVideos: [],
|
|
4096
|
-
counts: {},
|
|
4097
|
-
workspaceId,
|
|
4098
|
-
date,
|
|
4099
|
-
shiftId: shiftId.toString(),
|
|
4100
|
-
lastUpdated: /* @__PURE__ */ new Date(),
|
|
4101
|
-
_debugId: `vid_${Date.now()}_${Math.random().toString(36).substring(7)}`
|
|
4102
|
-
} : null;
|
|
4103
|
-
const countPromises = categoryFolders.map(async (category) => {
|
|
4104
|
-
const categoryPrefix = `${basePrefix}${category}/videos/`;
|
|
4105
|
-
const categoryVideos = [];
|
|
4106
|
-
try {
|
|
4107
|
-
if (effectiveBuildIndex) ; else {
|
|
4108
|
-
const command = new clientS3.ListObjectsV2Command({
|
|
4109
|
-
Bucket: this.config.s3Config.bucketName,
|
|
4110
|
-
Prefix: categoryPrefix,
|
|
4111
|
-
Delimiter: "/",
|
|
4112
|
-
MaxKeys: 1e3
|
|
4113
|
-
});
|
|
4114
|
-
let folderCount = 0;
|
|
4115
|
-
let continuationToken;
|
|
4116
|
-
do {
|
|
4117
|
-
if (continuationToken) {
|
|
4118
|
-
command.input.ContinuationToken = continuationToken;
|
|
4119
|
-
}
|
|
4120
|
-
const response = await this.s3Client.send(command);
|
|
4121
|
-
if (response.CommonPrefixes) {
|
|
4122
|
-
folderCount += response.CommonPrefixes.length;
|
|
4123
|
-
}
|
|
4124
|
-
continuationToken = response.NextContinuationToken;
|
|
4125
|
-
} while (continuationToken);
|
|
4126
|
-
return { category, count: folderCount, videos: [] };
|
|
4127
|
-
}
|
|
4128
|
-
} catch (error) {
|
|
4129
|
-
console.error(`Error ${buildIndex ? "building index for" : "counting folders for"} category ${category}:`, error);
|
|
4130
|
-
return { category, count: 0, videos: [] };
|
|
4131
|
-
}
|
|
4132
|
-
});
|
|
4133
|
-
const results = await Promise.all(countPromises);
|
|
4134
|
-
for (const { category, count, videos } of results) {
|
|
4135
|
-
counts[category] = count;
|
|
4136
|
-
counts.total += count;
|
|
4137
|
-
if (effectiveBuildIndex && videoIndex && videos) ;
|
|
4138
|
-
}
|
|
4139
|
-
if (effectiveBuildIndex && videoIndex) ;
|
|
4140
|
-
const elapsed = performance.now() - startTime;
|
|
4141
|
-
console.log(`[S3ClipsService] Clip counts completed in ${elapsed.toFixed(2)}ms - Total: ${counts.total}`);
|
|
4142
|
-
if (effectiveBuildIndex && videoIndex) ;
|
|
4143
|
-
return counts;
|
|
4144
|
-
} finally {
|
|
4287
|
+
if (buildIndex) {
|
|
4288
|
+
const result = await this.apiClient.getClipCountsWithIndex(workspaceId, date, shiftId);
|
|
4289
|
+
const cacheKey = `clip-counts:${workspaceId}:${date}:${shiftId}`;
|
|
4290
|
+
await smartVideoCache.setClipCounts(cacheKey, result);
|
|
4291
|
+
return result;
|
|
4292
|
+
} else {
|
|
4293
|
+
const counts = await this.apiClient.getClipCounts(workspaceId, date, shiftId);
|
|
4294
|
+
return counts;
|
|
4295
|
+
}
|
|
4296
|
+
} catch (error) {
|
|
4297
|
+
console.error("[S3ClipsService] Error fetching clip counts:", error);
|
|
4298
|
+
return buildIndex ? { counts: {}, videoIndex: this.createEmptyVideoIndex(workspaceId, date, shiftId.toString()) } : {};
|
|
4145
4299
|
}
|
|
4146
4300
|
}
|
|
4147
4301
|
async getClipCountsCacheFirst(workspaceId, date, shiftId, buildIndex) {
|
|
4148
4302
|
const cacheKey = `clip-counts:${workspaceId}:${date}:${shiftId}`;
|
|
4149
4303
|
const cachedResult = await smartVideoCache.getClipCounts(cacheKey);
|
|
4150
4304
|
if (cachedResult) {
|
|
4151
|
-
console.log(
|
|
4152
|
-
|
|
4153
|
-
return cachedResult;
|
|
4154
|
-
} else {
|
|
4155
|
-
return cachedResult.counts;
|
|
4156
|
-
}
|
|
4305
|
+
console.log("[S3ClipsService] Using cached clip counts");
|
|
4306
|
+
return buildIndex ? cachedResult : cachedResult.counts;
|
|
4157
4307
|
}
|
|
4158
|
-
console.log(
|
|
4308
|
+
console.log("[S3ClipsService] Cache miss - fetching from API");
|
|
4159
4309
|
return buildIndex ? this.getClipCounts(workspaceId, date, shiftId, true) : this.getClipCounts(workspaceId, date, shiftId);
|
|
4160
4310
|
}
|
|
4161
4311
|
/**
|
|
4162
|
-
* Get first clip for
|
|
4312
|
+
* Get first clip for category
|
|
4163
4313
|
*/
|
|
4164
4314
|
async getFirstClipForCategory(workspaceId, date, shiftId, category) {
|
|
4165
4315
|
if (!isValidShiftId(shiftId)) {
|
|
4166
|
-
console.error(`[S3ClipsService]
|
|
4316
|
+
console.error(`[S3ClipsService] Invalid shift ID: ${shiftId}`);
|
|
4167
4317
|
return null;
|
|
4168
4318
|
}
|
|
4169
|
-
const deduplicationKey = `first-clip:${workspaceId}:${date}:${shiftId}:${category}`;
|
|
4170
|
-
return this.requestCache.deduplicate(
|
|
4171
|
-
deduplicationKey,
|
|
4172
|
-
() => this.executeGetFirstClipForCategory(workspaceId, date, shiftId, category),
|
|
4173
|
-
"FirstClip"
|
|
4174
|
-
);
|
|
4175
|
-
}
|
|
4176
|
-
/**
|
|
4177
|
-
* Internal implementation of first clip fetching
|
|
4178
|
-
*/
|
|
4179
|
-
async executeGetFirstClipForCategory(workspaceId, date, shiftId, category) {
|
|
4180
|
-
const categoryPrefix = `sop_violations/${workspaceId}/${date}/${shiftId}/${category}/videos/`;
|
|
4181
4319
|
try {
|
|
4182
|
-
|
|
4183
|
-
Bucket: this.config.s3Config.bucketName,
|
|
4184
|
-
Prefix: categoryPrefix,
|
|
4185
|
-
MaxKeys: 10
|
|
4186
|
-
// Small limit since we only need one
|
|
4187
|
-
});
|
|
4188
|
-
const response = await this.s3Client.send(command);
|
|
4189
|
-
if (response.Contents) {
|
|
4190
|
-
const playlistObj = response.Contents.find((obj) => obj.Key?.endsWith("playlist.m3u8"));
|
|
4191
|
-
if (playlistObj && playlistObj.Key) {
|
|
4192
|
-
const s3Uri = `s3://${this.config.s3Config.bucketName}/${playlistObj.Key}`;
|
|
4193
|
-
const sopCategories = this.getSOPCategories(workspaceId);
|
|
4194
|
-
const parsedInfo = parseS3Uri(s3Uri, sopCategories);
|
|
4195
|
-
if (parsedInfo) {
|
|
4196
|
-
const cloudfrontUrl = this.s3UriToCloudfront(s3Uri);
|
|
4197
|
-
return {
|
|
4198
|
-
id: `${workspaceId}-${date}-${shiftId}-${category}-first`,
|
|
4199
|
-
src: cloudfrontUrl,
|
|
4200
|
-
...parsedInfo,
|
|
4201
|
-
originalUri: s3Uri
|
|
4202
|
-
};
|
|
4203
|
-
}
|
|
4204
|
-
}
|
|
4205
|
-
}
|
|
4320
|
+
return await this.apiClient.getFirstClipForCategory(workspaceId, date, shiftId, category);
|
|
4206
4321
|
} catch (error) {
|
|
4207
|
-
console.error(
|
|
4322
|
+
console.error("[S3ClipsService] Error fetching first clip:", error);
|
|
4323
|
+
return null;
|
|
4208
4324
|
}
|
|
4209
|
-
return null;
|
|
4210
4325
|
}
|
|
4211
4326
|
/**
|
|
4212
|
-
* Get
|
|
4327
|
+
* Get video from index (for compatibility)
|
|
4213
4328
|
*/
|
|
4214
4329
|
async getVideoFromIndex(videoIndex, category, index, includeCycleTime = true, includeMetadata = true) {
|
|
4330
|
+
const { workspaceId, date, shiftId } = videoIndex;
|
|
4331
|
+
return this.getClipByIndex(
|
|
4332
|
+
workspaceId,
|
|
4333
|
+
date,
|
|
4334
|
+
shiftId,
|
|
4335
|
+
category,
|
|
4336
|
+
index,
|
|
4337
|
+
includeCycleTime,
|
|
4338
|
+
includeMetadata
|
|
4339
|
+
);
|
|
4340
|
+
}
|
|
4341
|
+
/**
|
|
4342
|
+
* Get clip by index
|
|
4343
|
+
*/
|
|
4344
|
+
async getClipByIndex(workspaceId, date, shiftId, category, index, includeCycleTime = true, includeMetadata = false) {
|
|
4215
4345
|
try {
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
console.warn(`Video index total videos: ${videoIndex.allVideos.length}`);
|
|
4222
|
-
return null;
|
|
4223
|
-
}
|
|
4224
|
-
const videoEntry = categoryVideos[index];
|
|
4225
|
-
return this.processFullVideo(
|
|
4226
|
-
videoEntry.uri,
|
|
4346
|
+
return await this.apiClient.getClipByIndex(
|
|
4347
|
+
workspaceId,
|
|
4348
|
+
date,
|
|
4349
|
+
shiftId,
|
|
4350
|
+
category,
|
|
4227
4351
|
index,
|
|
4228
|
-
videoEntry.workspaceId,
|
|
4229
|
-
videoEntry.date,
|
|
4230
|
-
videoEntry.shiftId,
|
|
4231
4352
|
includeCycleTime,
|
|
4232
4353
|
includeMetadata
|
|
4233
4354
|
);
|
|
4234
4355
|
} catch (error) {
|
|
4235
|
-
console.error(
|
|
4356
|
+
console.error("[S3ClipsService] Error fetching clip by index:", error);
|
|
4236
4357
|
return null;
|
|
4237
4358
|
}
|
|
4238
4359
|
}
|
|
4239
4360
|
/**
|
|
4240
|
-
*
|
|
4241
|
-
* @deprecated Use getVideoFromIndex with a pre-built VideoIndex for better performance
|
|
4242
|
-
*/
|
|
4243
|
-
async getClipByIndex(workspaceId, date, shiftId, category, index, includeCycleTime = true, includeMetadata = false) {
|
|
4244
|
-
const deduplicationKey = `clip-by-index:${workspaceId}:${date}:${shiftId}:${category}:${index}:${includeCycleTime}:${includeMetadata}`;
|
|
4245
|
-
return this.requestCache.deduplicate(
|
|
4246
|
-
deduplicationKey,
|
|
4247
|
-
() => this.executeGetClipByIndex(workspaceId, date, shiftId, category, index, includeCycleTime, includeMetadata),
|
|
4248
|
-
"ClipByIndex"
|
|
4249
|
-
);
|
|
4250
|
-
}
|
|
4251
|
-
/**
|
|
4252
|
-
* Internal implementation of clip by index fetching
|
|
4253
|
-
*/
|
|
4254
|
-
async executeGetClipByIndex(workspaceId, date, shiftId, category, index, includeCycleTime = true, includeMetadata = false) {
|
|
4255
|
-
const categoryPrefix = `sop_violations/${workspaceId}/${date}/${shiftId}/${category}/videos/`;
|
|
4256
|
-
try {
|
|
4257
|
-
const command = new clientS3.ListObjectsV2Command({
|
|
4258
|
-
Bucket: this.config.s3Config.bucketName,
|
|
4259
|
-
Prefix: categoryPrefix,
|
|
4260
|
-
MaxKeys: 1e3
|
|
4261
|
-
});
|
|
4262
|
-
let playlists = [];
|
|
4263
|
-
let continuationToken = void 0;
|
|
4264
|
-
do {
|
|
4265
|
-
if (continuationToken) {
|
|
4266
|
-
command.input.ContinuationToken = continuationToken;
|
|
4267
|
-
}
|
|
4268
|
-
const response = await this.s3Client.send(command);
|
|
4269
|
-
if (response.Contents) {
|
|
4270
|
-
for (const obj of response.Contents) {
|
|
4271
|
-
if (obj.Key && obj.Key.endsWith("playlist.m3u8")) {
|
|
4272
|
-
playlists.push(`s3://${this.config.s3Config.bucketName}/${obj.Key}`);
|
|
4273
|
-
}
|
|
4274
|
-
}
|
|
4275
|
-
}
|
|
4276
|
-
continuationToken = response.NextContinuationToken;
|
|
4277
|
-
} while (continuationToken && playlists.length < index + 10);
|
|
4278
|
-
playlists.sort((a, b) => {
|
|
4279
|
-
const parsedA = parseS3Uri(a);
|
|
4280
|
-
const parsedB = parseS3Uri(b);
|
|
4281
|
-
if (!parsedA || !parsedB) return 0;
|
|
4282
|
-
return parsedB.timestamp.localeCompare(parsedA.timestamp);
|
|
4283
|
-
});
|
|
4284
|
-
if (index < playlists.length) {
|
|
4285
|
-
const uri = playlists[index];
|
|
4286
|
-
return this.processFullVideo(
|
|
4287
|
-
uri,
|
|
4288
|
-
index,
|
|
4289
|
-
workspaceId,
|
|
4290
|
-
date,
|
|
4291
|
-
shiftId.toString(),
|
|
4292
|
-
includeCycleTime,
|
|
4293
|
-
includeMetadata
|
|
4294
|
-
);
|
|
4295
|
-
}
|
|
4296
|
-
} catch (error) {
|
|
4297
|
-
console.error(`[getClipByIndex] Error getting clip at index ${index} for category ${category}:`, error);
|
|
4298
|
-
}
|
|
4299
|
-
return null;
|
|
4300
|
-
}
|
|
4301
|
-
/**
|
|
4302
|
-
* Get one sample video from each category for preloading
|
|
4303
|
-
*/
|
|
4304
|
-
async getSampleVideos(workspaceId, date, shiftId) {
|
|
4305
|
-
const basePrefix = `sop_violations/${workspaceId}/${date}/${shiftId}/`;
|
|
4306
|
-
const samples = {};
|
|
4307
|
-
const sopCategories = this.getSOPCategories(workspaceId);
|
|
4308
|
-
const categoriesToSample = sopCategories ? sopCategories.map((cat) => cat.id) : ["low_value", "best_cycle_time", "worst_cycle_time", "long_cycle_time", "cycle_completion"];
|
|
4309
|
-
console.log(`[S3ClipsService] Getting sample videos for categories:`, categoriesToSample);
|
|
4310
|
-
const samplePromises = categoriesToSample.map(async (category) => {
|
|
4311
|
-
const categoryPrefix = `${basePrefix}${category}/`;
|
|
4312
|
-
try {
|
|
4313
|
-
const command = new clientS3.ListObjectsV2Command({
|
|
4314
|
-
Bucket: this.config.s3Config.bucketName,
|
|
4315
|
-
Prefix: categoryPrefix,
|
|
4316
|
-
MaxKeys: 20
|
|
4317
|
-
// Small limit since we only need one
|
|
4318
|
-
});
|
|
4319
|
-
const response = await this.s3Client.send(command);
|
|
4320
|
-
if (response.Contents) {
|
|
4321
|
-
const playlistObj = response.Contents.find((obj) => obj.Key?.endsWith("playlist.m3u8"));
|
|
4322
|
-
if (playlistObj && playlistObj.Key) {
|
|
4323
|
-
const s3Uri = `s3://${this.config.s3Config.bucketName}/${playlistObj.Key}`;
|
|
4324
|
-
const parsedInfo = parseS3Uri(s3Uri, sopCategories);
|
|
4325
|
-
if (parsedInfo) {
|
|
4326
|
-
return {
|
|
4327
|
-
category,
|
|
4328
|
-
sample: {
|
|
4329
|
-
id: `${workspaceId}-${date}-${shiftId}-${category}-sample`,
|
|
4330
|
-
src: this.s3UriToCloudfront(s3Uri),
|
|
4331
|
-
// Pre-convert to CloudFront URL
|
|
4332
|
-
...parsedInfo,
|
|
4333
|
-
originalUri: s3Uri
|
|
4334
|
-
}
|
|
4335
|
-
};
|
|
4336
|
-
}
|
|
4337
|
-
}
|
|
4338
|
-
}
|
|
4339
|
-
} catch (error) {
|
|
4340
|
-
console.error(`Error getting sample for category ${category}:`, error);
|
|
4341
|
-
}
|
|
4342
|
-
return { category, sample: null };
|
|
4343
|
-
});
|
|
4344
|
-
const sampleResults = await Promise.all(samplePromises);
|
|
4345
|
-
for (const { category, sample } of sampleResults) {
|
|
4346
|
-
if (sample) {
|
|
4347
|
-
samples[category] = sample;
|
|
4348
|
-
}
|
|
4349
|
-
}
|
|
4350
|
-
return samples;
|
|
4351
|
-
}
|
|
4352
|
-
/**
|
|
4353
|
-
* Processes a single video completely
|
|
4361
|
+
* Process full video (for compatibility)
|
|
4354
4362
|
*/
|
|
4355
4363
|
async processFullVideo(uri, index, workspaceId, date, shiftId, includeCycleTime, includeMetadata = false) {
|
|
4356
4364
|
const sopCategories = this.getSOPCategories(workspaceId);
|
|
@@ -4359,39 +4367,43 @@ var S3ClipsService = class {
|
|
|
4359
4367
|
console.warn(`Skipping URI due to parsing failure: ${uri}`);
|
|
4360
4368
|
return null;
|
|
4361
4369
|
}
|
|
4362
|
-
|
|
4363
|
-
|
|
4370
|
+
const video = {
|
|
4371
|
+
id: `${workspaceId}-${date}-${shiftId}-${index}`,
|
|
4372
|
+
src: uri,
|
|
4373
|
+
// Already signed from API
|
|
4374
|
+
...parsedInfo,
|
|
4375
|
+
originalUri: uri
|
|
4376
|
+
};
|
|
4364
4377
|
if (includeMetadata) {
|
|
4365
4378
|
const metadata = await this.getFullMetadata(uri);
|
|
4366
4379
|
if (metadata) {
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
}
|
|
4370
|
-
creationTimestamp = metadata.upload_timestamp || metadata.original_task_metadata?.timestamp || metadata.creation_timestamp || metadata[""];
|
|
4380
|
+
video.cycle_time_seconds = metadata.original_task_metadata?.cycle_time;
|
|
4381
|
+
video.creation_timestamp = metadata.upload_timestamp || metadata.original_task_metadata?.timestamp || metadata.creation_timestamp;
|
|
4371
4382
|
}
|
|
4372
|
-
} else if (includeCycleTime && (parsedInfo.type === "bottleneck" && parsedInfo.description.toLowerCase().includes("cycle time") || parsedInfo.type === "best_cycle_time" || parsedInfo.type === "worst_cycle_time" || parsedInfo.type === "cycle_completion")) {
|
|
4373
|
-
cycleTimeSeconds = null;
|
|
4374
4383
|
}
|
|
4375
|
-
|
|
4376
|
-
const { type: videoType, timestamp: videoTimestamp } = parsedInfo;
|
|
4377
|
-
return {
|
|
4378
|
-
id: `${workspaceId}-${date}-${shiftId}-${videoType}-${videoTimestamp.replace(/:/g, "")}-${index}`,
|
|
4379
|
-
src: cloudfrontPlaylistUrl,
|
|
4380
|
-
// Direct CloudFront playlist URL
|
|
4381
|
-
...parsedInfo,
|
|
4382
|
-
cycle_time_seconds: cycleTimeSeconds || void 0,
|
|
4383
|
-
creation_timestamp: creationTimestamp
|
|
4384
|
-
};
|
|
4384
|
+
return video;
|
|
4385
4385
|
}
|
|
4386
4386
|
/**
|
|
4387
|
-
*
|
|
4387
|
+
* Fetch clips (main method for compatibility)
|
|
4388
4388
|
*/
|
|
4389
4389
|
async fetchClips(params) {
|
|
4390
|
-
const {
|
|
4390
|
+
const {
|
|
4391
|
+
workspaceId,
|
|
4392
|
+
date: inputDate,
|
|
4393
|
+
shift,
|
|
4394
|
+
category,
|
|
4395
|
+
limit,
|
|
4396
|
+
offset = 0,
|
|
4397
|
+
mode
|
|
4398
|
+
} = params;
|
|
4391
4399
|
if (!workspaceId) {
|
|
4392
4400
|
throw new Error("Valid Workspace ID is required");
|
|
4393
4401
|
}
|
|
4394
|
-
const date = inputDate ||
|
|
4402
|
+
const date = inputDate || getOperationalDate(
|
|
4403
|
+
this.config.dateTimeConfig?.defaultTimezone || "Asia/Kolkata",
|
|
4404
|
+
/* @__PURE__ */ new Date(),
|
|
4405
|
+
this.config.shiftConfig?.dayShift?.startTime || "06:00"
|
|
4406
|
+
);
|
|
4395
4407
|
if (!isValidDateFormat(date)) {
|
|
4396
4408
|
throw new Error("Invalid date format. Use YYYY-MM-DD.");
|
|
4397
4409
|
}
|
|
@@ -4402,160 +4414,72 @@ var S3ClipsService = class {
|
|
|
4402
4414
|
}
|
|
4403
4415
|
shiftId = parseInt(shift, 10);
|
|
4404
4416
|
} else {
|
|
4405
|
-
const { shiftId: currentShiftId } =
|
|
4417
|
+
const { shiftId: currentShiftId } = getCurrentShift(
|
|
4418
|
+
this.config.dateTimeConfig?.defaultTimezone || "Asia/Kolkata",
|
|
4419
|
+
this.config.shiftConfig
|
|
4420
|
+
);
|
|
4406
4421
|
shiftId = currentShiftId;
|
|
4407
4422
|
}
|
|
4408
|
-
console.log(`S3ClipsService
|
|
4423
|
+
console.log(`[S3ClipsService] Fetching clips for workspace ${workspaceId}`);
|
|
4409
4424
|
if (mode === "summary") {
|
|
4410
4425
|
const counts = await this.getClipCounts(workspaceId, date, shiftId.toString());
|
|
4411
4426
|
return { counts, samples: {} };
|
|
4412
4427
|
}
|
|
4413
4428
|
const effectiveLimit = limit || this.defaultLimitPerCategory;
|
|
4414
|
-
const
|
|
4429
|
+
const result = await this.getVideosPage(
|
|
4415
4430
|
workspaceId,
|
|
4416
4431
|
date,
|
|
4417
4432
|
shiftId,
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
if (!parsedInfo) return false;
|
|
4430
|
-
if (category === "long_cycle_time") {
|
|
4431
|
-
return parsedInfo.type === "long_cycle_time" || parsedInfo.type === "bottleneck" && parsedInfo.description.toLowerCase().includes("cycle time");
|
|
4432
|
-
}
|
|
4433
|
-
return parsedInfo.type === category;
|
|
4434
|
-
});
|
|
4435
|
-
filteredUris = filteredUris.slice(offset, offset + effectiveLimit);
|
|
4436
|
-
}
|
|
4437
|
-
const videoPromises = filteredUris.map(async (uri, index) => {
|
|
4438
|
-
return this.processFullVideo(
|
|
4439
|
-
uri,
|
|
4440
|
-
index,
|
|
4433
|
+
category || "all",
|
|
4434
|
+
effectiveLimit
|
|
4435
|
+
);
|
|
4436
|
+
return result.videos;
|
|
4437
|
+
}
|
|
4438
|
+
/**
|
|
4439
|
+
* Get videos page using pagination API
|
|
4440
|
+
*/
|
|
4441
|
+
async getVideosPage(workspaceId, date, shiftId, category, pageSize = 5, startAfter) {
|
|
4442
|
+
try {
|
|
4443
|
+
return await this.apiClient.getVideosPage(
|
|
4441
4444
|
workspaceId,
|
|
4442
4445
|
date,
|
|
4443
|
-
shiftId
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
|
|
4446
|
+
shiftId,
|
|
4447
|
+
category,
|
|
4448
|
+
pageSize,
|
|
4449
|
+
startAfter
|
|
4447
4450
|
);
|
|
4448
|
-
})
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
if (timestampStart || timestampEnd) {
|
|
4452
|
-
videos = videos.filter((video) => {
|
|
4453
|
-
if (!video.creation_timestamp) return false;
|
|
4454
|
-
const videoTimestamp = new Date(video.creation_timestamp).getTime();
|
|
4455
|
-
if (timestampStart && timestampEnd) {
|
|
4456
|
-
const start = new Date(timestampStart).getTime();
|
|
4457
|
-
const end = new Date(timestampEnd).getTime();
|
|
4458
|
-
return videoTimestamp >= start && videoTimestamp <= end;
|
|
4459
|
-
} else if (timestampStart) {
|
|
4460
|
-
const start = new Date(timestampStart).getTime();
|
|
4461
|
-
return videoTimestamp >= start;
|
|
4462
|
-
} else if (timestampEnd) {
|
|
4463
|
-
const end = new Date(timestampEnd).getTime();
|
|
4464
|
-
return videoTimestamp <= end;
|
|
4465
|
-
}
|
|
4466
|
-
return true;
|
|
4467
|
-
});
|
|
4468
|
-
}
|
|
4469
|
-
videos.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
4470
|
-
const cacheKey = `videos:${workspaceId}:${date}:${shiftId}:${category || "all"}`;
|
|
4471
|
-
if (videos.length > 0) {
|
|
4472
|
-
smartVideoCache.setVideos(cacheKey, videos);
|
|
4451
|
+
} catch (error) {
|
|
4452
|
+
console.error("[S3ClipsService] Error fetching videos page:", error);
|
|
4453
|
+
return { videos: [], hasMore: false };
|
|
4473
4454
|
}
|
|
4474
|
-
return videos;
|
|
4475
4455
|
}
|
|
4476
4456
|
/**
|
|
4477
|
-
*
|
|
4478
|
-
* This method replaces the need for full video indexing
|
|
4479
|
-
* @param workspaceId - Workspace ID
|
|
4480
|
-
* @param date - Date in YYYY-MM-DD format
|
|
4481
|
-
* @param shiftId - Shift ID (0 for day, 1 for night)
|
|
4482
|
-
* @param category - Category to fetch videos from
|
|
4483
|
-
* @param pageSize - Number of videos to fetch per page (default 5)
|
|
4484
|
-
* @param startAfter - Optional key to start after for pagination
|
|
4485
|
-
* @returns Page of videos with continuation token
|
|
4457
|
+
* Create empty video index for compatibility
|
|
4486
4458
|
*/
|
|
4487
|
-
|
|
4488
|
-
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
console.log(`[S3ClipsService] Fetching page of ${pageSize} videos for category '${category}'`);
|
|
4499
|
-
const command = new clientS3.ListObjectsV2Command({
|
|
4500
|
-
Bucket: this.config.s3Config.bucketName,
|
|
4501
|
-
Prefix: categoryPrefix,
|
|
4502
|
-
MaxKeys: pageSize * 10,
|
|
4503
|
-
// Fetch extra to account for non-playlist files
|
|
4504
|
-
StartAfter: startAfter
|
|
4505
|
-
});
|
|
4506
|
-
const response = await this.s3Client.send(command);
|
|
4507
|
-
const videos = [];
|
|
4508
|
-
if (response.Contents) {
|
|
4509
|
-
const sopCategories = this.getSOPCategories(workspaceId);
|
|
4510
|
-
for (const obj of response.Contents) {
|
|
4511
|
-
if (videos.length >= pageSize) break;
|
|
4512
|
-
if (obj.Key && obj.Key.endsWith("playlist.m3u8")) {
|
|
4513
|
-
if (obj.Key.includes("missed_qchecks")) continue;
|
|
4514
|
-
const s3Uri = `s3://${this.config.s3Config.bucketName}/${obj.Key}`;
|
|
4515
|
-
const parsedInfo = parseS3Uri(s3Uri, sopCategories);
|
|
4516
|
-
if (parsedInfo) {
|
|
4517
|
-
const cloudfrontUrl = this.s3UriToCloudfront(s3Uri);
|
|
4518
|
-
videos.push({
|
|
4519
|
-
id: `${workspaceId}-${date}-${shiftId}-${category}-${videos.length}`,
|
|
4520
|
-
src: cloudfrontUrl,
|
|
4521
|
-
...parsedInfo,
|
|
4522
|
-
originalUri: s3Uri
|
|
4523
|
-
});
|
|
4524
|
-
}
|
|
4525
|
-
}
|
|
4526
|
-
}
|
|
4527
|
-
const lastKey = response.Contents[response.Contents.length - 1]?.Key;
|
|
4528
|
-
const hasMore = !!response.IsTruncated || response.Contents.length === pageSize * 10;
|
|
4529
|
-
console.log(`[S3ClipsService] Fetched ${videos.length} videos, hasMore: ${hasMore}`);
|
|
4530
|
-
return {
|
|
4531
|
-
videos,
|
|
4532
|
-
nextToken: hasMore ? lastKey : void 0,
|
|
4533
|
-
hasMore
|
|
4534
|
-
};
|
|
4535
|
-
}
|
|
4536
|
-
return { videos: [], hasMore: false };
|
|
4537
|
-
} catch (error) {
|
|
4538
|
-
console.error(`[S3ClipsService] Error fetching videos page:`, error);
|
|
4539
|
-
return { videos: [], hasMore: false };
|
|
4540
|
-
}
|
|
4541
|
-
},
|
|
4542
|
-
"VideosPage"
|
|
4543
|
-
);
|
|
4459
|
+
createEmptyVideoIndex(workspaceId, date, shiftId) {
|
|
4460
|
+
return {
|
|
4461
|
+
byCategory: /* @__PURE__ */ new Map(),
|
|
4462
|
+
allVideos: [],
|
|
4463
|
+
counts: {},
|
|
4464
|
+
workspaceId,
|
|
4465
|
+
date,
|
|
4466
|
+
shiftId,
|
|
4467
|
+
lastUpdated: /* @__PURE__ */ new Date(),
|
|
4468
|
+
_debugId: `empty_${Date.now()}`
|
|
4469
|
+
};
|
|
4544
4470
|
}
|
|
4545
4471
|
/**
|
|
4546
|
-
* Cleanup
|
|
4472
|
+
* Cleanup
|
|
4547
4473
|
*/
|
|
4548
4474
|
dispose() {
|
|
4549
|
-
console.log("[S3ClipsService] Disposing service
|
|
4550
|
-
this.
|
|
4475
|
+
console.log("[S3ClipsService] Disposing service");
|
|
4476
|
+
this.apiClient.dispose();
|
|
4551
4477
|
}
|
|
4552
4478
|
/**
|
|
4553
|
-
* Get
|
|
4479
|
+
* Get statistics
|
|
4554
4480
|
*/
|
|
4555
4481
|
getStats() {
|
|
4556
|
-
return
|
|
4557
|
-
requestCache: this.requestCache.getStats()
|
|
4558
|
-
};
|
|
4482
|
+
return this.apiClient.getStats();
|
|
4559
4483
|
}
|
|
4560
4484
|
};
|
|
4561
4485
|
|
|
@@ -8133,7 +8057,7 @@ var useActiveBreaks = (lineIds) => {
|
|
|
8133
8057
|
}
|
|
8134
8058
|
return { elapsedMinutes, remainingMinutes };
|
|
8135
8059
|
};
|
|
8136
|
-
const
|
|
8060
|
+
const getCurrentShift2 = (currentMinutes, dayStart, nightStart) => {
|
|
8137
8061
|
const dayStartMinutes = parseTimeToMinutes2(dayStart);
|
|
8138
8062
|
const nightStartMinutes = parseTimeToMinutes2(nightStart);
|
|
8139
8063
|
if (nightStartMinutes < dayStartMinutes) {
|
|
@@ -8165,7 +8089,7 @@ var useActiveBreaks = (lineIds) => {
|
|
|
8165
8089
|
const dayShift = dayShifts?.find((s) => s.line_id === lineId);
|
|
8166
8090
|
const nightShift = nightShifts?.find((s) => s.line_id === lineId);
|
|
8167
8091
|
if (!dayShift || !nightShift) continue;
|
|
8168
|
-
const currentShift =
|
|
8092
|
+
const currentShift = getCurrentShift2(
|
|
8169
8093
|
currentMinutes,
|
|
8170
8094
|
dayShift.start_time || "06:00",
|
|
8171
8095
|
nightShift.start_time || "18:00"
|
|
@@ -11662,9 +11586,12 @@ var usePrefetchClipCounts = ({
|
|
|
11662
11586
|
shiftStr = "0";
|
|
11663
11587
|
console.log(`[usePrefetchClipCounts] No shift provided for historical date ${date}, defaulting to day shift (0)`);
|
|
11664
11588
|
} else {
|
|
11665
|
-
const currentShift =
|
|
11589
|
+
const currentShift = getCurrentShift(
|
|
11590
|
+
dashboardConfig.dateTimeConfig?.defaultTimezone || "Asia/Kolkata",
|
|
11591
|
+
dashboardConfig.shiftConfig
|
|
11592
|
+
);
|
|
11666
11593
|
shiftStr = currentShift.shiftId.toString();
|
|
11667
|
-
console.log(`[usePrefetchClipCounts] Using current operational shift: ${shiftStr}
|
|
11594
|
+
console.log(`[usePrefetchClipCounts] Using current operational shift: ${shiftStr}`);
|
|
11668
11595
|
}
|
|
11669
11596
|
return {
|
|
11670
11597
|
workspaceId: workspaceId || "",
|
|
@@ -12144,6 +12071,140 @@ function useWorkspaceNavigation() {
|
|
|
12144
12071
|
getWorkspaceNavigationParams: getWorkspaceNavigationParams2
|
|
12145
12072
|
};
|
|
12146
12073
|
}
|
|
12074
|
+
function useWorkspaceHealth(options = {}) {
|
|
12075
|
+
const [workspaces, setWorkspaces] = React19.useState([]);
|
|
12076
|
+
const [summary, setSummary] = React19.useState(null);
|
|
12077
|
+
const [loading, setLoading] = React19.useState(true);
|
|
12078
|
+
const [error, setError] = React19.useState(null);
|
|
12079
|
+
const unsubscribeRef = React19.useRef(null);
|
|
12080
|
+
const refreshIntervalRef = React19.useRef(null);
|
|
12081
|
+
const {
|
|
12082
|
+
enableRealtime = true,
|
|
12083
|
+
refreshInterval = 3e4,
|
|
12084
|
+
// 30 seconds default
|
|
12085
|
+
...filterOptions
|
|
12086
|
+
} = options;
|
|
12087
|
+
const fetchData = React19.useCallback(async () => {
|
|
12088
|
+
try {
|
|
12089
|
+
setError(null);
|
|
12090
|
+
workspaceHealthService.clearCache();
|
|
12091
|
+
const [workspacesData, summaryData] = await Promise.all([
|
|
12092
|
+
workspaceHealthService.getWorkspaceHealthStatus(filterOptions),
|
|
12093
|
+
workspaceHealthService.getHealthSummary(filterOptions.lineId, filterOptions.companyId)
|
|
12094
|
+
]);
|
|
12095
|
+
setWorkspaces(workspacesData);
|
|
12096
|
+
setSummary(summaryData);
|
|
12097
|
+
} catch (err) {
|
|
12098
|
+
console.error("Error fetching workspace health:", err);
|
|
12099
|
+
setError(err);
|
|
12100
|
+
} finally {
|
|
12101
|
+
setLoading(false);
|
|
12102
|
+
}
|
|
12103
|
+
}, [filterOptions.lineId, filterOptions.companyId, filterOptions.status, filterOptions.searchTerm, filterOptions.sortBy, filterOptions.sortOrder]);
|
|
12104
|
+
const handleRealtimeUpdate = React19.useCallback(async (data) => {
|
|
12105
|
+
try {
|
|
12106
|
+
const [workspacesData, summaryData] = await Promise.all([
|
|
12107
|
+
workspaceHealthService.getWorkspaceHealthStatus(filterOptions),
|
|
12108
|
+
workspaceHealthService.getHealthSummary(filterOptions.lineId, filterOptions.companyId)
|
|
12109
|
+
]);
|
|
12110
|
+
setWorkspaces(workspacesData);
|
|
12111
|
+
setSummary(summaryData);
|
|
12112
|
+
} catch (err) {
|
|
12113
|
+
console.error("Error updating real-time health data:", err);
|
|
12114
|
+
}
|
|
12115
|
+
}, [filterOptions]);
|
|
12116
|
+
React19.useEffect(() => {
|
|
12117
|
+
fetchData();
|
|
12118
|
+
if (refreshInterval > 0) {
|
|
12119
|
+
refreshIntervalRef.current = setInterval(fetchData, 1e4);
|
|
12120
|
+
}
|
|
12121
|
+
if (enableRealtime) {
|
|
12122
|
+
unsubscribeRef.current = workspaceHealthService.subscribeToHealthUpdates(
|
|
12123
|
+
handleRealtimeUpdate,
|
|
12124
|
+
{ lineId: filterOptions.lineId, companyId: filterOptions.companyId }
|
|
12125
|
+
);
|
|
12126
|
+
}
|
|
12127
|
+
return () => {
|
|
12128
|
+
if (refreshIntervalRef.current) {
|
|
12129
|
+
clearInterval(refreshIntervalRef.current);
|
|
12130
|
+
}
|
|
12131
|
+
if (unsubscribeRef.current) {
|
|
12132
|
+
unsubscribeRef.current();
|
|
12133
|
+
}
|
|
12134
|
+
};
|
|
12135
|
+
}, [fetchData, enableRealtime, refreshInterval, handleRealtimeUpdate, filterOptions.lineId, filterOptions.companyId]);
|
|
12136
|
+
return {
|
|
12137
|
+
workspaces,
|
|
12138
|
+
summary,
|
|
12139
|
+
loading,
|
|
12140
|
+
error,
|
|
12141
|
+
refetch: fetchData
|
|
12142
|
+
};
|
|
12143
|
+
}
|
|
12144
|
+
function useWorkspaceHealthById(workspaceId, options = {}) {
|
|
12145
|
+
const [workspace, setWorkspace] = React19.useState(null);
|
|
12146
|
+
const [metrics2, setMetrics] = React19.useState(null);
|
|
12147
|
+
const [loading, setLoading] = React19.useState(true);
|
|
12148
|
+
const [error, setError] = React19.useState(null);
|
|
12149
|
+
const unsubscribeRef = React19.useRef(null);
|
|
12150
|
+
const refreshIntervalRef = React19.useRef(null);
|
|
12151
|
+
const { enableRealtime = true, refreshInterval = 3e4 } = options;
|
|
12152
|
+
const fetchData = React19.useCallback(async () => {
|
|
12153
|
+
if (!workspaceId) {
|
|
12154
|
+
setWorkspace(null);
|
|
12155
|
+
setMetrics(null);
|
|
12156
|
+
setLoading(false);
|
|
12157
|
+
return;
|
|
12158
|
+
}
|
|
12159
|
+
try {
|
|
12160
|
+
setLoading(true);
|
|
12161
|
+
setError(null);
|
|
12162
|
+
const [workspaceData, metricsData] = await Promise.all([
|
|
12163
|
+
workspaceHealthService.getWorkspaceHealthById(workspaceId),
|
|
12164
|
+
workspaceHealthService.getHealthMetrics(workspaceId)
|
|
12165
|
+
]);
|
|
12166
|
+
setWorkspace(workspaceData);
|
|
12167
|
+
setMetrics(metricsData);
|
|
12168
|
+
} catch (err) {
|
|
12169
|
+
console.error("Error fetching workspace health by ID:", err);
|
|
12170
|
+
setError(err);
|
|
12171
|
+
} finally {
|
|
12172
|
+
setLoading(false);
|
|
12173
|
+
}
|
|
12174
|
+
}, [workspaceId]);
|
|
12175
|
+
const handleRealtimeUpdate = React19.useCallback((data) => {
|
|
12176
|
+
if (data.workspace_id === workspaceId) {
|
|
12177
|
+
const updatedWorkspace = workspaceHealthService["processHealthStatus"](data);
|
|
12178
|
+
setWorkspace(updatedWorkspace);
|
|
12179
|
+
}
|
|
12180
|
+
}, [workspaceId]);
|
|
12181
|
+
React19.useEffect(() => {
|
|
12182
|
+
fetchData();
|
|
12183
|
+
if (refreshInterval > 0) {
|
|
12184
|
+
refreshIntervalRef.current = setInterval(fetchData, 1e4);
|
|
12185
|
+
}
|
|
12186
|
+
if (enableRealtime && workspaceId) {
|
|
12187
|
+
unsubscribeRef.current = workspaceHealthService.subscribeToHealthUpdates(
|
|
12188
|
+
handleRealtimeUpdate
|
|
12189
|
+
);
|
|
12190
|
+
}
|
|
12191
|
+
return () => {
|
|
12192
|
+
if (refreshIntervalRef.current) {
|
|
12193
|
+
clearInterval(refreshIntervalRef.current);
|
|
12194
|
+
}
|
|
12195
|
+
if (unsubscribeRef.current) {
|
|
12196
|
+
unsubscribeRef.current();
|
|
12197
|
+
}
|
|
12198
|
+
};
|
|
12199
|
+
}, [fetchData, enableRealtime, refreshInterval, handleRealtimeUpdate, workspaceId]);
|
|
12200
|
+
return {
|
|
12201
|
+
workspace,
|
|
12202
|
+
metrics: metrics2,
|
|
12203
|
+
loading,
|
|
12204
|
+
error,
|
|
12205
|
+
refetch: fetchData
|
|
12206
|
+
};
|
|
12207
|
+
}
|
|
12147
12208
|
function useDateFormatter() {
|
|
12148
12209
|
const { defaultTimezone, defaultLocale, dateFormatOptions, timeFormatOptions, dateTimeFormatOptions } = useDateTimeConfig();
|
|
12149
12210
|
const formatDate = React19.useCallback(
|
|
@@ -23552,6 +23613,165 @@ var TicketHistory = ({ companyId }) => {
|
|
|
23552
23613
|
] });
|
|
23553
23614
|
};
|
|
23554
23615
|
var TicketHistory_default = TicketHistory;
|
|
23616
|
+
var HealthStatusIndicator = ({
|
|
23617
|
+
status,
|
|
23618
|
+
lastUpdated,
|
|
23619
|
+
showLabel = false,
|
|
23620
|
+
showTime = true,
|
|
23621
|
+
size = "md",
|
|
23622
|
+
className = "",
|
|
23623
|
+
inline = true,
|
|
23624
|
+
pulse = true
|
|
23625
|
+
}) => {
|
|
23626
|
+
const getStatusConfig = () => {
|
|
23627
|
+
switch (status) {
|
|
23628
|
+
case "healthy":
|
|
23629
|
+
return {
|
|
23630
|
+
color: "text-green-500",
|
|
23631
|
+
bgColor: "bg-green-500",
|
|
23632
|
+
borderColor: "border-green-500",
|
|
23633
|
+
label: "Healthy",
|
|
23634
|
+
icon: lucideReact.CheckCircle,
|
|
23635
|
+
shouldPulse: pulse
|
|
23636
|
+
};
|
|
23637
|
+
case "unhealthy":
|
|
23638
|
+
return {
|
|
23639
|
+
color: "text-red-500",
|
|
23640
|
+
bgColor: "bg-red-500",
|
|
23641
|
+
borderColor: "border-red-500",
|
|
23642
|
+
label: "Unhealthy",
|
|
23643
|
+
icon: lucideReact.XCircle,
|
|
23644
|
+
shouldPulse: false
|
|
23645
|
+
};
|
|
23646
|
+
case "warning":
|
|
23647
|
+
return {
|
|
23648
|
+
color: "text-yellow-500",
|
|
23649
|
+
bgColor: "bg-yellow-500",
|
|
23650
|
+
borderColor: "border-yellow-500",
|
|
23651
|
+
label: "Warning",
|
|
23652
|
+
icon: lucideReact.AlertTriangle,
|
|
23653
|
+
shouldPulse: true
|
|
23654
|
+
};
|
|
23655
|
+
case "unknown":
|
|
23656
|
+
default:
|
|
23657
|
+
return {
|
|
23658
|
+
color: "text-gray-400",
|
|
23659
|
+
bgColor: "bg-gray-400",
|
|
23660
|
+
borderColor: "border-gray-400",
|
|
23661
|
+
label: "Unknown",
|
|
23662
|
+
icon: lucideReact.Activity,
|
|
23663
|
+
shouldPulse: false
|
|
23664
|
+
};
|
|
23665
|
+
}
|
|
23666
|
+
};
|
|
23667
|
+
const config = getStatusConfig();
|
|
23668
|
+
config.icon;
|
|
23669
|
+
const sizeClasses = {
|
|
23670
|
+
sm: {
|
|
23671
|
+
dot: "h-2 w-2",
|
|
23672
|
+
icon: "h-3 w-3",
|
|
23673
|
+
text: "text-xs",
|
|
23674
|
+
spacing: "gap-1"
|
|
23675
|
+
},
|
|
23676
|
+
md: {
|
|
23677
|
+
dot: "h-3 w-3",
|
|
23678
|
+
icon: "h-4 w-4",
|
|
23679
|
+
text: "text-sm",
|
|
23680
|
+
spacing: "gap-1.5"
|
|
23681
|
+
},
|
|
23682
|
+
lg: {
|
|
23683
|
+
dot: "h-4 w-4",
|
|
23684
|
+
icon: "h-5 w-5",
|
|
23685
|
+
text: "text-base",
|
|
23686
|
+
spacing: "gap-2"
|
|
23687
|
+
}
|
|
23688
|
+
};
|
|
23689
|
+
const currentSize = sizeClasses[size];
|
|
23690
|
+
const containerClasses = clsx(
|
|
23691
|
+
"flex items-center",
|
|
23692
|
+
currentSize.spacing,
|
|
23693
|
+
inline ? "inline-flex" : "flex",
|
|
23694
|
+
className
|
|
23695
|
+
);
|
|
23696
|
+
const dotClasses = clsx(
|
|
23697
|
+
"rounded-full",
|
|
23698
|
+
currentSize.dot,
|
|
23699
|
+
config.bgColor,
|
|
23700
|
+
config.shouldPulse && "animate-pulse"
|
|
23701
|
+
);
|
|
23702
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: containerClasses, children: [
|
|
23703
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center justify-center", children: [
|
|
23704
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: dotClasses }),
|
|
23705
|
+
config.shouldPulse && status === "healthy" && /* @__PURE__ */ jsxRuntime.jsx(
|
|
23706
|
+
"div",
|
|
23707
|
+
{
|
|
23708
|
+
className: clsx(
|
|
23709
|
+
"absolute rounded-full opacity-25",
|
|
23710
|
+
currentSize.dot,
|
|
23711
|
+
config.bgColor,
|
|
23712
|
+
"animate-ping"
|
|
23713
|
+
)
|
|
23714
|
+
}
|
|
23715
|
+
)
|
|
23716
|
+
] }),
|
|
23717
|
+
showLabel && /* @__PURE__ */ jsxRuntime.jsx("span", { className: clsx(currentSize.text, "font-medium", config.color), children: config.label }),
|
|
23718
|
+
showTime && lastUpdated && /* @__PURE__ */ jsxRuntime.jsx("span", { className: clsx(currentSize.text, "text-gray-500 dark:text-gray-400"), children: lastUpdated })
|
|
23719
|
+
] });
|
|
23720
|
+
};
|
|
23721
|
+
var DetailedHealthStatus = ({
|
|
23722
|
+
workspaceName,
|
|
23723
|
+
lineName,
|
|
23724
|
+
consecutiveMisses,
|
|
23725
|
+
showDetails = true,
|
|
23726
|
+
...indicatorProps
|
|
23727
|
+
}) => {
|
|
23728
|
+
const getStatusConfig = () => {
|
|
23729
|
+
switch (indicatorProps.status) {
|
|
23730
|
+
case "healthy":
|
|
23731
|
+
return {
|
|
23732
|
+
bgClass: "bg-green-50 dark:bg-green-900/20",
|
|
23733
|
+
borderClass: "border-green-200 dark:border-green-800"
|
|
23734
|
+
};
|
|
23735
|
+
case "unhealthy":
|
|
23736
|
+
return {
|
|
23737
|
+
bgClass: "bg-red-50 dark:bg-red-900/20",
|
|
23738
|
+
borderClass: "border-red-200 dark:border-red-800"
|
|
23739
|
+
};
|
|
23740
|
+
case "warning":
|
|
23741
|
+
return {
|
|
23742
|
+
bgClass: "bg-yellow-50 dark:bg-yellow-900/20",
|
|
23743
|
+
borderClass: "border-yellow-200 dark:border-yellow-800"
|
|
23744
|
+
};
|
|
23745
|
+
default:
|
|
23746
|
+
return {
|
|
23747
|
+
bgClass: "bg-gray-50 dark:bg-gray-900/20",
|
|
23748
|
+
borderClass: "border-gray-200 dark:border-gray-800"
|
|
23749
|
+
};
|
|
23750
|
+
}
|
|
23751
|
+
};
|
|
23752
|
+
const config = getStatusConfig();
|
|
23753
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
23754
|
+
"div",
|
|
23755
|
+
{
|
|
23756
|
+
className: clsx(
|
|
23757
|
+
"rounded-lg border p-3",
|
|
23758
|
+
config.bgClass,
|
|
23759
|
+
config.borderClass
|
|
23760
|
+
),
|
|
23761
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between", children: [
|
|
23762
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
|
|
23763
|
+
showDetails && workspaceName && /* @__PURE__ */ jsxRuntime.jsx("h4", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: workspaceName }),
|
|
23764
|
+
showDetails && lineName && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-0.5", children: lineName }),
|
|
23765
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsxRuntime.jsx(HealthStatusIndicator, { ...indicatorProps, showLabel: true }) })
|
|
23766
|
+
] }),
|
|
23767
|
+
showDetails && consecutiveMisses !== void 0 && consecutiveMisses > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ml-3 text-right", children: [
|
|
23768
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-medium text-gray-500 dark:text-gray-400", children: "Missed" }),
|
|
23769
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-bold text-gray-900 dark:text-gray-100", children: consecutiveMisses })
|
|
23770
|
+
] })
|
|
23771
|
+
] })
|
|
23772
|
+
}
|
|
23773
|
+
);
|
|
23774
|
+
};
|
|
23555
23775
|
var LinePdfExportButton = ({
|
|
23556
23776
|
targetElement,
|
|
23557
23777
|
fileName = "line-export",
|
|
@@ -24674,6 +24894,8 @@ var WorkspaceCard = ({
|
|
|
24674
24894
|
cycleTime,
|
|
24675
24895
|
operators,
|
|
24676
24896
|
status = "normal",
|
|
24897
|
+
healthStatus,
|
|
24898
|
+
healthLastUpdated,
|
|
24677
24899
|
onCardClick,
|
|
24678
24900
|
headerActions,
|
|
24679
24901
|
footerContent,
|
|
@@ -24766,6 +24988,19 @@ var WorkspaceCard = ({
|
|
|
24766
24988
|
] })
|
|
24767
24989
|
] })
|
|
24768
24990
|
] }),
|
|
24991
|
+
healthStatus && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pt-2 mt-auto border-t dark:border-gray-700", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
24992
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: "System Health" }),
|
|
24993
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
24994
|
+
HealthStatusIndicator,
|
|
24995
|
+
{
|
|
24996
|
+
status: healthStatus,
|
|
24997
|
+
lastUpdated: healthLastUpdated,
|
|
24998
|
+
showTime: false,
|
|
24999
|
+
size: "sm",
|
|
25000
|
+
pulse: healthStatus === "healthy"
|
|
25001
|
+
}
|
|
25002
|
+
)
|
|
25003
|
+
] }) }),
|
|
24769
25004
|
chartData && chartData.data && chartData.data.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
24770
25005
|
"div",
|
|
24771
25006
|
{
|
|
@@ -26424,6 +26659,194 @@ var BackButtonMinimal = ({
|
|
|
26424
26659
|
}
|
|
26425
26660
|
);
|
|
26426
26661
|
};
|
|
26662
|
+
var InlineEditableText = ({
|
|
26663
|
+
value,
|
|
26664
|
+
onSave,
|
|
26665
|
+
placeholder = "Click to edit",
|
|
26666
|
+
className = "",
|
|
26667
|
+
editIconClassName = "",
|
|
26668
|
+
inputClassName = "",
|
|
26669
|
+
debounceDelay = 750,
|
|
26670
|
+
// 750ms for quick auto-save
|
|
26671
|
+
disabled = false
|
|
26672
|
+
}) => {
|
|
26673
|
+
const [isEditing, setIsEditing] = React19.useState(false);
|
|
26674
|
+
const [editValue, setEditValue] = React19.useState(value);
|
|
26675
|
+
const [saveStatus, setSaveStatus] = React19.useState("idle");
|
|
26676
|
+
const [lastSavedValue, setLastSavedValue] = React19.useState(value);
|
|
26677
|
+
const [hasUnsavedChanges, setHasUnsavedChanges] = React19.useState(false);
|
|
26678
|
+
const inputRef = React19.useRef(null);
|
|
26679
|
+
const containerRef = React19.useRef(null);
|
|
26680
|
+
const debounceTimeout = React19.useRef(void 0);
|
|
26681
|
+
const saveStatusTimeout = React19.useRef(void 0);
|
|
26682
|
+
React19.useEffect(() => {
|
|
26683
|
+
if (!isEditing) {
|
|
26684
|
+
setEditValue(value);
|
|
26685
|
+
setLastSavedValue(value);
|
|
26686
|
+
}
|
|
26687
|
+
}, [value, isEditing]);
|
|
26688
|
+
React19.useEffect(() => {
|
|
26689
|
+
if (isEditing && inputRef.current) {
|
|
26690
|
+
requestAnimationFrame(() => {
|
|
26691
|
+
inputRef.current?.focus();
|
|
26692
|
+
inputRef.current?.select();
|
|
26693
|
+
});
|
|
26694
|
+
}
|
|
26695
|
+
}, [isEditing]);
|
|
26696
|
+
React19.useEffect(() => {
|
|
26697
|
+
return () => {
|
|
26698
|
+
if (debounceTimeout.current) clearTimeout(debounceTimeout.current);
|
|
26699
|
+
if (saveStatusTimeout.current) clearTimeout(saveStatusTimeout.current);
|
|
26700
|
+
};
|
|
26701
|
+
}, []);
|
|
26702
|
+
const performSave = React19.useCallback(async (valueToSave, shouldClose = false) => {
|
|
26703
|
+
const trimmedValue = valueToSave.trim();
|
|
26704
|
+
if (trimmedValue === lastSavedValue.trim()) {
|
|
26705
|
+
setHasUnsavedChanges(false);
|
|
26706
|
+
if (shouldClose) {
|
|
26707
|
+
setIsEditing(false);
|
|
26708
|
+
setSaveStatus("idle");
|
|
26709
|
+
}
|
|
26710
|
+
return;
|
|
26711
|
+
}
|
|
26712
|
+
setSaveStatus("saving");
|
|
26713
|
+
setHasUnsavedChanges(false);
|
|
26714
|
+
try {
|
|
26715
|
+
await onSave(trimmedValue);
|
|
26716
|
+
setLastSavedValue(trimmedValue);
|
|
26717
|
+
setSaveStatus("saved");
|
|
26718
|
+
if (!shouldClose && inputRef.current) {
|
|
26719
|
+
requestAnimationFrame(() => {
|
|
26720
|
+
inputRef.current?.focus();
|
|
26721
|
+
});
|
|
26722
|
+
}
|
|
26723
|
+
if (saveStatusTimeout.current) clearTimeout(saveStatusTimeout.current);
|
|
26724
|
+
saveStatusTimeout.current = setTimeout(() => {
|
|
26725
|
+
setSaveStatus("idle");
|
|
26726
|
+
}, 2e3);
|
|
26727
|
+
if (shouldClose) {
|
|
26728
|
+
setIsEditing(false);
|
|
26729
|
+
}
|
|
26730
|
+
} catch (error) {
|
|
26731
|
+
console.error("Failed to save:", error);
|
|
26732
|
+
setSaveStatus("error");
|
|
26733
|
+
setHasUnsavedChanges(true);
|
|
26734
|
+
if (saveStatusTimeout.current) clearTimeout(saveStatusTimeout.current);
|
|
26735
|
+
saveStatusTimeout.current = setTimeout(() => {
|
|
26736
|
+
setSaveStatus("idle");
|
|
26737
|
+
}, 3e3);
|
|
26738
|
+
if (!shouldClose && inputRef.current) {
|
|
26739
|
+
requestAnimationFrame(() => {
|
|
26740
|
+
inputRef.current?.focus();
|
|
26741
|
+
});
|
|
26742
|
+
}
|
|
26743
|
+
}
|
|
26744
|
+
}, [lastSavedValue, onSave]);
|
|
26745
|
+
React19.useEffect(() => {
|
|
26746
|
+
const handleClickOutside = (event) => {
|
|
26747
|
+
if (isEditing && containerRef.current && !containerRef.current.contains(event.target)) {
|
|
26748
|
+
if (debounceTimeout.current) {
|
|
26749
|
+
clearTimeout(debounceTimeout.current);
|
|
26750
|
+
}
|
|
26751
|
+
performSave(editValue, true);
|
|
26752
|
+
}
|
|
26753
|
+
};
|
|
26754
|
+
if (isEditing) {
|
|
26755
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
26756
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
26757
|
+
}
|
|
26758
|
+
}, [isEditing, editValue, performSave]);
|
|
26759
|
+
const handleInputChange = (e) => {
|
|
26760
|
+
const newValue = e.target.value;
|
|
26761
|
+
setEditValue(newValue);
|
|
26762
|
+
if (newValue.trim() !== lastSavedValue.trim()) {
|
|
26763
|
+
setHasUnsavedChanges(true);
|
|
26764
|
+
setSaveStatus("idle");
|
|
26765
|
+
} else {
|
|
26766
|
+
setHasUnsavedChanges(false);
|
|
26767
|
+
}
|
|
26768
|
+
if (debounceTimeout.current) {
|
|
26769
|
+
clearTimeout(debounceTimeout.current);
|
|
26770
|
+
}
|
|
26771
|
+
if (newValue.trim() !== lastSavedValue.trim()) {
|
|
26772
|
+
debounceTimeout.current = setTimeout(() => {
|
|
26773
|
+
performSave(newValue, false);
|
|
26774
|
+
}, debounceDelay);
|
|
26775
|
+
}
|
|
26776
|
+
};
|
|
26777
|
+
const handleKeyDown = (e) => {
|
|
26778
|
+
if (e.key === "Enter") {
|
|
26779
|
+
e.preventDefault();
|
|
26780
|
+
if (debounceTimeout.current) {
|
|
26781
|
+
clearTimeout(debounceTimeout.current);
|
|
26782
|
+
}
|
|
26783
|
+
performSave(editValue, true);
|
|
26784
|
+
} else if (e.key === "Escape") {
|
|
26785
|
+
e.preventDefault();
|
|
26786
|
+
if (debounceTimeout.current) {
|
|
26787
|
+
clearTimeout(debounceTimeout.current);
|
|
26788
|
+
}
|
|
26789
|
+
setEditValue(lastSavedValue);
|
|
26790
|
+
setHasUnsavedChanges(false);
|
|
26791
|
+
setSaveStatus("idle");
|
|
26792
|
+
setIsEditing(false);
|
|
26793
|
+
}
|
|
26794
|
+
};
|
|
26795
|
+
const handleClick = () => {
|
|
26796
|
+
if (!disabled && !isEditing) {
|
|
26797
|
+
setIsEditing(true);
|
|
26798
|
+
setSaveStatus("idle");
|
|
26799
|
+
}
|
|
26800
|
+
};
|
|
26801
|
+
if (isEditing) {
|
|
26802
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: containerRef, className: "inline-flex items-center gap-1.5", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
26803
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
26804
|
+
"input",
|
|
26805
|
+
{
|
|
26806
|
+
ref: inputRef,
|
|
26807
|
+
type: "text",
|
|
26808
|
+
value: editValue,
|
|
26809
|
+
onChange: handleInputChange,
|
|
26810
|
+
onKeyDown: handleKeyDown,
|
|
26811
|
+
className: `px-2 py-1 pr-7 text-sm border rounded-md transition-colors duration-200
|
|
26812
|
+
${saveStatus === "error" ? "border-red-400 focus:ring-red-500 focus:border-red-500" : hasUnsavedChanges ? "border-yellow-400 focus:ring-yellow-500 focus:border-yellow-500" : saveStatus === "saved" ? "border-green-400 focus:ring-green-500 focus:border-green-500" : "border-blue-400 focus:ring-blue-500 focus:border-blue-500"}
|
|
26813
|
+
focus:outline-none focus:ring-2 bg-white
|
|
26814
|
+
${inputClassName}`,
|
|
26815
|
+
placeholder,
|
|
26816
|
+
autoComplete: "off"
|
|
26817
|
+
}
|
|
26818
|
+
),
|
|
26819
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute right-1.5 top-1/2 -translate-y-1/2", children: [
|
|
26820
|
+
saveStatus === "saving" && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "w-3.5 h-3.5 text-blue-500 animate-spin" }),
|
|
26821
|
+
saveStatus === "saved" && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "w-3.5 h-3.5 text-green-500" }),
|
|
26822
|
+
saveStatus === "error" && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircle, { className: "w-3.5 h-3.5 text-red-500" }),
|
|
26823
|
+
saveStatus === "idle" && hasUnsavedChanges && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-1.5 h-1.5 bg-yellow-400 rounded-full" })
|
|
26824
|
+
] })
|
|
26825
|
+
] }) });
|
|
26826
|
+
}
|
|
26827
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
26828
|
+
"div",
|
|
26829
|
+
{
|
|
26830
|
+
onClick: handleClick,
|
|
26831
|
+
className: `inline-flex items-center gap-1.5 cursor-pointer px-2 py-1 rounded-md
|
|
26832
|
+
transition-all duration-200 hover:bg-gray-50 group
|
|
26833
|
+
${disabled ? "cursor-not-allowed opacity-50" : ""}
|
|
26834
|
+
${className}`,
|
|
26835
|
+
children: [
|
|
26836
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium text-gray-900", children: editValue || placeholder }),
|
|
26837
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
26838
|
+
lucideReact.Edit2,
|
|
26839
|
+
{
|
|
26840
|
+
className: `w-3.5 h-3.5 text-gray-400 transition-opacity duration-200
|
|
26841
|
+
opacity-0 group-hover:opacity-100
|
|
26842
|
+
${disabled ? "hidden" : ""}
|
|
26843
|
+
${editIconClassName}`
|
|
26844
|
+
}
|
|
26845
|
+
)
|
|
26846
|
+
]
|
|
26847
|
+
}
|
|
26848
|
+
);
|
|
26849
|
+
};
|
|
26427
26850
|
var BottlenecksContent = ({
|
|
26428
26851
|
workspaceId,
|
|
26429
26852
|
workspaceName,
|
|
@@ -26505,11 +26928,14 @@ var BottlenecksContent = ({
|
|
|
26505
26928
|
console.log(`[BottlenecksContent] No shift provided for historical date ${date}, defaulting to day shift (0)`);
|
|
26506
26929
|
return "0";
|
|
26507
26930
|
} else {
|
|
26508
|
-
const currentShift =
|
|
26509
|
-
|
|
26931
|
+
const currentShift = getCurrentShift(
|
|
26932
|
+
dashboardConfig.dateTimeConfig?.defaultTimezone || "Asia/Kolkata",
|
|
26933
|
+
dashboardConfig.shiftConfig
|
|
26934
|
+
);
|
|
26935
|
+
console.log(`[BottlenecksContent] Using current operational shift: ${currentShift.shiftId}`);
|
|
26510
26936
|
return currentShift.shiftId.toString();
|
|
26511
26937
|
}
|
|
26512
|
-
}, [shift, date]);
|
|
26938
|
+
}, [shift, date, dashboardConfig]);
|
|
26513
26939
|
const {
|
|
26514
26940
|
data: prefetchData,
|
|
26515
26941
|
isFullyIndexed,
|
|
@@ -28169,6 +28595,388 @@ var KPISection = React19.memo(({
|
|
|
28169
28595
|
return true;
|
|
28170
28596
|
});
|
|
28171
28597
|
KPISection.displayName = "KPISection";
|
|
28598
|
+
var WorkspaceHealthCard = ({
|
|
28599
|
+
workspace,
|
|
28600
|
+
onClick,
|
|
28601
|
+
showDetails = true,
|
|
28602
|
+
className = ""
|
|
28603
|
+
}) => {
|
|
28604
|
+
const getStatusConfig = () => {
|
|
28605
|
+
switch (workspace.status) {
|
|
28606
|
+
case "healthy":
|
|
28607
|
+
return {
|
|
28608
|
+
gradient: "from-emerald-50 to-green-50 dark:from-emerald-950/30 dark:to-green-950/30",
|
|
28609
|
+
border: "border-emerald-200 dark:border-emerald-800",
|
|
28610
|
+
icon: lucideReact.CheckCircle2,
|
|
28611
|
+
iconColor: "text-emerald-600 dark:text-emerald-400",
|
|
28612
|
+
badge: "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/50 dark:text-emerald-300",
|
|
28613
|
+
statusText: "Online",
|
|
28614
|
+
pulse: true
|
|
28615
|
+
};
|
|
28616
|
+
case "unhealthy":
|
|
28617
|
+
return {
|
|
28618
|
+
gradient: "from-rose-50 to-red-50 dark:from-rose-950/30 dark:to-red-950/30",
|
|
28619
|
+
border: "border-rose-200 dark:border-rose-800",
|
|
28620
|
+
icon: lucideReact.XCircle,
|
|
28621
|
+
iconColor: "text-rose-600 dark:text-rose-400",
|
|
28622
|
+
badge: "bg-rose-100 text-rose-700 dark:bg-rose-900/50 dark:text-rose-300",
|
|
28623
|
+
statusText: "Offline",
|
|
28624
|
+
pulse: false
|
|
28625
|
+
};
|
|
28626
|
+
case "warning":
|
|
28627
|
+
return {
|
|
28628
|
+
gradient: "from-amber-50 to-yellow-50 dark:from-amber-950/30 dark:to-yellow-950/30",
|
|
28629
|
+
border: "border-amber-200 dark:border-amber-800",
|
|
28630
|
+
icon: lucideReact.AlertTriangle,
|
|
28631
|
+
iconColor: "text-amber-600 dark:text-amber-400",
|
|
28632
|
+
badge: "bg-amber-100 text-amber-700 dark:bg-amber-900/50 dark:text-amber-300",
|
|
28633
|
+
statusText: "Degraded",
|
|
28634
|
+
pulse: true
|
|
28635
|
+
};
|
|
28636
|
+
default:
|
|
28637
|
+
return {
|
|
28638
|
+
gradient: "from-gray-50 to-slate-50 dark:from-gray-950/30 dark:to-slate-950/30",
|
|
28639
|
+
border: "border-gray-200 dark:border-gray-800",
|
|
28640
|
+
icon: lucideReact.Activity,
|
|
28641
|
+
iconColor: "text-gray-500 dark:text-gray-400",
|
|
28642
|
+
badge: "bg-gray-100 text-gray-700 dark:bg-gray-900/50 dark:text-gray-300",
|
|
28643
|
+
statusText: "Unknown",
|
|
28644
|
+
pulse: false
|
|
28645
|
+
};
|
|
28646
|
+
}
|
|
28647
|
+
};
|
|
28648
|
+
const config = getStatusConfig();
|
|
28649
|
+
const StatusIcon = config.icon;
|
|
28650
|
+
const handleClick = () => {
|
|
28651
|
+
if (onClick) {
|
|
28652
|
+
onClick(workspace);
|
|
28653
|
+
}
|
|
28654
|
+
};
|
|
28655
|
+
const handleKeyDown = (event) => {
|
|
28656
|
+
if (onClick && (event.key === "Enter" || event.key === " ")) {
|
|
28657
|
+
event.preventDefault();
|
|
28658
|
+
onClick(workspace);
|
|
28659
|
+
}
|
|
28660
|
+
};
|
|
28661
|
+
const formatTimeAgo = (timeString) => {
|
|
28662
|
+
return timeString.replace("about ", "").replace(" ago", "");
|
|
28663
|
+
};
|
|
28664
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
28665
|
+
Card2,
|
|
28666
|
+
{
|
|
28667
|
+
className: clsx(
|
|
28668
|
+
"relative overflow-hidden transition-all duration-300",
|
|
28669
|
+
"bg-gradient-to-br",
|
|
28670
|
+
config.gradient,
|
|
28671
|
+
"border",
|
|
28672
|
+
config.border,
|
|
28673
|
+
"shadow-sm hover:shadow-md",
|
|
28674
|
+
onClick && "cursor-pointer hover:scale-[1.01]",
|
|
28675
|
+
workspace.isStale && "opacity-90",
|
|
28676
|
+
className
|
|
28677
|
+
),
|
|
28678
|
+
onClick: handleClick,
|
|
28679
|
+
onKeyDown: handleKeyDown,
|
|
28680
|
+
tabIndex: onClick ? 0 : void 0,
|
|
28681
|
+
role: onClick ? "button" : void 0,
|
|
28682
|
+
"aria-label": `Workspace ${workspace.workspace_display_name} status: ${workspace.status}`,
|
|
28683
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4", children: [
|
|
28684
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between mb-3", children: [
|
|
28685
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1", children: [
|
|
28686
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100 mb-1", children: workspace.workspace_display_name || `Workspace ${workspace.workspace_id.slice(0, 8)}` }),
|
|
28687
|
+
showDetails && workspace.line_name && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400", children: workspace.line_name })
|
|
28688
|
+
] }),
|
|
28689
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: clsx(
|
|
28690
|
+
"flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium",
|
|
28691
|
+
config.badge
|
|
28692
|
+
), children: [
|
|
28693
|
+
/* @__PURE__ */ jsxRuntime.jsx(StatusIcon, { className: "h-3.5 w-3.5" }),
|
|
28694
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: config.statusText })
|
|
28695
|
+
] })
|
|
28696
|
+
] }),
|
|
28697
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-1", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
28698
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-3.5 w-3.5 text-gray-400" }),
|
|
28699
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-gray-600 dark:text-gray-400 whitespace-nowrap", children: [
|
|
28700
|
+
"Last seen: ",
|
|
28701
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium", children: formatTimeAgo(workspace.timeSinceLastUpdate) })
|
|
28702
|
+
] })
|
|
28703
|
+
] }) }) })
|
|
28704
|
+
] })
|
|
28705
|
+
}
|
|
28706
|
+
);
|
|
28707
|
+
};
|
|
28708
|
+
var CompactWorkspaceHealthCard = ({
|
|
28709
|
+
workspace,
|
|
28710
|
+
onClick,
|
|
28711
|
+
className = ""
|
|
28712
|
+
}) => {
|
|
28713
|
+
const getStatusConfig = () => {
|
|
28714
|
+
switch (workspace.status) {
|
|
28715
|
+
case "healthy":
|
|
28716
|
+
return {
|
|
28717
|
+
dot: "bg-emerald-500",
|
|
28718
|
+
icon: lucideReact.CheckCircle2,
|
|
28719
|
+
iconColor: "text-emerald-600 dark:text-emerald-400",
|
|
28720
|
+
bg: "hover:bg-emerald-50 dark:hover:bg-emerald-950/20"
|
|
28721
|
+
};
|
|
28722
|
+
case "unhealthy":
|
|
28723
|
+
return {
|
|
28724
|
+
dot: "bg-rose-500",
|
|
28725
|
+
icon: lucideReact.XCircle,
|
|
28726
|
+
iconColor: "text-rose-600 dark:text-rose-400",
|
|
28727
|
+
bg: "hover:bg-rose-50 dark:hover:bg-rose-950/20"
|
|
28728
|
+
};
|
|
28729
|
+
case "warning":
|
|
28730
|
+
return {
|
|
28731
|
+
dot: "bg-amber-500",
|
|
28732
|
+
icon: lucideReact.AlertTriangle,
|
|
28733
|
+
iconColor: "text-amber-600 dark:text-amber-400",
|
|
28734
|
+
bg: "hover:bg-amber-50 dark:hover:bg-amber-950/20"
|
|
28735
|
+
};
|
|
28736
|
+
default:
|
|
28737
|
+
return {
|
|
28738
|
+
dot: "bg-gray-400",
|
|
28739
|
+
icon: lucideReact.Activity,
|
|
28740
|
+
iconColor: "text-gray-500 dark:text-gray-400",
|
|
28741
|
+
bg: "hover:bg-gray-50 dark:hover:bg-gray-950/20"
|
|
28742
|
+
};
|
|
28743
|
+
}
|
|
28744
|
+
};
|
|
28745
|
+
const config = getStatusConfig();
|
|
28746
|
+
const StatusIcon = config.icon;
|
|
28747
|
+
const handleClick = () => {
|
|
28748
|
+
if (onClick) {
|
|
28749
|
+
onClick(workspace);
|
|
28750
|
+
}
|
|
28751
|
+
};
|
|
28752
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
28753
|
+
"div",
|
|
28754
|
+
{
|
|
28755
|
+
className: clsx(
|
|
28756
|
+
"flex items-center justify-between px-4 py-3 rounded-lg border",
|
|
28757
|
+
"bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700",
|
|
28758
|
+
"transition-all duration-200",
|
|
28759
|
+
onClick && `cursor-pointer ${config.bg}`,
|
|
28760
|
+
className
|
|
28761
|
+
),
|
|
28762
|
+
onClick: handleClick,
|
|
28763
|
+
role: onClick ? "button" : void 0,
|
|
28764
|
+
tabIndex: onClick ? 0 : void 0,
|
|
28765
|
+
children: [
|
|
28766
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
28767
|
+
/* @__PURE__ */ jsxRuntime.jsx(StatusIcon, { className: clsx("h-5 w-5", config.iconColor) }),
|
|
28768
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
28769
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-gray-900 dark:text-gray-100", children: workspace.workspace_display_name || `WS-${workspace.workspace_id.slice(0, 6)}` }),
|
|
28770
|
+
workspace.line_name && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: workspace.line_name })
|
|
28771
|
+
] })
|
|
28772
|
+
] }),
|
|
28773
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
28774
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: workspace.timeSinceLastUpdate }),
|
|
28775
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: clsx("h-2 w-2 rounded-full", config.dot) })
|
|
28776
|
+
] })
|
|
28777
|
+
]
|
|
28778
|
+
}
|
|
28779
|
+
);
|
|
28780
|
+
};
|
|
28781
|
+
var HealthStatusGrid = ({
|
|
28782
|
+
workspaces,
|
|
28783
|
+
onWorkspaceClick,
|
|
28784
|
+
showFilters = true,
|
|
28785
|
+
groupBy: initialGroupBy = "none",
|
|
28786
|
+
className = ""
|
|
28787
|
+
}) => {
|
|
28788
|
+
const [searchTerm, setSearchTerm] = React19.useState("");
|
|
28789
|
+
const [statusFilter, setStatusFilter] = React19.useState("all");
|
|
28790
|
+
const [groupBy, setGroupBy] = React19.useState(initialGroupBy);
|
|
28791
|
+
const [expandedGroups, setExpandedGroups] = React19.useState(/* @__PURE__ */ new Set());
|
|
28792
|
+
const lastGroupByRef = React19.useRef(initialGroupBy);
|
|
28793
|
+
const hasInitializedGroupsRef = React19.useRef(false);
|
|
28794
|
+
const filteredWorkspaces = React19.useMemo(() => {
|
|
28795
|
+
let filtered = [...workspaces];
|
|
28796
|
+
if (searchTerm) {
|
|
28797
|
+
const search = searchTerm.toLowerCase();
|
|
28798
|
+
filtered = filtered.filter(
|
|
28799
|
+
(w) => w.workspace_display_name?.toLowerCase().includes(search) || w.line_name?.toLowerCase().includes(search) || w.company_name?.toLowerCase().includes(search)
|
|
28800
|
+
);
|
|
28801
|
+
}
|
|
28802
|
+
if (statusFilter !== "all") {
|
|
28803
|
+
filtered = filtered.filter((w) => w.status === statusFilter);
|
|
28804
|
+
}
|
|
28805
|
+
return filtered;
|
|
28806
|
+
}, [workspaces, searchTerm, statusFilter]);
|
|
28807
|
+
const groupedWorkspaces = React19.useMemo(() => {
|
|
28808
|
+
if (groupBy === "none") {
|
|
28809
|
+
return { "All Workspaces": filteredWorkspaces };
|
|
28810
|
+
}
|
|
28811
|
+
const groups = {};
|
|
28812
|
+
filteredWorkspaces.forEach((workspace) => {
|
|
28813
|
+
let key = "Unknown";
|
|
28814
|
+
switch (groupBy) {
|
|
28815
|
+
case "line":
|
|
28816
|
+
key = workspace.line_name || "Unknown Line";
|
|
28817
|
+
break;
|
|
28818
|
+
case "status":
|
|
28819
|
+
key = workspace.status;
|
|
28820
|
+
break;
|
|
28821
|
+
}
|
|
28822
|
+
if (!groups[key]) {
|
|
28823
|
+
groups[key] = [];
|
|
28824
|
+
}
|
|
28825
|
+
groups[key].push(workspace);
|
|
28826
|
+
});
|
|
28827
|
+
const sortedGroups = {};
|
|
28828
|
+
Object.keys(groups).sort().forEach((key) => {
|
|
28829
|
+
sortedGroups[key] = groups[key];
|
|
28830
|
+
});
|
|
28831
|
+
return sortedGroups;
|
|
28832
|
+
}, [filteredWorkspaces, groupBy]);
|
|
28833
|
+
React19.useEffect(() => {
|
|
28834
|
+
if (groupBy !== lastGroupByRef.current) {
|
|
28835
|
+
lastGroupByRef.current = groupBy;
|
|
28836
|
+
hasInitializedGroupsRef.current = false;
|
|
28837
|
+
if (groupBy === "none") {
|
|
28838
|
+
setExpandedGroups(/* @__PURE__ */ new Set());
|
|
28839
|
+
}
|
|
28840
|
+
}
|
|
28841
|
+
}, [groupBy]);
|
|
28842
|
+
React19.useEffect(() => {
|
|
28843
|
+
if (groupBy !== "none" && !hasInitializedGroupsRef.current && Object.keys(groupedWorkspaces).length > 0) {
|
|
28844
|
+
hasInitializedGroupsRef.current = true;
|
|
28845
|
+
setExpandedGroups(new Set(Object.keys(groupedWorkspaces)));
|
|
28846
|
+
}
|
|
28847
|
+
}, [groupBy, groupedWorkspaces]);
|
|
28848
|
+
const toggleGroup = (groupName) => {
|
|
28849
|
+
const newExpanded = new Set(expandedGroups);
|
|
28850
|
+
if (newExpanded.has(groupName)) {
|
|
28851
|
+
newExpanded.delete(groupName);
|
|
28852
|
+
} else {
|
|
28853
|
+
newExpanded.add(groupName);
|
|
28854
|
+
}
|
|
28855
|
+
setExpandedGroups(newExpanded);
|
|
28856
|
+
};
|
|
28857
|
+
const getStatusCounts = () => {
|
|
28858
|
+
const counts = {
|
|
28859
|
+
healthy: 0,
|
|
28860
|
+
unhealthy: 0,
|
|
28861
|
+
warning: 0,
|
|
28862
|
+
unknown: 0
|
|
28863
|
+
};
|
|
28864
|
+
workspaces.forEach((w) => {
|
|
28865
|
+
counts[w.status]++;
|
|
28866
|
+
});
|
|
28867
|
+
return counts;
|
|
28868
|
+
};
|
|
28869
|
+
const statusCounts = getStatusCounts();
|
|
28870
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: clsx("space-y-4", className), children: [
|
|
28871
|
+
showFilters && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4", children: [
|
|
28872
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col sm:flex-row gap-3 flex-wrap", children: [
|
|
28873
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-w-[200px]", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
|
|
28874
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400" }),
|
|
28875
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
28876
|
+
"input",
|
|
28877
|
+
{
|
|
28878
|
+
type: "text",
|
|
28879
|
+
placeholder: "Search workspaces...",
|
|
28880
|
+
value: searchTerm,
|
|
28881
|
+
onChange: (e) => setSearchTerm(e.target.value),
|
|
28882
|
+
className: "w-full pl-10 pr-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100"
|
|
28883
|
+
}
|
|
28884
|
+
)
|
|
28885
|
+
] }) }),
|
|
28886
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Select, { value: statusFilter, onValueChange: (value) => setStatusFilter(value), children: [
|
|
28887
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectTrigger, { className: "w-full sm:w-[180px] bg-white border-gray-200", children: /* @__PURE__ */ jsxRuntime.jsx(SelectValue, { placeholder: "All statuses" }) }),
|
|
28888
|
+
/* @__PURE__ */ jsxRuntime.jsxs(SelectContent, { className: "bg-white border border-gray-200 shadow-lg", children: [
|
|
28889
|
+
/* @__PURE__ */ jsxRuntime.jsxs(SelectItem, { value: "all", className: "text-gray-700 hover:bg-gray-50 focus:bg-gray-50 border-b border-gray-100 last:border-b-0", children: [
|
|
28890
|
+
"All (",
|
|
28891
|
+
workspaces.length,
|
|
28892
|
+
")"
|
|
28893
|
+
] }),
|
|
28894
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: "healthy", className: "text-gray-700 hover:bg-gray-50 focus:bg-gray-50 border-b border-gray-100 last:border-b-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
28895
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-2 w-2 rounded-full bg-green-500" }),
|
|
28896
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
28897
|
+
"Healthy (",
|
|
28898
|
+
statusCounts.healthy,
|
|
28899
|
+
")"
|
|
28900
|
+
] })
|
|
28901
|
+
] }) }),
|
|
28902
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: "unhealthy", className: "text-gray-700 hover:bg-gray-50 focus:bg-gray-50 border-b border-gray-100 last:border-b-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
28903
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-2 w-2 rounded-full bg-red-500" }),
|
|
28904
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
28905
|
+
"Unhealthy (",
|
|
28906
|
+
statusCounts.unhealthy,
|
|
28907
|
+
")"
|
|
28908
|
+
] })
|
|
28909
|
+
] }) }),
|
|
28910
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: "warning", className: "text-gray-700 hover:bg-gray-50 focus:bg-gray-50 border-b border-gray-100 last:border-b-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
28911
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-2 w-2 rounded-full bg-yellow-500" }),
|
|
28912
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
28913
|
+
"Warning (",
|
|
28914
|
+
statusCounts.warning,
|
|
28915
|
+
")"
|
|
28916
|
+
] })
|
|
28917
|
+
] }) })
|
|
28918
|
+
] })
|
|
28919
|
+
] }),
|
|
28920
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Select, { value: groupBy, onValueChange: (value) => setGroupBy(value), children: [
|
|
28921
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectTrigger, { className: "w-full sm:w-[160px] bg-white border-gray-200", children: /* @__PURE__ */ jsxRuntime.jsx(SelectValue, { placeholder: "Group by" }) }),
|
|
28922
|
+
/* @__PURE__ */ jsxRuntime.jsxs(SelectContent, { className: "bg-white border border-gray-200 shadow-lg", children: [
|
|
28923
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: "none", className: "text-gray-700 hover:bg-gray-50 focus:bg-gray-50 border-b border-gray-100 last:border-b-0", children: "No grouping" }),
|
|
28924
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: "line", className: "text-gray-700 hover:bg-gray-50 focus:bg-gray-50 border-b border-gray-100 last:border-b-0", children: "Group by Line" }),
|
|
28925
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: "status", className: "text-gray-700 hover:bg-gray-50 focus:bg-gray-50 border-b border-gray-100 last:border-b-0", children: "Group by Status" })
|
|
28926
|
+
] })
|
|
28927
|
+
] })
|
|
28928
|
+
] }),
|
|
28929
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3 text-sm text-gray-500 dark:text-gray-400", children: [
|
|
28930
|
+
"Showing ",
|
|
28931
|
+
filteredWorkspaces.length,
|
|
28932
|
+
" of ",
|
|
28933
|
+
workspaces.length,
|
|
28934
|
+
" workspaces"
|
|
28935
|
+
] })
|
|
28936
|
+
] }),
|
|
28937
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-6", children: Object.entries(groupedWorkspaces).map(([groupName, groupWorkspaces]) => {
|
|
28938
|
+
const isExpanded = groupBy === "none" || expandedGroups.has(groupName);
|
|
28939
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
|
|
28940
|
+
groupBy !== "none" && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
28941
|
+
"div",
|
|
28942
|
+
{
|
|
28943
|
+
className: "flex items-center justify-between cursor-pointer group",
|
|
28944
|
+
onClick: () => toggleGroup(groupName),
|
|
28945
|
+
children: [
|
|
28946
|
+
/* @__PURE__ */ jsxRuntime.jsxs("h3", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-2", children: [
|
|
28947
|
+
groupName,
|
|
28948
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm font-normal text-gray-500 dark:text-gray-400", children: [
|
|
28949
|
+
"(",
|
|
28950
|
+
groupWorkspaces.length,
|
|
28951
|
+
")"
|
|
28952
|
+
] })
|
|
28953
|
+
] }),
|
|
28954
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
28955
|
+
lucideReact.ChevronDown,
|
|
28956
|
+
{
|
|
28957
|
+
className: clsx(
|
|
28958
|
+
"h-5 w-5 text-gray-400 transition-transform",
|
|
28959
|
+
isExpanded && "rotate-180"
|
|
28960
|
+
)
|
|
28961
|
+
}
|
|
28962
|
+
)
|
|
28963
|
+
]
|
|
28964
|
+
}
|
|
28965
|
+
),
|
|
28966
|
+
isExpanded && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4", children: groupWorkspaces.map((workspace) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
28967
|
+
WorkspaceHealthCard,
|
|
28968
|
+
{
|
|
28969
|
+
workspace,
|
|
28970
|
+
onClick: onWorkspaceClick,
|
|
28971
|
+
showDetails: true
|
|
28972
|
+
},
|
|
28973
|
+
workspace.workspace_id
|
|
28974
|
+
)) })
|
|
28975
|
+
] }, groupName);
|
|
28976
|
+
}) }),
|
|
28977
|
+
filteredWorkspaces.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-12", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-500 dark:text-gray-400", children: searchTerm || statusFilter !== "all" ? "No workspaces found matching your filters." : "No workspaces available." }) })
|
|
28978
|
+
] });
|
|
28979
|
+
};
|
|
28172
28980
|
var ISTTimer2 = ISTTimer_default;
|
|
28173
28981
|
var DashboardHeader = React19.memo(({ lineTitle, className = "", headerControls }) => {
|
|
28174
28982
|
const getShiftName = () => {
|
|
@@ -28666,6 +29474,17 @@ var SideNavBar = React19.memo(({
|
|
|
28666
29474
|
});
|
|
28667
29475
|
onMobileMenuClose?.();
|
|
28668
29476
|
}, [navigate, onMobileMenuClose]);
|
|
29477
|
+
const handleHealthClick = React19.useCallback(() => {
|
|
29478
|
+
navigate("/health", {
|
|
29479
|
+
trackingEvent: {
|
|
29480
|
+
name: "Health Status Page Clicked",
|
|
29481
|
+
properties: {
|
|
29482
|
+
source: "side_nav"
|
|
29483
|
+
}
|
|
29484
|
+
}
|
|
29485
|
+
});
|
|
29486
|
+
onMobileMenuClose?.();
|
|
29487
|
+
}, [navigate, onMobileMenuClose]);
|
|
28669
29488
|
const handleLogoClick = React19.useCallback(() => {
|
|
28670
29489
|
navigate("/");
|
|
28671
29490
|
onMobileMenuClose?.();
|
|
@@ -28679,6 +29498,7 @@ var SideNavBar = React19.memo(({
|
|
|
28679
29498
|
const profileButtonClasses = React19.useMemo(() => getButtonClasses("/profile"), [getButtonClasses, pathname]);
|
|
28680
29499
|
const helpButtonClasses = React19.useMemo(() => getButtonClasses("/help"), [getButtonClasses, pathname]);
|
|
28681
29500
|
const skusButtonClasses = React19.useMemo(() => getButtonClasses("/skus"), [getButtonClasses, pathname]);
|
|
29501
|
+
const healthButtonClasses = React19.useMemo(() => getButtonClasses("/health"), [getButtonClasses, pathname]);
|
|
28682
29502
|
const NavigationContent = () => /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
28683
29503
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full py-6 px-4 flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
28684
29504
|
"button",
|
|
@@ -28823,6 +29643,21 @@ var SideNavBar = React19.memo(({
|
|
|
28823
29643
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-medium leading-tight", children: "Help" })
|
|
28824
29644
|
]
|
|
28825
29645
|
}
|
|
29646
|
+
),
|
|
29647
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
29648
|
+
"button",
|
|
29649
|
+
{
|
|
29650
|
+
onClick: handleHealthClick,
|
|
29651
|
+
className: healthButtonClasses,
|
|
29652
|
+
"aria-label": "System Health",
|
|
29653
|
+
tabIndex: 0,
|
|
29654
|
+
role: "tab",
|
|
29655
|
+
"aria-selected": pathname === "/health" || pathname.startsWith("/health/"),
|
|
29656
|
+
children: [
|
|
29657
|
+
/* @__PURE__ */ jsxRuntime.jsx(outline.HeartIcon, { className: "w-5 h-5 mb-1" }),
|
|
29658
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-medium leading-tight", children: "Health" })
|
|
29659
|
+
]
|
|
29660
|
+
}
|
|
28826
29661
|
)
|
|
28827
29662
|
] })
|
|
28828
29663
|
] }),
|
|
@@ -35448,6 +36283,7 @@ var TargetsViewUI = ({
|
|
|
35448
36283
|
onSaveLine,
|
|
35449
36284
|
onToggleBulkConfigure,
|
|
35450
36285
|
onBulkConfigure,
|
|
36286
|
+
onUpdateWorkspaceDisplayName,
|
|
35451
36287
|
// SKU props
|
|
35452
36288
|
skuEnabled = false,
|
|
35453
36289
|
skus = [],
|
|
@@ -35613,7 +36449,18 @@ var TargetsViewUI = ({
|
|
|
35613
36449
|
{
|
|
35614
36450
|
className: "px-6 py-4 hover:bg-gray-50 transition-all duration-200",
|
|
35615
36451
|
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-12 gap-6 items-center", children: [
|
|
35616
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-2", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
36452
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-2", children: onUpdateWorkspaceDisplayName ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
36453
|
+
InlineEditableText,
|
|
36454
|
+
{
|
|
36455
|
+
value: formattedName,
|
|
36456
|
+
onSave: async (newName) => {
|
|
36457
|
+
await onUpdateWorkspaceDisplayName(workspace.id, newName);
|
|
36458
|
+
},
|
|
36459
|
+
placeholder: "Workspace name",
|
|
36460
|
+
className: "font-medium text-gray-900",
|
|
36461
|
+
inputClassName: "min-w-[120px]"
|
|
36462
|
+
}
|
|
36463
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-gray-900", children: formattedName }) }),
|
|
35617
36464
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-2", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
35618
36465
|
"select",
|
|
35619
36466
|
{
|
|
@@ -36354,6 +37201,17 @@ var TargetsView = ({
|
|
|
36354
37201
|
router.push("/");
|
|
36355
37202
|
}
|
|
36356
37203
|
};
|
|
37204
|
+
const handleUpdateWorkspaceDisplayName = React19.useCallback(async (workspaceId, displayName) => {
|
|
37205
|
+
try {
|
|
37206
|
+
await workspaceService.updateWorkspaceDisplayName(workspaceId, displayName);
|
|
37207
|
+
await forceRefreshWorkspaceDisplayNames();
|
|
37208
|
+
sonner.toast.success("Workspace name updated successfully");
|
|
37209
|
+
} catch (error) {
|
|
37210
|
+
console.error("Error updating workspace display name:", error);
|
|
37211
|
+
sonner.toast.error("Failed to update workspace name");
|
|
37212
|
+
throw error;
|
|
37213
|
+
}
|
|
37214
|
+
}, []);
|
|
36357
37215
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
36358
37216
|
TargetsViewUI_default,
|
|
36359
37217
|
{
|
|
@@ -36376,6 +37234,7 @@ var TargetsView = ({
|
|
|
36376
37234
|
onSaveLine: handleSaveLine,
|
|
36377
37235
|
onToggleBulkConfigure: handleToggleBulkConfigure,
|
|
36378
37236
|
onBulkConfigure: handleBulkConfigure,
|
|
37237
|
+
onUpdateWorkspaceDisplayName: handleUpdateWorkspaceDisplayName,
|
|
36379
37238
|
skuEnabled,
|
|
36380
37239
|
skus,
|
|
36381
37240
|
onUpdateSelectedSKU: updateSelectedSKU,
|
|
@@ -36474,6 +37333,14 @@ var WorkspaceDetailView = ({
|
|
|
36474
37333
|
const [usingFallbackData, setUsingFallbackData] = React19.useState(false);
|
|
36475
37334
|
const [showIdleTime, setShowIdleTime] = React19.useState(false);
|
|
36476
37335
|
const dashboardConfig = useDashboardConfig();
|
|
37336
|
+
const {
|
|
37337
|
+
workspace: workspaceHealth,
|
|
37338
|
+
loading: healthLoading,
|
|
37339
|
+
error: healthError
|
|
37340
|
+
} = useWorkspaceHealthById(workspaceId, {
|
|
37341
|
+
enableRealtime: true,
|
|
37342
|
+
refreshInterval: 3e4
|
|
37343
|
+
});
|
|
36477
37344
|
const {
|
|
36478
37345
|
status: prefetchStatus,
|
|
36479
37346
|
data: prefetchData,
|
|
@@ -36826,10 +37693,23 @@ var WorkspaceDetailView = ({
|
|
|
36826
37693
|
"aria-label": "Navigate back to previous page"
|
|
36827
37694
|
}
|
|
36828
37695
|
) }),
|
|
36829
|
-
/* @__PURE__ */ jsxRuntime.
|
|
37696
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-1/2 transform -translate-x-1/2", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
36830
37697
|
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-3xl font-semibold text-gray-900", children: formattedWorkspaceName }),
|
|
36831
|
-
/* @__PURE__ */ jsxRuntime.
|
|
36832
|
-
|
|
37698
|
+
workspaceHealth && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex h-2.5 w-2.5", children: [
|
|
37699
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: clsx(
|
|
37700
|
+
"animate-ping absolute inline-flex h-full w-full rounded-full opacity-75",
|
|
37701
|
+
workspaceHealth.status === "healthy" ? "bg-green-400" : "bg-red-400"
|
|
37702
|
+
) }),
|
|
37703
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: clsx(
|
|
37704
|
+
"relative inline-flex rounded-full h-2.5 w-2.5",
|
|
37705
|
+
workspaceHealth.status === "healthy" ? "bg-green-500" : "bg-red-500"
|
|
37706
|
+
) })
|
|
37707
|
+
] })
|
|
37708
|
+
] }) }),
|
|
37709
|
+
workspaceHealth && activeTab !== "monthly_history" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute right-0 top-0 flex items-center h-8", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-gray-500", children: [
|
|
37710
|
+
"Last update: ",
|
|
37711
|
+
workspaceHealth.timeSinceLastUpdate
|
|
37712
|
+
] }) }),
|
|
36833
37713
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-8" })
|
|
36834
37714
|
] }),
|
|
36835
37715
|
activeTab !== "monthly_history" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 bg-blue-50 px-3 py-2 rounded-lg", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-4", children: [
|
|
@@ -37393,6 +38273,253 @@ var SKUManagementView = () => {
|
|
|
37393
38273
|
)
|
|
37394
38274
|
] });
|
|
37395
38275
|
};
|
|
38276
|
+
var WorkspaceHealthView = ({
|
|
38277
|
+
lineId,
|
|
38278
|
+
companyId,
|
|
38279
|
+
onNavigate,
|
|
38280
|
+
className = ""
|
|
38281
|
+
}) => {
|
|
38282
|
+
const router$1 = router.useRouter();
|
|
38283
|
+
const [groupBy, setGroupBy] = React19.useState("line");
|
|
38284
|
+
const operationalDate = getOperationalDate();
|
|
38285
|
+
const currentHour = (/* @__PURE__ */ new Date()).getHours();
|
|
38286
|
+
const isNightShift = currentHour >= 18 || currentHour < 6;
|
|
38287
|
+
const shiftType = isNightShift ? "Night" : "Day";
|
|
38288
|
+
const formatDate = (date) => {
|
|
38289
|
+
const d = new Date(date);
|
|
38290
|
+
return d.toLocaleDateString("en-IN", {
|
|
38291
|
+
month: "long",
|
|
38292
|
+
day: "numeric",
|
|
38293
|
+
year: "numeric",
|
|
38294
|
+
timeZone: "Asia/Kolkata"
|
|
38295
|
+
});
|
|
38296
|
+
};
|
|
38297
|
+
const getShiftIcon = (shift) => {
|
|
38298
|
+
return shift === "Night" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Moon, { className: "h-4 w-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sun, { className: "h-4 w-4" });
|
|
38299
|
+
};
|
|
38300
|
+
const {
|
|
38301
|
+
workspaces,
|
|
38302
|
+
summary,
|
|
38303
|
+
loading,
|
|
38304
|
+
error,
|
|
38305
|
+
refetch
|
|
38306
|
+
} = useWorkspaceHealth({
|
|
38307
|
+
lineId,
|
|
38308
|
+
companyId,
|
|
38309
|
+
enableRealtime: true,
|
|
38310
|
+
refreshInterval: 1e4
|
|
38311
|
+
// Refresh every 10 seconds for more responsive updates
|
|
38312
|
+
});
|
|
38313
|
+
const handleWorkspaceClick = React19.useCallback(
|
|
38314
|
+
(workspace) => {
|
|
38315
|
+
const url = `/workspace/${workspace.workspace_id}`;
|
|
38316
|
+
if (onNavigate) {
|
|
38317
|
+
onNavigate(url);
|
|
38318
|
+
} else {
|
|
38319
|
+
router$1.push(url);
|
|
38320
|
+
}
|
|
38321
|
+
},
|
|
38322
|
+
[router$1, onNavigate]
|
|
38323
|
+
);
|
|
38324
|
+
const handleExport = React19.useCallback(() => {
|
|
38325
|
+
const csv = [
|
|
38326
|
+
["Workspace", "Line", "Company", "Status", "Last Heartbeat", "Consecutive Misses"],
|
|
38327
|
+
...workspaces.map((w) => [
|
|
38328
|
+
w.workspace_display_name || "",
|
|
38329
|
+
w.line_name || "",
|
|
38330
|
+
w.company_name || "",
|
|
38331
|
+
w.status,
|
|
38332
|
+
w.last_heartbeat,
|
|
38333
|
+
w.consecutive_misses?.toString() || "0"
|
|
38334
|
+
])
|
|
38335
|
+
].map((row) => row.join(",")).join("\n");
|
|
38336
|
+
const blob = new Blob([csv], { type: "text/csv" });
|
|
38337
|
+
const url = window.URL.createObjectURL(blob);
|
|
38338
|
+
const a = document.createElement("a");
|
|
38339
|
+
a.href = url;
|
|
38340
|
+
a.download = `workspace-health-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.csv`;
|
|
38341
|
+
document.body.appendChild(a);
|
|
38342
|
+
a.click();
|
|
38343
|
+
document.body.removeChild(a);
|
|
38344
|
+
window.URL.revokeObjectURL(url);
|
|
38345
|
+
}, [workspaces]);
|
|
38346
|
+
const getStatusIcon = (status) => {
|
|
38347
|
+
switch (status) {
|
|
38348
|
+
case "healthy":
|
|
38349
|
+
return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckCircle, { className: "h-5 w-5 text-green-500" });
|
|
38350
|
+
case "unhealthy":
|
|
38351
|
+
return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.XCircle, { className: "h-5 w-5 text-red-500" });
|
|
38352
|
+
case "warning":
|
|
38353
|
+
return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "h-5 w-5 text-yellow-500" });
|
|
38354
|
+
default:
|
|
38355
|
+
return /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Activity, { className: "h-5 w-5 text-gray-400" });
|
|
38356
|
+
}
|
|
38357
|
+
};
|
|
38358
|
+
const getUptimeColor = (percentage) => {
|
|
38359
|
+
if (percentage >= 99) return "text-green-600 dark:text-green-400";
|
|
38360
|
+
if (percentage >= 95) return "text-yellow-600 dark:text-yellow-400";
|
|
38361
|
+
return "text-red-600 dark:text-red-400";
|
|
38362
|
+
};
|
|
38363
|
+
if (loading && !summary) {
|
|
38364
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-screen bg-gray-50 dark:bg-gray-900 p-4", children: /* @__PURE__ */ jsxRuntime.jsx(LoadingState, {}) });
|
|
38365
|
+
}
|
|
38366
|
+
if (error) {
|
|
38367
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-screen bg-gray-50 dark:bg-gray-900 p-4", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-7xl mx-auto", children: /* @__PURE__ */ jsxRuntime.jsx(Card2, { className: "border-red-200 dark:border-red-800", children: /* @__PURE__ */ jsxRuntime.jsxs(CardContent2, { className: "p-8 text-center", children: [
|
|
38368
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.XCircle, { className: "h-12 w-12 text-red-500 mx-auto mb-4" }),
|
|
38369
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-xl font-semibold text-gray-900 dark:text-gray-100 mb-2", children: "Error Loading Health Status" }),
|
|
38370
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 dark:text-gray-400 mb-4", children: error.message || "Unable to load workspace health status" }),
|
|
38371
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
38372
|
+
"button",
|
|
38373
|
+
{
|
|
38374
|
+
onClick: () => refetch(),
|
|
38375
|
+
className: "px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors",
|
|
38376
|
+
children: "Try Again"
|
|
38377
|
+
}
|
|
38378
|
+
)
|
|
38379
|
+
] }) }) }) });
|
|
38380
|
+
}
|
|
38381
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: clsx("min-h-screen bg-slate-50", className), children: [
|
|
38382
|
+
/* @__PURE__ */ jsxRuntime.jsxs("header", { className: "sticky top-0 z-10 px-2 sm:px-2.5 lg:px-3 py-1.5 sm:py-2 lg:py-3 flex flex-col shadow-sm bg-white", children: [
|
|
38383
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex items-center", children: [
|
|
38384
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-0 z-10", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
38385
|
+
BackButtonMinimal,
|
|
38386
|
+
{
|
|
38387
|
+
onClick: () => router$1.push("/"),
|
|
38388
|
+
text: "Back",
|
|
38389
|
+
size: "default",
|
|
38390
|
+
"aria-label": "Navigate back to dashboard"
|
|
38391
|
+
}
|
|
38392
|
+
) }),
|
|
38393
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-1/2 transform -translate-x-1/2", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
38394
|
+
/* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-3xl font-semibold text-gray-900", children: "System Health" }),
|
|
38395
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative flex h-2.5 w-2.5", children: [
|
|
38396
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" }),
|
|
38397
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500" })
|
|
38398
|
+
] })
|
|
38399
|
+
] }) }),
|
|
38400
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute right-0 flex gap-2", children: [
|
|
38401
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
38402
|
+
"button",
|
|
38403
|
+
{
|
|
38404
|
+
onClick: () => {
|
|
38405
|
+
refetch();
|
|
38406
|
+
},
|
|
38407
|
+
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
38408
|
+
"aria-label": "Refresh",
|
|
38409
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: "h-5 w-5" })
|
|
38410
|
+
}
|
|
38411
|
+
),
|
|
38412
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
38413
|
+
"button",
|
|
38414
|
+
{
|
|
38415
|
+
onClick: handleExport,
|
|
38416
|
+
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
38417
|
+
"aria-label": "Export CSV",
|
|
38418
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-5 w-5" })
|
|
38419
|
+
}
|
|
38420
|
+
)
|
|
38421
|
+
] }),
|
|
38422
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full h-8" })
|
|
38423
|
+
] }),
|
|
38424
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 bg-blue-50 px-3 py-2 rounded-lg", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center gap-4", children: [
|
|
38425
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-lg font-medium text-blue-600", children: /* @__PURE__ */ jsxRuntime.jsx(LiveTimer, {}) }),
|
|
38426
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px h-4 bg-blue-300" }),
|
|
38427
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-base font-medium text-blue-600", children: formatDate(operationalDate) }),
|
|
38428
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-px h-4 bg-blue-300" }),
|
|
38429
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
38430
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-blue-600", children: getShiftIcon(shiftType) }),
|
|
38431
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-base font-medium text-blue-600", children: [
|
|
38432
|
+
shiftType,
|
|
38433
|
+
" Shift"
|
|
38434
|
+
] })
|
|
38435
|
+
] })
|
|
38436
|
+
] }) })
|
|
38437
|
+
] }),
|
|
38438
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-7xl mx-auto p-4 space-y-6", children: [
|
|
38439
|
+
summary && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
38440
|
+
motion.div,
|
|
38441
|
+
{
|
|
38442
|
+
initial: { opacity: 0, y: 20 },
|
|
38443
|
+
animate: { opacity: 1, y: 0 },
|
|
38444
|
+
transition: { duration: 0.3, delay: 0.1 },
|
|
38445
|
+
className: "grid grid-cols-2 sm:grid-cols-2 md:grid-cols-5 gap-2 sm:gap-3 lg:gap-4",
|
|
38446
|
+
children: [
|
|
38447
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "col-span-2 sm:col-span-2 md:col-span-2 bg-white", children: [
|
|
38448
|
+
/* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400", children: "Overall System Status" }) }),
|
|
38449
|
+
/* @__PURE__ */ jsxRuntime.jsxs(CardContent2, { children: [
|
|
38450
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline gap-2", children: [
|
|
38451
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: clsx("text-3xl font-bold", getUptimeColor(summary.uptimePercentage)), children: [
|
|
38452
|
+
summary.uptimePercentage.toFixed(1),
|
|
38453
|
+
"%"
|
|
38454
|
+
] }),
|
|
38455
|
+
summary.uptimePercentage >= 99 ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TrendingUp, { className: "h-5 w-5 text-green-500" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.TrendingDown, { className: "h-5 w-5 text-red-500" })
|
|
38456
|
+
] }),
|
|
38457
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: [
|
|
38458
|
+
summary.healthyWorkspaces,
|
|
38459
|
+
" of ",
|
|
38460
|
+
summary.totalWorkspaces,
|
|
38461
|
+
" workspaces healthy"
|
|
38462
|
+
] })
|
|
38463
|
+
] })
|
|
38464
|
+
] }),
|
|
38465
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "bg-white", children: [
|
|
38466
|
+
/* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxRuntime.jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
|
|
38467
|
+
getStatusIcon("healthy"),
|
|
38468
|
+
"Healthy"
|
|
38469
|
+
] }) }),
|
|
38470
|
+
/* @__PURE__ */ jsxRuntime.jsxs(CardContent2, { children: [
|
|
38471
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-2xl font-bold text-gray-900 dark:text-gray-50", children: summary.healthyWorkspaces }),
|
|
38472
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Operating normally" })
|
|
38473
|
+
] })
|
|
38474
|
+
] }),
|
|
38475
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "bg-white", children: [
|
|
38476
|
+
/* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxRuntime.jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
|
|
38477
|
+
getStatusIcon("warning"),
|
|
38478
|
+
"Warning"
|
|
38479
|
+
] }) }),
|
|
38480
|
+
/* @__PURE__ */ jsxRuntime.jsxs(CardContent2, { children: [
|
|
38481
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-2xl font-bold text-gray-900 dark:text-gray-50", children: summary.warningWorkspaces }),
|
|
38482
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Delayed updates" })
|
|
38483
|
+
] })
|
|
38484
|
+
] }),
|
|
38485
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Card2, { className: "bg-white", children: [
|
|
38486
|
+
/* @__PURE__ */ jsxRuntime.jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxRuntime.jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
|
|
38487
|
+
getStatusIcon("unhealthy"),
|
|
38488
|
+
"Unhealthy"
|
|
38489
|
+
] }) }),
|
|
38490
|
+
/* @__PURE__ */ jsxRuntime.jsxs(CardContent2, { children: [
|
|
38491
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-2xl font-bold text-gray-900 dark:text-gray-50", children: summary.unhealthyWorkspaces }),
|
|
38492
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Requires attention" })
|
|
38493
|
+
] })
|
|
38494
|
+
] })
|
|
38495
|
+
]
|
|
38496
|
+
}
|
|
38497
|
+
),
|
|
38498
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
38499
|
+
motion.div,
|
|
38500
|
+
{
|
|
38501
|
+
initial: { opacity: 0, y: 20 },
|
|
38502
|
+
animate: { opacity: 1, y: 0 },
|
|
38503
|
+
transition: { duration: 0.3, delay: 0.2 },
|
|
38504
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
38505
|
+
HealthStatusGrid,
|
|
38506
|
+
{
|
|
38507
|
+
workspaces,
|
|
38508
|
+
onWorkspaceClick: handleWorkspaceClick,
|
|
38509
|
+
showFilters: true,
|
|
38510
|
+
groupBy
|
|
38511
|
+
}
|
|
38512
|
+
)
|
|
38513
|
+
}
|
|
38514
|
+
)
|
|
38515
|
+
] })
|
|
38516
|
+
] });
|
|
38517
|
+
};
|
|
38518
|
+
var WorkspaceHealthView_default = withAuth(WorkspaceHealthView, {
|
|
38519
|
+
redirectTo: "/login",
|
|
38520
|
+
requireAuth: true
|
|
38521
|
+
});
|
|
38522
|
+
var AuthenticatedWorkspaceHealthView = WorkspaceHealthView;
|
|
37396
38523
|
var S3Service = class {
|
|
37397
38524
|
constructor(config) {
|
|
37398
38525
|
this.s3Client = null;
|
|
@@ -37859,6 +38986,7 @@ exports.AuthenticatedHelpView = AuthenticatedHelpView;
|
|
|
37859
38986
|
exports.AuthenticatedHomeView = AuthenticatedHomeView;
|
|
37860
38987
|
exports.AuthenticatedShiftsView = AuthenticatedShiftsView;
|
|
37861
38988
|
exports.AuthenticatedTargetsView = AuthenticatedTargetsView;
|
|
38989
|
+
exports.AuthenticatedWorkspaceHealthView = AuthenticatedWorkspaceHealthView;
|
|
37862
38990
|
exports.BackButton = BackButton;
|
|
37863
38991
|
exports.BackButtonMinimal = BackButtonMinimal;
|
|
37864
38992
|
exports.BarChart = BarChart;
|
|
@@ -37872,6 +39000,7 @@ exports.CardDescription = CardDescription2;
|
|
|
37872
39000
|
exports.CardFooter = CardFooter2;
|
|
37873
39001
|
exports.CardHeader = CardHeader2;
|
|
37874
39002
|
exports.CardTitle = CardTitle2;
|
|
39003
|
+
exports.CompactWorkspaceHealthCard = CompactWorkspaceHealthCard;
|
|
37875
39004
|
exports.CongratulationsOverlay = CongratulationsOverlay;
|
|
37876
39005
|
exports.CycleTimeChart = CycleTimeChart;
|
|
37877
39006
|
exports.CycleTimeOverTimeChart = CycleTimeOverTimeChart;
|
|
@@ -37895,6 +39024,7 @@ exports.DateDisplay = DateDisplay_default;
|
|
|
37895
39024
|
exports.DateTimeDisplay = DateTimeDisplay;
|
|
37896
39025
|
exports.DebugAuth = DebugAuth;
|
|
37897
39026
|
exports.DebugAuthView = DebugAuthView_default;
|
|
39027
|
+
exports.DetailedHealthStatus = DetailedHealthStatus;
|
|
37898
39028
|
exports.EmptyStateMessage = EmptyStateMessage;
|
|
37899
39029
|
exports.EncouragementOverlay = EncouragementOverlay;
|
|
37900
39030
|
exports.FactoryView = FactoryView_default;
|
|
@@ -37902,10 +39032,13 @@ exports.GaugeChart = GaugeChart;
|
|
|
37902
39032
|
exports.GridComponentsPlaceholder = GridComponentsPlaceholder;
|
|
37903
39033
|
exports.HamburgerButton = HamburgerButton;
|
|
37904
39034
|
exports.Header = Header;
|
|
39035
|
+
exports.HealthStatusGrid = HealthStatusGrid;
|
|
39036
|
+
exports.HealthStatusIndicator = HealthStatusIndicator;
|
|
37905
39037
|
exports.HelpView = HelpView_default;
|
|
37906
39038
|
exports.HomeView = HomeView_default;
|
|
37907
39039
|
exports.HourlyOutputChart = HourlyOutputChart2;
|
|
37908
39040
|
exports.ISTTimer = ISTTimer_default;
|
|
39041
|
+
exports.InlineEditableText = InlineEditableText;
|
|
37909
39042
|
exports.KPICard = KPICard;
|
|
37910
39043
|
exports.KPIDetailView = KPIDetailView_default;
|
|
37911
39044
|
exports.KPIGrid = KPIGrid;
|
|
@@ -37947,6 +39080,7 @@ exports.PrefetchStatus = PrefetchStatus;
|
|
|
37947
39080
|
exports.PrefetchTimeoutError = PrefetchTimeoutError;
|
|
37948
39081
|
exports.ProfileView = ProfileView_default;
|
|
37949
39082
|
exports.RegistryProvider = RegistryProvider;
|
|
39083
|
+
exports.S3ClipsService = S3ClipsService;
|
|
37950
39084
|
exports.S3Service = S3Service;
|
|
37951
39085
|
exports.SKUManagementView = SKUManagementView;
|
|
37952
39086
|
exports.SOPComplianceChart = SOPComplianceChart;
|
|
@@ -37987,6 +39121,8 @@ exports.WorkspaceDetailView = WorkspaceDetailView_default;
|
|
|
37987
39121
|
exports.WorkspaceDisplayNameExample = WorkspaceDisplayNameExample;
|
|
37988
39122
|
exports.WorkspaceGrid = WorkspaceGrid;
|
|
37989
39123
|
exports.WorkspaceGridItem = WorkspaceGridItem;
|
|
39124
|
+
exports.WorkspaceHealthCard = WorkspaceHealthCard;
|
|
39125
|
+
exports.WorkspaceHealthView = WorkspaceHealthView_default;
|
|
37990
39126
|
exports.WorkspaceHistoryCalendar = WorkspaceHistoryCalendar;
|
|
37991
39127
|
exports.WorkspaceMetricCards = WorkspaceMetricCards;
|
|
37992
39128
|
exports.WorkspaceMonthlyDataFetcher = WorkspaceMonthlyDataFetcher;
|
|
@@ -38071,6 +39207,7 @@ exports.isWorkspaceDisplayNamesLoading = isWorkspaceDisplayNamesLoading;
|
|
|
38071
39207
|
exports.mergeWithDefaultConfig = mergeWithDefaultConfig;
|
|
38072
39208
|
exports.migrateLegacyConfiguration = migrateLegacyConfiguration;
|
|
38073
39209
|
exports.optifyeAgentClient = optifyeAgentClient;
|
|
39210
|
+
exports.parseS3Uri = parseS3Uri;
|
|
38074
39211
|
exports.preInitializeWorkspaceDisplayNames = preInitializeWorkspaceDisplayNames;
|
|
38075
39212
|
exports.preloadS3Video = preloadS3Video;
|
|
38076
39213
|
exports.preloadS3VideoUrl = preloadS3VideoUrl;
|
|
@@ -38084,6 +39221,7 @@ exports.resetCoreMixpanel = resetCoreMixpanel;
|
|
|
38084
39221
|
exports.resetFailedUrl = resetFailedUrl;
|
|
38085
39222
|
exports.resetSubscriptionManager = resetSubscriptionManager;
|
|
38086
39223
|
exports.s3VideoPreloader = s3VideoPreloader;
|
|
39224
|
+
exports.shuffleArray = shuffleArray;
|
|
38087
39225
|
exports.skuService = skuService;
|
|
38088
39226
|
exports.startCoreSessionRecording = startCoreSessionRecording;
|
|
38089
39227
|
exports.stopCoreSessionRecording = stopCoreSessionRecording;
|
|
@@ -38150,6 +39288,8 @@ exports.useWorkspaceDetailedMetrics = useWorkspaceDetailedMetrics;
|
|
|
38150
39288
|
exports.useWorkspaceDisplayName = useWorkspaceDisplayName;
|
|
38151
39289
|
exports.useWorkspaceDisplayNames = useWorkspaceDisplayNames;
|
|
38152
39290
|
exports.useWorkspaceDisplayNamesMap = useWorkspaceDisplayNamesMap;
|
|
39291
|
+
exports.useWorkspaceHealth = useWorkspaceHealth;
|
|
39292
|
+
exports.useWorkspaceHealthById = useWorkspaceHealthById;
|
|
38153
39293
|
exports.useWorkspaceMetrics = useWorkspaceMetrics;
|
|
38154
39294
|
exports.useWorkspaceNavigation = useWorkspaceNavigation;
|
|
38155
39295
|
exports.useWorkspaceOperators = useWorkspaceOperators;
|
|
@@ -38158,4 +39298,5 @@ exports.videoPreloader = videoPreloader;
|
|
|
38158
39298
|
exports.whatsappService = whatsappService;
|
|
38159
39299
|
exports.withAuth = withAuth;
|
|
38160
39300
|
exports.withRegistry = withRegistry;
|
|
39301
|
+
exports.workspaceHealthService = workspaceHealthService;
|
|
38161
39302
|
exports.workspaceService = workspaceService;
|