@optifye/dashboard-core 6.4.2 → 6.5.0
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 +297 -0
- package/dist/index.d.mts +243 -77
- package/dist/index.d.ts +243 -77
- package/dist/index.js +1847 -628
- package/dist/index.mjs +1837 -632
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -3,10 +3,9 @@ import React19__default, { createContext, useRef, useCallback, useState, useMemo
|
|
|
3
3
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
import { useRouter } from 'next/router';
|
|
5
5
|
import { toZonedTime, formatInTimeZone } from 'date-fns-tz';
|
|
6
|
-
import { subDays, format, parseISO, isValid, isFuture, isToday } from 'date-fns';
|
|
6
|
+
import { subDays, format, parseISO, isValid, formatDistanceToNow, isFuture, isToday } from 'date-fns';
|
|
7
7
|
import mixpanel from 'mixpanel-browser';
|
|
8
8
|
import { EventEmitter } from 'events';
|
|
9
|
-
import { S3Client, ListObjectsV2Command, GetObjectCommand } from '@aws-sdk/client-s3';
|
|
10
9
|
import { REALTIME_SUBSCRIBE_STATES, createClient } from '@supabase/supabase-js';
|
|
11
10
|
import Hls2 from 'hls.js';
|
|
12
11
|
import useSWR from 'swr';
|
|
@@ -14,9 +13,9 @@ import { noop, warning, invariant, progress, secondsToMilliseconds, milliseconds
|
|
|
14
13
|
import { getValueTransition, hover, press, isPrimaryPointer, GroupPlaybackControls, setDragLock, supportsLinearEasing, attachTimeline, isGenerator, calcGeneratorDuration, isWaapiSupportedEasing, mapEasingToNativeEasing, maxGeneratorDuration, generateLinearEasing, isBezierDefinition } from 'motion-dom';
|
|
15
14
|
import { BarChart as BarChart$1, CartesianGrid, XAxis, YAxis, Tooltip, Legend, Bar, LabelList, ResponsiveContainer, LineChart as LineChart$1, Line, PieChart, Pie, Cell, ReferenceLine, ComposedChart, Area, ScatterChart, Scatter } from 'recharts';
|
|
16
15
|
import { Slot } from '@radix-ui/react-slot';
|
|
17
|
-
import { Camera, ChevronDown, ChevronUp, Check, ShieldCheck, Star, Award, X, Coffee, Plus, ArrowLeft, Clock, Calendar, Save, Minus, ArrowDown, ArrowUp, Settings2, CheckCircle2, Search, CheckCircle, AlertTriangle, Info, Share2, Trophy, Target, Download, User, XCircle, ChevronLeft, ChevronRight,
|
|
16
|
+
import { Camera, ChevronDown, ChevronUp, Check, ShieldCheck, Star, Award, X, Coffee, Plus, ArrowLeft, Clock, Calendar, Save, Minus, ArrowDown, ArrowUp, Settings2, CheckCircle2, Search, Loader2, AlertCircle, Edit2, CheckCircle, AlertTriangle, Info, Share2, Trophy, Target, Download, User, XCircle, ChevronLeft, ChevronRight, WifiOff, Wifi, Grid3x3, List, Sun, Moon, MessageSquare, Trash2, RefreshCw, Menu, Send, Copy, UserCheck, LogOut, Package, TrendingUp, TrendingDown, Activity, Settings, LifeBuoy, EyeOff, Eye, Zap, UserCircle } from 'lucide-react';
|
|
18
17
|
import { DayPicker, useNavigation as useNavigation$1 } from 'react-day-picker';
|
|
19
|
-
import { XMarkIcon, ArrowRightIcon, HomeIcon, TrophyIcon, ChartBarIcon, AdjustmentsHorizontalIcon, ClockIcon, CubeIcon, SparklesIcon, QuestionMarkCircleIcon, UserCircleIcon, ExclamationCircleIcon, EnvelopeIcon, DocumentTextIcon, ChevronUpIcon, ChevronDownIcon, Bars3Icon, CheckCircleIcon, ChatBubbleLeftRightIcon, XCircleIcon, InformationCircleIcon, ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline';
|
|
18
|
+
import { XMarkIcon, ArrowRightIcon, HomeIcon, TrophyIcon, ChartBarIcon, AdjustmentsHorizontalIcon, ClockIcon, CubeIcon, SparklesIcon, QuestionMarkCircleIcon, HeartIcon, UserCircleIcon, ExclamationCircleIcon, EnvelopeIcon, DocumentTextIcon, ChevronUpIcon, ChevronDownIcon, Bars3Icon, CheckCircleIcon, ChatBubbleLeftRightIcon, XCircleIcon, InformationCircleIcon, ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline';
|
|
20
19
|
import { CheckIcon } from '@heroicons/react/24/solid';
|
|
21
20
|
import html2canvas from 'html2canvas';
|
|
22
21
|
import jsPDF, { jsPDF as jsPDF$1 } from 'jspdf';
|
|
@@ -24,6 +23,7 @@ import * as SelectPrimitive from '@radix-ui/react-select';
|
|
|
24
23
|
import videojs from 'video.js';
|
|
25
24
|
import 'video.js/dist/video-js.css';
|
|
26
25
|
import { toast } from 'sonner';
|
|
26
|
+
import { S3Client, ListObjectsV2Command, GetObjectCommand } from '@aws-sdk/client-s3';
|
|
27
27
|
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
|
28
28
|
import { Readable } from 'stream';
|
|
29
29
|
|
|
@@ -1674,6 +1674,25 @@ var workspaceService = {
|
|
|
1674
1674
|
this._workspaceDisplayNamesCache.clear();
|
|
1675
1675
|
this._cacheTimestamp = 0;
|
|
1676
1676
|
},
|
|
1677
|
+
/**
|
|
1678
|
+
* Updates the display name for a workspace
|
|
1679
|
+
* @param workspaceId - The workspace UUID
|
|
1680
|
+
* @param displayName - The new display name
|
|
1681
|
+
* @returns Promise<void>
|
|
1682
|
+
*/
|
|
1683
|
+
async updateWorkspaceDisplayName(workspaceId, displayName) {
|
|
1684
|
+
const supabase = _getSupabaseInstance();
|
|
1685
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
1686
|
+
const config = _getDashboardConfigInstance();
|
|
1687
|
+
const dbConfig = config?.databaseConfig;
|
|
1688
|
+
const workspacesTable = getTable3(dbConfig, "workspaces");
|
|
1689
|
+
const { error } = await supabase.from(workspacesTable).update({ display_name: displayName.trim() }).eq("id", workspaceId);
|
|
1690
|
+
if (error) {
|
|
1691
|
+
console.error(`Error updating workspace display name for ${workspaceId}:`, error);
|
|
1692
|
+
throw error;
|
|
1693
|
+
}
|
|
1694
|
+
this.clearWorkspaceDisplayNamesCache();
|
|
1695
|
+
},
|
|
1677
1696
|
async updateWorkspaceAction(updates) {
|
|
1678
1697
|
const supabase = _getSupabaseInstance();
|
|
1679
1698
|
if (!supabase) throw new Error("Supabase client not initialized");
|
|
@@ -1817,6 +1836,209 @@ var workspaceService = {
|
|
|
1817
1836
|
}
|
|
1818
1837
|
}
|
|
1819
1838
|
};
|
|
1839
|
+
var WorkspaceHealthService = class _WorkspaceHealthService {
|
|
1840
|
+
constructor() {
|
|
1841
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
1842
|
+
this.cacheExpiryMs = 30 * 1e3;
|
|
1843
|
+
}
|
|
1844
|
+
// 30 seconds cache
|
|
1845
|
+
static getInstance() {
|
|
1846
|
+
if (!_WorkspaceHealthService.instance) {
|
|
1847
|
+
_WorkspaceHealthService.instance = new _WorkspaceHealthService();
|
|
1848
|
+
}
|
|
1849
|
+
return _WorkspaceHealthService.instance;
|
|
1850
|
+
}
|
|
1851
|
+
getFromCache(key) {
|
|
1852
|
+
const cached = this.cache.get(key);
|
|
1853
|
+
if (cached && Date.now() - cached.timestamp < this.cacheExpiryMs) {
|
|
1854
|
+
return cached.data;
|
|
1855
|
+
}
|
|
1856
|
+
this.cache.delete(key);
|
|
1857
|
+
return null;
|
|
1858
|
+
}
|
|
1859
|
+
setCache(key, data) {
|
|
1860
|
+
this.cache.set(key, { data, timestamp: Date.now() });
|
|
1861
|
+
}
|
|
1862
|
+
async getWorkspaceHealthStatus(options = {}) {
|
|
1863
|
+
const supabase = _getSupabaseInstance();
|
|
1864
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
1865
|
+
let query = supabase.from("workspace_health_status").select("*").order("workspace_display_name", { ascending: true });
|
|
1866
|
+
if (options.lineId) {
|
|
1867
|
+
query = query.eq("line_id", options.lineId);
|
|
1868
|
+
}
|
|
1869
|
+
if (options.companyId) {
|
|
1870
|
+
query = query.eq("company_id", options.companyId);
|
|
1871
|
+
}
|
|
1872
|
+
const { data, error } = await query;
|
|
1873
|
+
if (error) {
|
|
1874
|
+
console.error("Error fetching workspace health status:", error);
|
|
1875
|
+
throw error;
|
|
1876
|
+
}
|
|
1877
|
+
const processedData = (data || []).map((item) => this.processHealthStatus(item));
|
|
1878
|
+
let filteredData = processedData;
|
|
1879
|
+
try {
|
|
1880
|
+
const { data: enabledWorkspaces, error: workspaceError } = await supabase.from("workspaces").select("workspace_id, display_name").eq("enable", true);
|
|
1881
|
+
if (!workspaceError && enabledWorkspaces && enabledWorkspaces.length > 0) {
|
|
1882
|
+
const enabledWorkspaceNames = /* @__PURE__ */ new Set();
|
|
1883
|
+
enabledWorkspaces.forEach((w) => {
|
|
1884
|
+
if (w.workspace_id) enabledWorkspaceNames.add(w.workspace_id);
|
|
1885
|
+
if (w.display_name) enabledWorkspaceNames.add(w.display_name);
|
|
1886
|
+
});
|
|
1887
|
+
filteredData = filteredData.filter((item) => {
|
|
1888
|
+
const displayName = item.workspace_display_name || "";
|
|
1889
|
+
return enabledWorkspaceNames.has(displayName);
|
|
1890
|
+
});
|
|
1891
|
+
} else if (!workspaceError && enabledWorkspaces && enabledWorkspaces.length === 0) {
|
|
1892
|
+
return [];
|
|
1893
|
+
} else if (workspaceError) {
|
|
1894
|
+
console.error("Error fetching enabled workspaces:", workspaceError);
|
|
1895
|
+
}
|
|
1896
|
+
} catch (e) {
|
|
1897
|
+
console.error("Error filtering workspaces:", e);
|
|
1898
|
+
}
|
|
1899
|
+
if (options.status) {
|
|
1900
|
+
filteredData = filteredData.filter((item) => item.status === options.status);
|
|
1901
|
+
}
|
|
1902
|
+
if (options.searchTerm) {
|
|
1903
|
+
const searchLower = options.searchTerm.toLowerCase();
|
|
1904
|
+
filteredData = filteredData.filter(
|
|
1905
|
+
(item) => item.workspace_display_name?.toLowerCase().includes(searchLower) || item.line_name?.toLowerCase().includes(searchLower) || item.company_name?.toLowerCase().includes(searchLower)
|
|
1906
|
+
);
|
|
1907
|
+
}
|
|
1908
|
+
if (options.sortBy) {
|
|
1909
|
+
filteredData.sort((a, b) => {
|
|
1910
|
+
let compareValue = 0;
|
|
1911
|
+
switch (options.sortBy) {
|
|
1912
|
+
case "name":
|
|
1913
|
+
compareValue = (a.workspace_display_name || "").localeCompare(b.workspace_display_name || "");
|
|
1914
|
+
break;
|
|
1915
|
+
case "status":
|
|
1916
|
+
compareValue = this.getStatusPriority(a.status) - this.getStatusPriority(b.status);
|
|
1917
|
+
break;
|
|
1918
|
+
case "lastUpdate":
|
|
1919
|
+
compareValue = new Date(b.last_heartbeat).getTime() - new Date(a.last_heartbeat).getTime();
|
|
1920
|
+
break;
|
|
1921
|
+
}
|
|
1922
|
+
return options.sortOrder === "desc" ? -compareValue : compareValue;
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1925
|
+
return filteredData;
|
|
1926
|
+
}
|
|
1927
|
+
async getWorkspaceHealthById(workspaceId) {
|
|
1928
|
+
const cacheKey = `health-${workspaceId}`;
|
|
1929
|
+
const cached = this.getFromCache(cacheKey);
|
|
1930
|
+
if (cached) return cached;
|
|
1931
|
+
const supabase = _getSupabaseInstance();
|
|
1932
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
1933
|
+
const { data, error } = await supabase.from("workspace_health_status").select("*").eq("workspace_id", workspaceId).single();
|
|
1934
|
+
if (error) {
|
|
1935
|
+
if (error.code === "PGRST116") {
|
|
1936
|
+
return null;
|
|
1937
|
+
}
|
|
1938
|
+
console.error("Error fetching workspace health:", error);
|
|
1939
|
+
throw error;
|
|
1940
|
+
}
|
|
1941
|
+
const processedData = data ? this.processHealthStatus(data) : null;
|
|
1942
|
+
if (processedData) {
|
|
1943
|
+
this.setCache(cacheKey, processedData);
|
|
1944
|
+
}
|
|
1945
|
+
return processedData;
|
|
1946
|
+
}
|
|
1947
|
+
async getHealthSummary(lineId, companyId) {
|
|
1948
|
+
this.clearCache();
|
|
1949
|
+
const workspaces = await this.getWorkspaceHealthStatus({ lineId, companyId });
|
|
1950
|
+
const totalWorkspaces = workspaces.length;
|
|
1951
|
+
const healthyWorkspaces = workspaces.filter((w) => w.status === "healthy").length;
|
|
1952
|
+
const unhealthyWorkspaces = workspaces.filter((w) => w.status === "unhealthy").length;
|
|
1953
|
+
const warningWorkspaces = workspaces.filter((w) => w.status === "warning").length;
|
|
1954
|
+
const uptimePercentage = totalWorkspaces > 0 ? healthyWorkspaces / totalWorkspaces * 100 : 0;
|
|
1955
|
+
return {
|
|
1956
|
+
totalWorkspaces,
|
|
1957
|
+
healthyWorkspaces,
|
|
1958
|
+
unhealthyWorkspaces,
|
|
1959
|
+
warningWorkspaces,
|
|
1960
|
+
uptimePercentage,
|
|
1961
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
async getHealthMetrics(workspaceId, startDate, endDate) {
|
|
1965
|
+
const supabase = _getSupabaseInstance();
|
|
1966
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
1967
|
+
return {
|
|
1968
|
+
avgResponseTime: 250,
|
|
1969
|
+
totalDowntime: 0,
|
|
1970
|
+
incidentCount: 0,
|
|
1971
|
+
mttr: 0
|
|
1972
|
+
};
|
|
1973
|
+
}
|
|
1974
|
+
processHealthStatus(data) {
|
|
1975
|
+
const now2 = /* @__PURE__ */ new Date();
|
|
1976
|
+
const lastHeartbeat = new Date(data.last_heartbeat);
|
|
1977
|
+
const minutesSinceUpdate = Math.floor((now2.getTime() - lastHeartbeat.getTime()) / (1e3 * 60));
|
|
1978
|
+
let status = "unknown";
|
|
1979
|
+
let isStale = false;
|
|
1980
|
+
if (data.is_healthy) {
|
|
1981
|
+
if (minutesSinceUpdate < 3) {
|
|
1982
|
+
status = "healthy";
|
|
1983
|
+
} else if (minutesSinceUpdate < 5) {
|
|
1984
|
+
status = "warning";
|
|
1985
|
+
isStale = true;
|
|
1986
|
+
} else {
|
|
1987
|
+
status = "unhealthy";
|
|
1988
|
+
isStale = true;
|
|
1989
|
+
}
|
|
1990
|
+
} else {
|
|
1991
|
+
status = "unhealthy";
|
|
1992
|
+
if (minutesSinceUpdate > 5) {
|
|
1993
|
+
isStale = true;
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
const timeSinceLastUpdate = formatDistanceToNow(lastHeartbeat, { addSuffix: true });
|
|
1997
|
+
return {
|
|
1998
|
+
...data,
|
|
1999
|
+
status,
|
|
2000
|
+
timeSinceLastUpdate,
|
|
2001
|
+
isStale
|
|
2002
|
+
};
|
|
2003
|
+
}
|
|
2004
|
+
getStatusPriority(status) {
|
|
2005
|
+
const priorities = {
|
|
2006
|
+
unhealthy: 0,
|
|
2007
|
+
warning: 1,
|
|
2008
|
+
unknown: 2,
|
|
2009
|
+
healthy: 3
|
|
2010
|
+
};
|
|
2011
|
+
return priorities[status];
|
|
2012
|
+
}
|
|
2013
|
+
subscribeToHealthUpdates(callback, filters) {
|
|
2014
|
+
const supabase = _getSupabaseInstance();
|
|
2015
|
+
if (!supabase) throw new Error("Supabase client not initialized");
|
|
2016
|
+
let subscription = supabase.channel("workspace-health-updates").on(
|
|
2017
|
+
"postgres_changes",
|
|
2018
|
+
{
|
|
2019
|
+
event: "*",
|
|
2020
|
+
schema: "public",
|
|
2021
|
+
table: "workspace_health_status"
|
|
2022
|
+
},
|
|
2023
|
+
(payload) => {
|
|
2024
|
+
if (payload.new) {
|
|
2025
|
+
const newData = payload.new;
|
|
2026
|
+
if (filters?.lineId && newData.line_id !== filters.lineId) return;
|
|
2027
|
+
if (filters?.companyId && newData.company_id !== filters.companyId) return;
|
|
2028
|
+
this.clearCache();
|
|
2029
|
+
callback(newData);
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
).subscribe();
|
|
2033
|
+
return () => {
|
|
2034
|
+
subscription.unsubscribe();
|
|
2035
|
+
};
|
|
2036
|
+
}
|
|
2037
|
+
clearCache() {
|
|
2038
|
+
this.cache.clear();
|
|
2039
|
+
}
|
|
2040
|
+
};
|
|
2041
|
+
var workspaceHealthService = WorkspaceHealthService.getInstance();
|
|
1820
2042
|
|
|
1821
2043
|
// src/lib/services/skuService.ts
|
|
1822
2044
|
var getTable4 = (dbConfig, tableName) => {
|
|
@@ -3211,6 +3433,14 @@ function parseS3Uri(s3Uri, sopCategories) {
|
|
|
3211
3433
|
return null;
|
|
3212
3434
|
}
|
|
3213
3435
|
}
|
|
3436
|
+
function shuffleArray(array) {
|
|
3437
|
+
const shuffled = [...array];
|
|
3438
|
+
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
3439
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
3440
|
+
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
|
3441
|
+
}
|
|
3442
|
+
return shuffled;
|
|
3443
|
+
}
|
|
3214
3444
|
|
|
3215
3445
|
// src/lib/cache/clipsCache.ts
|
|
3216
3446
|
var LRUCache = class _LRUCache {
|
|
@@ -3723,300 +3953,321 @@ if (typeof window !== "undefined") {
|
|
|
3723
3953
|
});
|
|
3724
3954
|
});
|
|
3725
3955
|
}
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3956
|
+
var getSupabaseClient = () => {
|
|
3957
|
+
const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
3958
|
+
const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
|
3959
|
+
if (!url || !key) {
|
|
3960
|
+
throw new Error("Supabase configuration missing");
|
|
3961
|
+
}
|
|
3962
|
+
return createClient(url, key);
|
|
3963
|
+
};
|
|
3964
|
+
var getAuthToken = async () => {
|
|
3965
|
+
try {
|
|
3966
|
+
const supabase = getSupabaseClient();
|
|
3967
|
+
const { data: { session } } = await supabase.auth.getSession();
|
|
3968
|
+
return session?.access_token || null;
|
|
3969
|
+
} catch (error) {
|
|
3970
|
+
console.error("[S3ClipsAPIClient] Error getting auth token:", error);
|
|
3971
|
+
return null;
|
|
3972
|
+
}
|
|
3973
|
+
};
|
|
3974
|
+
var S3ClipsAPIClient = class {
|
|
3975
|
+
constructor(sopCategories) {
|
|
3976
|
+
this.baseUrl = "/api/clips";
|
|
3977
|
+
this.requestCache = /* @__PURE__ */ new Map();
|
|
3978
|
+
this.sopCategories = sopCategories;
|
|
3979
|
+
console.log("[S3ClipsAPIClient] \u2705 Initialized - Using secure API routes (no direct S3 access)");
|
|
3735
3980
|
}
|
|
3736
3981
|
/**
|
|
3737
|
-
*
|
|
3982
|
+
* Fetch with authentication and error handling
|
|
3738
3983
|
*/
|
|
3739
|
-
async
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3984
|
+
async fetchWithAuth(endpoint, body) {
|
|
3985
|
+
const token = await getAuthToken();
|
|
3986
|
+
if (!token) {
|
|
3987
|
+
throw new Error("Authentication required");
|
|
3988
|
+
}
|
|
3989
|
+
const response = await fetch(endpoint, {
|
|
3990
|
+
method: "POST",
|
|
3991
|
+
headers: {
|
|
3992
|
+
"Authorization": `Bearer ${token}`,
|
|
3993
|
+
"Content-Type": "application/json"
|
|
3994
|
+
},
|
|
3995
|
+
body: JSON.stringify(body)
|
|
3996
|
+
});
|
|
3997
|
+
if (!response.ok) {
|
|
3998
|
+
const error = await response.json().catch(() => ({ error: "Request failed" }));
|
|
3999
|
+
throw new Error(error.error || `API error: ${response.status}`);
|
|
4000
|
+
}
|
|
4001
|
+
return response.json();
|
|
4002
|
+
}
|
|
4003
|
+
/**
|
|
4004
|
+
* Deduplicate requests to prevent multiple API calls
|
|
4005
|
+
*/
|
|
4006
|
+
async deduplicate(key, factory) {
|
|
4007
|
+
if (this.requestCache.has(key)) {
|
|
4008
|
+
console.log(`[S3ClipsAPIClient] Deduplicating request: ${key}`);
|
|
4009
|
+
return this.requestCache.get(key);
|
|
3745
4010
|
}
|
|
3746
|
-
console.log(`[${logPrefix}] Creating new request for key: ${key}`);
|
|
3747
4011
|
const promise = factory().finally(() => {
|
|
3748
|
-
this.
|
|
3749
|
-
console.log(`[${logPrefix}] Completed and cleaned up request: ${key}`);
|
|
4012
|
+
this.requestCache.delete(key);
|
|
3750
4013
|
});
|
|
3751
|
-
this.
|
|
4014
|
+
this.requestCache.set(key, promise);
|
|
3752
4015
|
return promise;
|
|
3753
4016
|
}
|
|
3754
4017
|
/**
|
|
3755
|
-
*
|
|
4018
|
+
* List S3 clips
|
|
3756
4019
|
*/
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
this.
|
|
4020
|
+
async listS3Clips(params) {
|
|
4021
|
+
const cacheKey = `list:${JSON.stringify(params)}`;
|
|
4022
|
+
return this.deduplicate(cacheKey, async () => {
|
|
4023
|
+
const response = await this.fetchWithAuth(this.baseUrl, {
|
|
4024
|
+
action: "list",
|
|
4025
|
+
workspaceId: params.workspaceId,
|
|
4026
|
+
date: params.date,
|
|
4027
|
+
shift: params.shiftId,
|
|
4028
|
+
maxKeys: params.maxKeys
|
|
4029
|
+
});
|
|
4030
|
+
return response.clips.map((clip) => clip.originalUri);
|
|
4031
|
+
});
|
|
3760
4032
|
}
|
|
3761
4033
|
/**
|
|
3762
|
-
* Get
|
|
4034
|
+
* Get clip counts
|
|
3763
4035
|
*/
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
4036
|
+
async getClipCounts(workspaceId, date, shiftId) {
|
|
4037
|
+
const cacheKey = `counts:${workspaceId}:${date}:${shiftId}`;
|
|
4038
|
+
return this.deduplicate(cacheKey, async () => {
|
|
4039
|
+
const response = await this.fetchWithAuth(this.baseUrl, {
|
|
4040
|
+
action: "count",
|
|
4041
|
+
workspaceId,
|
|
4042
|
+
date,
|
|
4043
|
+
shift: shiftId.toString()
|
|
4044
|
+
});
|
|
4045
|
+
return response.counts;
|
|
4046
|
+
});
|
|
4047
|
+
}
|
|
4048
|
+
/**
|
|
4049
|
+
* Get clip counts with index (for compatibility)
|
|
4050
|
+
*/
|
|
4051
|
+
async getClipCountsWithIndex(workspaceId, date, shiftId) {
|
|
4052
|
+
const counts = await this.getClipCounts(workspaceId, date, shiftId.toString());
|
|
4053
|
+
const videoIndex = {
|
|
4054
|
+
byCategory: /* @__PURE__ */ new Map(),
|
|
4055
|
+
allVideos: [],
|
|
4056
|
+
counts,
|
|
4057
|
+
workspaceId,
|
|
4058
|
+
date,
|
|
4059
|
+
shiftId: shiftId.toString(),
|
|
4060
|
+
lastUpdated: /* @__PURE__ */ new Date()
|
|
3768
4061
|
};
|
|
4062
|
+
return { counts, videoIndex };
|
|
3769
4063
|
}
|
|
3770
4064
|
/**
|
|
3771
|
-
*
|
|
4065
|
+
* Get metadata for a video
|
|
3772
4066
|
*/
|
|
3773
|
-
|
|
3774
|
-
const
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
4067
|
+
async getMetadata(playlistUri) {
|
|
4068
|
+
const cacheKey = `metadata:${playlistUri}`;
|
|
4069
|
+
return this.deduplicate(cacheKey, async () => {
|
|
4070
|
+
const response = await this.fetchWithAuth(this.baseUrl, {
|
|
4071
|
+
action: "metadata",
|
|
4072
|
+
playlistUri
|
|
4073
|
+
});
|
|
4074
|
+
return response.metadata;
|
|
4075
|
+
});
|
|
4076
|
+
}
|
|
4077
|
+
/**
|
|
4078
|
+
* Get metadata cycle time
|
|
4079
|
+
*/
|
|
4080
|
+
async getMetadataCycleTime(playlistUri) {
|
|
4081
|
+
const metadata = await this.getMetadata(playlistUri);
|
|
4082
|
+
return metadata?.cycle_time_seconds || null;
|
|
4083
|
+
}
|
|
4084
|
+
/**
|
|
4085
|
+
* Get first clip for category
|
|
4086
|
+
*/
|
|
4087
|
+
async getFirstClipForCategory(workspaceId, date, shiftId, category) {
|
|
4088
|
+
const cacheKey = `first:${workspaceId}:${date}:${shiftId}:${category}`;
|
|
4089
|
+
return this.deduplicate(cacheKey, async () => {
|
|
4090
|
+
const response = await this.fetchWithAuth(this.baseUrl, {
|
|
4091
|
+
action: "first",
|
|
4092
|
+
workspaceId,
|
|
4093
|
+
date,
|
|
4094
|
+
shift: shiftId.toString(),
|
|
4095
|
+
category,
|
|
4096
|
+
sopCategories: this.sopCategories
|
|
4097
|
+
});
|
|
4098
|
+
return response.video;
|
|
4099
|
+
});
|
|
4100
|
+
}
|
|
4101
|
+
/**
|
|
4102
|
+
* Get clip by index
|
|
4103
|
+
*/
|
|
4104
|
+
async getClipByIndex(workspaceId, date, shiftId, category, index, includeCycleTime = true, includeMetadata = false) {
|
|
4105
|
+
const cacheKey = `by-index:${workspaceId}:${date}:${shiftId}:${category}:${index}`;
|
|
4106
|
+
return this.deduplicate(cacheKey, async () => {
|
|
4107
|
+
const response = await this.fetchWithAuth(this.baseUrl, {
|
|
4108
|
+
action: "by-index",
|
|
4109
|
+
workspaceId,
|
|
4110
|
+
date,
|
|
4111
|
+
shift: shiftId.toString(),
|
|
4112
|
+
category,
|
|
4113
|
+
index,
|
|
4114
|
+
sopCategories: this.sopCategories
|
|
4115
|
+
});
|
|
4116
|
+
const video = response.video;
|
|
4117
|
+
if (video && includeMetadata && video.originalUri) {
|
|
4118
|
+
try {
|
|
4119
|
+
const metadata = await this.getMetadata(video.originalUri);
|
|
4120
|
+
if (metadata) {
|
|
4121
|
+
video.cycle_time_seconds = metadata.cycle_time_seconds;
|
|
4122
|
+
video.creation_timestamp = metadata.creation_timestamp;
|
|
4123
|
+
}
|
|
4124
|
+
} catch (error) {
|
|
4125
|
+
console.warn("[S3ClipsAPIClient] Failed to fetch metadata:", error);
|
|
4126
|
+
}
|
|
3783
4127
|
}
|
|
3784
|
-
|
|
3785
|
-
}
|
|
4128
|
+
return video;
|
|
4129
|
+
});
|
|
4130
|
+
}
|
|
4131
|
+
/**
|
|
4132
|
+
* Get videos page with pagination
|
|
4133
|
+
*/
|
|
4134
|
+
async getVideosPage(workspaceId, date, shiftId, category, pageSize = 5, startAfter) {
|
|
4135
|
+
const cacheKey = `page:${workspaceId}:${date}:${shiftId}:${category}:${pageSize}:${startAfter || "first"}`;
|
|
4136
|
+
return this.deduplicate(cacheKey, async () => {
|
|
4137
|
+
const response = await this.fetchWithAuth(this.baseUrl, {
|
|
4138
|
+
action: "page",
|
|
4139
|
+
workspaceId,
|
|
4140
|
+
date,
|
|
4141
|
+
shift: shiftId.toString(),
|
|
4142
|
+
category,
|
|
4143
|
+
pageSize,
|
|
4144
|
+
startAfter,
|
|
4145
|
+
sopCategories: this.sopCategories
|
|
4146
|
+
});
|
|
4147
|
+
return {
|
|
4148
|
+
videos: response.videos,
|
|
4149
|
+
nextToken: response.nextToken,
|
|
4150
|
+
hasMore: response.hasMore
|
|
4151
|
+
};
|
|
4152
|
+
});
|
|
4153
|
+
}
|
|
4154
|
+
/**
|
|
4155
|
+
* Convert S3 URI to CloudFront URL
|
|
4156
|
+
* In the API client, URLs are already signed from the server
|
|
4157
|
+
*/
|
|
4158
|
+
s3UriToCloudfront(s3Uri) {
|
|
4159
|
+
return s3Uri;
|
|
4160
|
+
}
|
|
4161
|
+
/**
|
|
4162
|
+
* Clean up resources
|
|
4163
|
+
*/
|
|
4164
|
+
dispose() {
|
|
4165
|
+
this.requestCache.clear();
|
|
4166
|
+
}
|
|
4167
|
+
/**
|
|
4168
|
+
* Get service statistics
|
|
4169
|
+
*/
|
|
4170
|
+
getStats() {
|
|
4171
|
+
return {
|
|
4172
|
+
requestCache: {
|
|
4173
|
+
pendingCount: this.requestCache.size,
|
|
4174
|
+
maxSize: 1e3
|
|
4175
|
+
}
|
|
4176
|
+
};
|
|
3786
4177
|
}
|
|
3787
4178
|
};
|
|
4179
|
+
|
|
4180
|
+
// src/lib/api/s3-clips-secure.ts
|
|
3788
4181
|
var S3ClipsService = class {
|
|
3789
4182
|
constructor(config) {
|
|
3790
|
-
//
|
|
3791
|
-
this.requestCache = new RequestDeduplicationCache();
|
|
3792
|
-
// Flag to prevent metadata fetching during index building
|
|
4183
|
+
// Flags for compatibility
|
|
3793
4184
|
this.isIndexBuilding = false;
|
|
3794
|
-
// Flag to prevent metadata fetching during entire prefetch operation
|
|
3795
4185
|
this.isPrefetching = false;
|
|
3796
|
-
// Global safeguard: limit concurrent metadata fetches to prevent flooding
|
|
3797
4186
|
this.currentMetadataFetches = 0;
|
|
3798
4187
|
this.MAX_CONCURRENT_METADATA = 3;
|
|
3799
4188
|
this.config = config;
|
|
3800
4189
|
if (!config.s3Config) {
|
|
3801
4190
|
throw new Error("S3 configuration is required");
|
|
3802
4191
|
}
|
|
4192
|
+
const sopCategories = config.s3Config.sopCategories?.default;
|
|
4193
|
+
this.apiClient = new S3ClipsAPIClient(sopCategories);
|
|
3803
4194
|
const processing = config.s3Config.processing || {};
|
|
3804
4195
|
this.defaultLimitPerCategory = processing.defaultLimitPerCategory || 30;
|
|
3805
4196
|
this.maxLimitPerCategory = processing.maxLimitPerCategory || 1e3;
|
|
3806
4197
|
this.concurrencyLimit = processing.concurrencyLimit || 10;
|
|
3807
4198
|
this.maxInitialFetch = processing.maxInitialFetch || 60;
|
|
3808
|
-
|
|
3809
|
-
console.log(`S3ClipsService: Using AWS region: ${region}`);
|
|
3810
|
-
this.s3Client = new S3Client({
|
|
3811
|
-
region,
|
|
3812
|
-
credentials: config.s3Config.credentials ? {
|
|
3813
|
-
accessKeyId: config.s3Config.credentials.accessKeyId,
|
|
3814
|
-
secretAccessKey: config.s3Config.credentials.secretAccessKey
|
|
3815
|
-
} : void 0
|
|
3816
|
-
});
|
|
4199
|
+
console.log("[S3ClipsService] \u2705 Initialized with secure API client - AWS credentials are now server-side only!");
|
|
3817
4200
|
}
|
|
3818
4201
|
/**
|
|
3819
|
-
*
|
|
3820
|
-
*/
|
|
3821
|
-
validateAndSanitizeRegion(region) {
|
|
3822
|
-
const defaultRegion = "us-east-1";
|
|
3823
|
-
if (!region || typeof region !== "string") {
|
|
3824
|
-
console.warn(`S3ClipsService: Invalid region provided (${region}), using default: ${defaultRegion}`);
|
|
3825
|
-
return defaultRegion;
|
|
3826
|
-
}
|
|
3827
|
-
const sanitizedRegion = region.trim().toLowerCase();
|
|
3828
|
-
if (!sanitizedRegion) {
|
|
3829
|
-
console.warn(`S3ClipsService: Empty region provided, using default: ${defaultRegion}`);
|
|
3830
|
-
return defaultRegion;
|
|
3831
|
-
}
|
|
3832
|
-
const regionPattern = /^[a-z]{2,3}-[a-z]+-\d+$/;
|
|
3833
|
-
if (!regionPattern.test(sanitizedRegion)) {
|
|
3834
|
-
console.warn(`S3ClipsService: Invalid region format (${sanitizedRegion}), using default: ${defaultRegion}`);
|
|
3835
|
-
return defaultRegion;
|
|
3836
|
-
}
|
|
3837
|
-
return sanitizedRegion;
|
|
3838
|
-
}
|
|
3839
|
-
/**
|
|
3840
|
-
* Lists S3 clips for a workspace, date, and shift with request deduplication
|
|
4202
|
+
* Lists S3 clips using API
|
|
3841
4203
|
*/
|
|
3842
4204
|
async listS3Clips(params) {
|
|
3843
|
-
const { workspaceId, date, shiftId
|
|
4205
|
+
const { workspaceId, date, shiftId } = params;
|
|
3844
4206
|
if (!isValidShiftId(shiftId)) {
|
|
3845
|
-
console.error(`[S3ClipsService] Invalid shift ID: ${shiftId}
|
|
4207
|
+
console.error(`[S3ClipsService] Invalid shift ID: ${shiftId}`);
|
|
3846
4208
|
return [];
|
|
3847
4209
|
}
|
|
3848
|
-
|
|
3849
|
-
console.log(`[S3ClipsService] Listing clips for workspace: ${workspaceId}, date: ${date}, shift: ${shiftId}`);
|
|
3850
|
-
const deduplicationKey = `list-s3-clips:${prefix}:${maxKeys || "all"}`;
|
|
3851
|
-
return this.requestCache.deduplicate(
|
|
3852
|
-
deduplicationKey,
|
|
3853
|
-
() => this.executeListS3Clips(params),
|
|
3854
|
-
"ListS3Clips"
|
|
3855
|
-
);
|
|
3856
|
-
}
|
|
3857
|
-
/**
|
|
3858
|
-
* Internal implementation of S3 listing (called through deduplication)
|
|
3859
|
-
*/
|
|
3860
|
-
async executeListS3Clips(params) {
|
|
3861
|
-
const { workspaceId, date, shiftId, maxKeys } = params;
|
|
3862
|
-
const prefix = `sop_violations/${workspaceId}/${date}/${shiftId}/`;
|
|
3863
|
-
console.log(`[S3ClipsService] Executing S3 list for prefix: ${prefix}`);
|
|
4210
|
+
console.log(`[S3ClipsService] Listing clips via API for workspace: ${workspaceId}`);
|
|
3864
4211
|
try {
|
|
3865
|
-
|
|
3866
|
-
let continuationToken = void 0;
|
|
3867
|
-
do {
|
|
3868
|
-
const command = new ListObjectsV2Command({
|
|
3869
|
-
Bucket: this.config.s3Config.bucketName,
|
|
3870
|
-
Prefix: prefix,
|
|
3871
|
-
ContinuationToken: continuationToken,
|
|
3872
|
-
MaxKeys: maxKeys ?? 1e3
|
|
3873
|
-
});
|
|
3874
|
-
const response = await this.s3Client.send(command);
|
|
3875
|
-
console.log(`Got S3 response for ${prefix}:`, {
|
|
3876
|
-
keyCount: response.KeyCount,
|
|
3877
|
-
contentsLength: response.Contents?.length || 0,
|
|
3878
|
-
hasContinuation: !!response.NextContinuationToken
|
|
3879
|
-
});
|
|
3880
|
-
if (response.Contents) {
|
|
3881
|
-
if (response.Contents.length > 0) {
|
|
3882
|
-
console.log("Sample Keys:", response.Contents.slice(0, 3).map((obj) => obj.Key));
|
|
3883
|
-
}
|
|
3884
|
-
for (const obj of response.Contents) {
|
|
3885
|
-
if (obj.Key && obj.Key.endsWith("playlist.m3u8")) {
|
|
3886
|
-
if (obj.Key.includes("missed_qchecks")) {
|
|
3887
|
-
console.log(`Skipping missed_qchecks path: ${obj.Key}`);
|
|
3888
|
-
continue;
|
|
3889
|
-
}
|
|
3890
|
-
playlists.push(`s3://${this.config.s3Config.bucketName}/${obj.Key}`);
|
|
3891
|
-
}
|
|
3892
|
-
}
|
|
3893
|
-
}
|
|
3894
|
-
continuationToken = response.NextContinuationToken;
|
|
3895
|
-
if (maxKeys && playlists.length >= maxKeys) {
|
|
3896
|
-
break;
|
|
3897
|
-
}
|
|
3898
|
-
} while (continuationToken && (!maxKeys || playlists.length < maxKeys));
|
|
3899
|
-
console.log(`Found ${playlists.length} HLS playlists in ${prefix}`);
|
|
3900
|
-
if (playlists.length > 0) {
|
|
3901
|
-
console.log("First playlist URI:", playlists[0]);
|
|
3902
|
-
}
|
|
3903
|
-
return playlists;
|
|
4212
|
+
return await this.apiClient.listS3Clips(params);
|
|
3904
4213
|
} catch (error) {
|
|
3905
|
-
console.error(
|
|
4214
|
+
console.error("[S3ClipsService] Error listing clips:", error);
|
|
3906
4215
|
return [];
|
|
3907
4216
|
}
|
|
3908
4217
|
}
|
|
3909
4218
|
/**
|
|
3910
|
-
*
|
|
4219
|
+
* Get metadata cycle time
|
|
3911
4220
|
*/
|
|
3912
4221
|
async getMetadataCycleTime(playlistUri) {
|
|
3913
|
-
const deduplicationKey = `metadata-cycle-time:${playlistUri}`;
|
|
3914
|
-
return this.requestCache.deduplicate(
|
|
3915
|
-
deduplicationKey,
|
|
3916
|
-
() => this.executeGetMetadataCycleTime(playlistUri),
|
|
3917
|
-
"MetadataCycleTime"
|
|
3918
|
-
);
|
|
3919
|
-
}
|
|
3920
|
-
/**
|
|
3921
|
-
* Internal implementation of metadata cycle time fetching
|
|
3922
|
-
*/
|
|
3923
|
-
async executeGetMetadataCycleTime(playlistUri) {
|
|
3924
4222
|
try {
|
|
3925
|
-
|
|
3926
|
-
const url = new URL(metadataUri);
|
|
3927
|
-
const bucket = url.hostname;
|
|
3928
|
-
const key = url.pathname.substring(1);
|
|
3929
|
-
console.log(`[S3ClipsService] Fetching metadata cycle time for: ${key}`);
|
|
3930
|
-
const command = new GetObjectCommand({
|
|
3931
|
-
Bucket: bucket,
|
|
3932
|
-
Key: key
|
|
3933
|
-
});
|
|
3934
|
-
const response = await this.s3Client.send(command);
|
|
3935
|
-
if (!response.Body) {
|
|
3936
|
-
console.warn(`Empty response body for metadata file: ${key}`);
|
|
3937
|
-
return null;
|
|
3938
|
-
}
|
|
3939
|
-
const metadataContent = await response.Body.transformToString();
|
|
3940
|
-
const metadata = JSON.parse(metadataContent);
|
|
3941
|
-
const cycleTimeFrames = metadata?.original_task_metadata?.cycle_time;
|
|
3942
|
-
if (typeof cycleTimeFrames === "number") {
|
|
3943
|
-
return cycleTimeFrames;
|
|
3944
|
-
}
|
|
3945
|
-
return null;
|
|
4223
|
+
return await this.apiClient.getMetadataCycleTime(playlistUri);
|
|
3946
4224
|
} catch (error) {
|
|
3947
|
-
console.error(
|
|
4225
|
+
console.error("[S3ClipsService] Error fetching metadata cycle time:", error);
|
|
3948
4226
|
return null;
|
|
3949
4227
|
}
|
|
3950
4228
|
}
|
|
3951
4229
|
/**
|
|
3952
|
-
* Control prefetch mode
|
|
4230
|
+
* Control prefetch mode
|
|
3953
4231
|
*/
|
|
3954
4232
|
setPrefetchMode(enabled) {
|
|
3955
4233
|
this.isPrefetching = enabled;
|
|
3956
|
-
console.log(`[S3ClipsService] Prefetch mode ${enabled ? "enabled" : "disabled"}
|
|
4234
|
+
console.log(`[S3ClipsService] Prefetch mode ${enabled ? "enabled" : "disabled"}`);
|
|
3957
4235
|
}
|
|
3958
4236
|
/**
|
|
3959
|
-
*
|
|
4237
|
+
* Get full metadata
|
|
3960
4238
|
*/
|
|
3961
4239
|
async getFullMetadata(playlistUri) {
|
|
3962
4240
|
if (this.isIndexBuilding || this.isPrefetching) {
|
|
3963
|
-
console.warn(
|
|
4241
|
+
console.warn("[S3ClipsService] Skipping metadata - operation in progress");
|
|
3964
4242
|
return null;
|
|
3965
4243
|
}
|
|
3966
4244
|
if (this.currentMetadataFetches >= this.MAX_CONCURRENT_METADATA) {
|
|
3967
|
-
console.warn(
|
|
4245
|
+
console.warn("[S3ClipsService] Skipping metadata - max concurrent fetches reached");
|
|
3968
4246
|
return null;
|
|
3969
4247
|
}
|
|
3970
4248
|
this.currentMetadataFetches++;
|
|
3971
4249
|
try {
|
|
3972
|
-
|
|
3973
|
-
return await this.requestCache.deduplicate(
|
|
3974
|
-
deduplicationKey,
|
|
3975
|
-
() => this.executeGetFullMetadata(playlistUri),
|
|
3976
|
-
"FullMetadata"
|
|
3977
|
-
);
|
|
3978
|
-
} finally {
|
|
3979
|
-
this.currentMetadataFetches--;
|
|
3980
|
-
}
|
|
3981
|
-
}
|
|
3982
|
-
/**
|
|
3983
|
-
* Internal implementation of full metadata fetching
|
|
3984
|
-
*/
|
|
3985
|
-
async executeGetFullMetadata(playlistUri) {
|
|
3986
|
-
try {
|
|
3987
|
-
const metadataUri = playlistUri.replace(/playlist\.m3u8$/, "metadata.json");
|
|
3988
|
-
const url = new URL(metadataUri);
|
|
3989
|
-
const bucket = url.hostname;
|
|
3990
|
-
const key = url.pathname.substring(1);
|
|
3991
|
-
console.log(`[S3ClipsService] Fetching full metadata for: ${key}`);
|
|
3992
|
-
const command = new GetObjectCommand({
|
|
3993
|
-
Bucket: bucket,
|
|
3994
|
-
Key: key
|
|
3995
|
-
});
|
|
3996
|
-
const response = await this.s3Client.send(command);
|
|
3997
|
-
if (!response.Body) {
|
|
3998
|
-
console.warn(`Empty response body for metadata file: ${key}`);
|
|
3999
|
-
return null;
|
|
4000
|
-
}
|
|
4001
|
-
const metadataContent = await response.Body.transformToString();
|
|
4002
|
-
const metadata = JSON.parse(metadataContent);
|
|
4003
|
-
return metadata;
|
|
4250
|
+
return await this.apiClient.getMetadata(playlistUri);
|
|
4004
4251
|
} catch (error) {
|
|
4005
|
-
console.error(
|
|
4252
|
+
console.error("[S3ClipsService] Error fetching metadata:", error);
|
|
4006
4253
|
return null;
|
|
4254
|
+
} finally {
|
|
4255
|
+
this.currentMetadataFetches--;
|
|
4007
4256
|
}
|
|
4008
4257
|
}
|
|
4009
4258
|
/**
|
|
4010
|
-
*
|
|
4259
|
+
* Convert S3 URI to CloudFront URL
|
|
4260
|
+
* URLs from API are already signed
|
|
4011
4261
|
*/
|
|
4012
4262
|
s3UriToCloudfront(s3Uri) {
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4263
|
+
if (s3Uri.startsWith("http")) {
|
|
4264
|
+
return s3Uri;
|
|
4265
|
+
}
|
|
4266
|
+
console.warn("[S3ClipsService] Unexpected S3 URI in secure mode:", s3Uri);
|
|
4267
|
+
return s3Uri;
|
|
4017
4268
|
}
|
|
4018
4269
|
/**
|
|
4019
|
-
*
|
|
4270
|
+
* Get SOP categories for workspace
|
|
4020
4271
|
*/
|
|
4021
4272
|
getSOPCategories(workspaceId) {
|
|
4022
4273
|
const sopConfig = this.config.s3Config?.sopCategories;
|
|
@@ -4028,299 +4279,85 @@ var S3ClipsService = class {
|
|
|
4028
4279
|
}
|
|
4029
4280
|
async getClipCounts(workspaceId, date, shiftId, buildIndex) {
|
|
4030
4281
|
if (!isValidShiftId(shiftId)) {
|
|
4031
|
-
console.error(`[S3ClipsService]
|
|
4032
|
-
return buildIndex ? { counts: {}, videoIndex:
|
|
4033
|
-
}
|
|
4034
|
-
const deduplicationKey = `clip-counts:${workspaceId}:${date}:${shiftId}:${buildIndex ? "with-index" : "counts-only"}`;
|
|
4035
|
-
return this.requestCache.deduplicate(
|
|
4036
|
-
deduplicationKey,
|
|
4037
|
-
() => this.executeGetClipCounts(workspaceId, date, shiftId, buildIndex),
|
|
4038
|
-
"ClipCounts"
|
|
4039
|
-
);
|
|
4040
|
-
}
|
|
4041
|
-
/**
|
|
4042
|
-
* Internal implementation of clip counts fetching
|
|
4043
|
-
*/
|
|
4044
|
-
async executeGetClipCounts(workspaceId, date, shiftId, buildIndex) {
|
|
4045
|
-
const effectiveBuildIndex = false;
|
|
4282
|
+
console.error(`[S3ClipsService] Invalid shift ID: ${shiftId}`);
|
|
4283
|
+
return buildIndex ? { counts: {}, videoIndex: this.createEmptyVideoIndex(workspaceId, date, shiftId.toString()) } : {};
|
|
4284
|
+
}
|
|
4046
4285
|
try {
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
];
|
|
4060
|
-
const shiftName = shiftId === 0 || shiftId === "0" ? "Day" : "Night";
|
|
4061
|
-
console.log(`[S3ClipsService] Fast counting clips for ${workspaceId} on ${date}, shift ${shiftId} (${shiftName} Shift)`);
|
|
4062
|
-
const startTime = performance.now();
|
|
4063
|
-
const videoIndex = effectiveBuildIndex ? {
|
|
4064
|
-
byCategory: /* @__PURE__ */ new Map(),
|
|
4065
|
-
allVideos: [],
|
|
4066
|
-
counts: {},
|
|
4067
|
-
workspaceId,
|
|
4068
|
-
date,
|
|
4069
|
-
shiftId: shiftId.toString(),
|
|
4070
|
-
lastUpdated: /* @__PURE__ */ new Date(),
|
|
4071
|
-
_debugId: `vid_${Date.now()}_${Math.random().toString(36).substring(7)}`
|
|
4072
|
-
} : null;
|
|
4073
|
-
const countPromises = categoryFolders.map(async (category) => {
|
|
4074
|
-
const categoryPrefix = `${basePrefix}${category}/videos/`;
|
|
4075
|
-
const categoryVideos = [];
|
|
4076
|
-
try {
|
|
4077
|
-
if (effectiveBuildIndex) ; else {
|
|
4078
|
-
const command = new ListObjectsV2Command({
|
|
4079
|
-
Bucket: this.config.s3Config.bucketName,
|
|
4080
|
-
Prefix: categoryPrefix,
|
|
4081
|
-
Delimiter: "/",
|
|
4082
|
-
MaxKeys: 1e3
|
|
4083
|
-
});
|
|
4084
|
-
let folderCount = 0;
|
|
4085
|
-
let continuationToken;
|
|
4086
|
-
do {
|
|
4087
|
-
if (continuationToken) {
|
|
4088
|
-
command.input.ContinuationToken = continuationToken;
|
|
4089
|
-
}
|
|
4090
|
-
const response = await this.s3Client.send(command);
|
|
4091
|
-
if (response.CommonPrefixes) {
|
|
4092
|
-
folderCount += response.CommonPrefixes.length;
|
|
4093
|
-
}
|
|
4094
|
-
continuationToken = response.NextContinuationToken;
|
|
4095
|
-
} while (continuationToken);
|
|
4096
|
-
return { category, count: folderCount, videos: [] };
|
|
4097
|
-
}
|
|
4098
|
-
} catch (error) {
|
|
4099
|
-
console.error(`Error ${buildIndex ? "building index for" : "counting folders for"} category ${category}:`, error);
|
|
4100
|
-
return { category, count: 0, videos: [] };
|
|
4101
|
-
}
|
|
4102
|
-
});
|
|
4103
|
-
const results = await Promise.all(countPromises);
|
|
4104
|
-
for (const { category, count, videos } of results) {
|
|
4105
|
-
counts[category] = count;
|
|
4106
|
-
counts.total += count;
|
|
4107
|
-
if (effectiveBuildIndex && videoIndex && videos) ;
|
|
4108
|
-
}
|
|
4109
|
-
if (effectiveBuildIndex && videoIndex) ;
|
|
4110
|
-
const elapsed = performance.now() - startTime;
|
|
4111
|
-
console.log(`[S3ClipsService] Clip counts completed in ${elapsed.toFixed(2)}ms - Total: ${counts.total}`);
|
|
4112
|
-
if (effectiveBuildIndex && videoIndex) ;
|
|
4113
|
-
return counts;
|
|
4114
|
-
} finally {
|
|
4286
|
+
if (buildIndex) {
|
|
4287
|
+
const result = await this.apiClient.getClipCountsWithIndex(workspaceId, date, shiftId);
|
|
4288
|
+
const cacheKey = `clip-counts:${workspaceId}:${date}:${shiftId}`;
|
|
4289
|
+
await smartVideoCache.setClipCounts(cacheKey, result);
|
|
4290
|
+
return result;
|
|
4291
|
+
} else {
|
|
4292
|
+
const counts = await this.apiClient.getClipCounts(workspaceId, date, shiftId);
|
|
4293
|
+
return counts;
|
|
4294
|
+
}
|
|
4295
|
+
} catch (error) {
|
|
4296
|
+
console.error("[S3ClipsService] Error fetching clip counts:", error);
|
|
4297
|
+
return buildIndex ? { counts: {}, videoIndex: this.createEmptyVideoIndex(workspaceId, date, shiftId.toString()) } : {};
|
|
4115
4298
|
}
|
|
4116
4299
|
}
|
|
4117
4300
|
async getClipCountsCacheFirst(workspaceId, date, shiftId, buildIndex) {
|
|
4118
4301
|
const cacheKey = `clip-counts:${workspaceId}:${date}:${shiftId}`;
|
|
4119
4302
|
const cachedResult = await smartVideoCache.getClipCounts(cacheKey);
|
|
4120
4303
|
if (cachedResult) {
|
|
4121
|
-
console.log(
|
|
4122
|
-
|
|
4123
|
-
return cachedResult;
|
|
4124
|
-
} else {
|
|
4125
|
-
return cachedResult.counts;
|
|
4126
|
-
}
|
|
4304
|
+
console.log("[S3ClipsService] Using cached clip counts");
|
|
4305
|
+
return buildIndex ? cachedResult : cachedResult.counts;
|
|
4127
4306
|
}
|
|
4128
|
-
console.log(
|
|
4307
|
+
console.log("[S3ClipsService] Cache miss - fetching from API");
|
|
4129
4308
|
return buildIndex ? this.getClipCounts(workspaceId, date, shiftId, true) : this.getClipCounts(workspaceId, date, shiftId);
|
|
4130
4309
|
}
|
|
4131
4310
|
/**
|
|
4132
|
-
* Get first clip for
|
|
4311
|
+
* Get first clip for category
|
|
4133
4312
|
*/
|
|
4134
4313
|
async getFirstClipForCategory(workspaceId, date, shiftId, category) {
|
|
4135
4314
|
if (!isValidShiftId(shiftId)) {
|
|
4136
|
-
console.error(`[S3ClipsService]
|
|
4315
|
+
console.error(`[S3ClipsService] Invalid shift ID: ${shiftId}`);
|
|
4137
4316
|
return null;
|
|
4138
4317
|
}
|
|
4139
|
-
const deduplicationKey = `first-clip:${workspaceId}:${date}:${shiftId}:${category}`;
|
|
4140
|
-
return this.requestCache.deduplicate(
|
|
4141
|
-
deduplicationKey,
|
|
4142
|
-
() => this.executeGetFirstClipForCategory(workspaceId, date, shiftId, category),
|
|
4143
|
-
"FirstClip"
|
|
4144
|
-
);
|
|
4145
|
-
}
|
|
4146
|
-
/**
|
|
4147
|
-
* Internal implementation of first clip fetching
|
|
4148
|
-
*/
|
|
4149
|
-
async executeGetFirstClipForCategory(workspaceId, date, shiftId, category) {
|
|
4150
|
-
const categoryPrefix = `sop_violations/${workspaceId}/${date}/${shiftId}/${category}/videos/`;
|
|
4151
4318
|
try {
|
|
4152
|
-
|
|
4153
|
-
Bucket: this.config.s3Config.bucketName,
|
|
4154
|
-
Prefix: categoryPrefix,
|
|
4155
|
-
MaxKeys: 10
|
|
4156
|
-
// Small limit since we only need one
|
|
4157
|
-
});
|
|
4158
|
-
const response = await this.s3Client.send(command);
|
|
4159
|
-
if (response.Contents) {
|
|
4160
|
-
const playlistObj = response.Contents.find((obj) => obj.Key?.endsWith("playlist.m3u8"));
|
|
4161
|
-
if (playlistObj && playlistObj.Key) {
|
|
4162
|
-
const s3Uri = `s3://${this.config.s3Config.bucketName}/${playlistObj.Key}`;
|
|
4163
|
-
const sopCategories = this.getSOPCategories(workspaceId);
|
|
4164
|
-
const parsedInfo = parseS3Uri(s3Uri, sopCategories);
|
|
4165
|
-
if (parsedInfo) {
|
|
4166
|
-
const cloudfrontUrl = this.s3UriToCloudfront(s3Uri);
|
|
4167
|
-
return {
|
|
4168
|
-
id: `${workspaceId}-${date}-${shiftId}-${category}-first`,
|
|
4169
|
-
src: cloudfrontUrl,
|
|
4170
|
-
...parsedInfo,
|
|
4171
|
-
originalUri: s3Uri
|
|
4172
|
-
};
|
|
4173
|
-
}
|
|
4174
|
-
}
|
|
4175
|
-
}
|
|
4319
|
+
return await this.apiClient.getFirstClipForCategory(workspaceId, date, shiftId, category);
|
|
4176
4320
|
} catch (error) {
|
|
4177
|
-
console.error(
|
|
4321
|
+
console.error("[S3ClipsService] Error fetching first clip:", error);
|
|
4322
|
+
return null;
|
|
4178
4323
|
}
|
|
4179
|
-
return null;
|
|
4180
4324
|
}
|
|
4181
4325
|
/**
|
|
4182
|
-
* Get
|
|
4326
|
+
* Get video from index (for compatibility)
|
|
4183
4327
|
*/
|
|
4184
4328
|
async getVideoFromIndex(videoIndex, category, index, includeCycleTime = true, includeMetadata = true) {
|
|
4329
|
+
const { workspaceId, date, shiftId } = videoIndex;
|
|
4330
|
+
return this.getClipByIndex(
|
|
4331
|
+
workspaceId,
|
|
4332
|
+
date,
|
|
4333
|
+
shiftId,
|
|
4334
|
+
category,
|
|
4335
|
+
index,
|
|
4336
|
+
includeCycleTime,
|
|
4337
|
+
includeMetadata
|
|
4338
|
+
);
|
|
4339
|
+
}
|
|
4340
|
+
/**
|
|
4341
|
+
* Get clip by index
|
|
4342
|
+
*/
|
|
4343
|
+
async getClipByIndex(workspaceId, date, shiftId, category, index, includeCycleTime = true, includeMetadata = false) {
|
|
4185
4344
|
try {
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
console.warn(`Video index total videos: ${videoIndex.allVideos.length}`);
|
|
4192
|
-
return null;
|
|
4193
|
-
}
|
|
4194
|
-
const videoEntry = categoryVideos[index];
|
|
4195
|
-
return this.processFullVideo(
|
|
4196
|
-
videoEntry.uri,
|
|
4345
|
+
return await this.apiClient.getClipByIndex(
|
|
4346
|
+
workspaceId,
|
|
4347
|
+
date,
|
|
4348
|
+
shiftId,
|
|
4349
|
+
category,
|
|
4197
4350
|
index,
|
|
4198
|
-
videoEntry.workspaceId,
|
|
4199
|
-
videoEntry.date,
|
|
4200
|
-
videoEntry.shiftId,
|
|
4201
4351
|
includeCycleTime,
|
|
4202
4352
|
includeMetadata
|
|
4203
4353
|
);
|
|
4204
4354
|
} catch (error) {
|
|
4205
|
-
console.error(
|
|
4355
|
+
console.error("[S3ClipsService] Error fetching clip by index:", error);
|
|
4206
4356
|
return null;
|
|
4207
4357
|
}
|
|
4208
4358
|
}
|
|
4209
4359
|
/**
|
|
4210
|
-
*
|
|
4211
|
-
* @deprecated Use getVideoFromIndex with a pre-built VideoIndex for better performance
|
|
4212
|
-
*/
|
|
4213
|
-
async getClipByIndex(workspaceId, date, shiftId, category, index, includeCycleTime = true, includeMetadata = false) {
|
|
4214
|
-
const deduplicationKey = `clip-by-index:${workspaceId}:${date}:${shiftId}:${category}:${index}:${includeCycleTime}:${includeMetadata}`;
|
|
4215
|
-
return this.requestCache.deduplicate(
|
|
4216
|
-
deduplicationKey,
|
|
4217
|
-
() => this.executeGetClipByIndex(workspaceId, date, shiftId, category, index, includeCycleTime, includeMetadata),
|
|
4218
|
-
"ClipByIndex"
|
|
4219
|
-
);
|
|
4220
|
-
}
|
|
4221
|
-
/**
|
|
4222
|
-
* Internal implementation of clip by index fetching
|
|
4223
|
-
*/
|
|
4224
|
-
async executeGetClipByIndex(workspaceId, date, shiftId, category, index, includeCycleTime = true, includeMetadata = false) {
|
|
4225
|
-
const categoryPrefix = `sop_violations/${workspaceId}/${date}/${shiftId}/${category}/videos/`;
|
|
4226
|
-
try {
|
|
4227
|
-
const command = new ListObjectsV2Command({
|
|
4228
|
-
Bucket: this.config.s3Config.bucketName,
|
|
4229
|
-
Prefix: categoryPrefix,
|
|
4230
|
-
MaxKeys: 1e3
|
|
4231
|
-
});
|
|
4232
|
-
let playlists = [];
|
|
4233
|
-
let continuationToken = void 0;
|
|
4234
|
-
do {
|
|
4235
|
-
if (continuationToken) {
|
|
4236
|
-
command.input.ContinuationToken = continuationToken;
|
|
4237
|
-
}
|
|
4238
|
-
const response = await this.s3Client.send(command);
|
|
4239
|
-
if (response.Contents) {
|
|
4240
|
-
for (const obj of response.Contents) {
|
|
4241
|
-
if (obj.Key && obj.Key.endsWith("playlist.m3u8")) {
|
|
4242
|
-
playlists.push(`s3://${this.config.s3Config.bucketName}/${obj.Key}`);
|
|
4243
|
-
}
|
|
4244
|
-
}
|
|
4245
|
-
}
|
|
4246
|
-
continuationToken = response.NextContinuationToken;
|
|
4247
|
-
} while (continuationToken && playlists.length < index + 10);
|
|
4248
|
-
playlists.sort((a, b) => {
|
|
4249
|
-
const parsedA = parseS3Uri(a);
|
|
4250
|
-
const parsedB = parseS3Uri(b);
|
|
4251
|
-
if (!parsedA || !parsedB) return 0;
|
|
4252
|
-
return parsedB.timestamp.localeCompare(parsedA.timestamp);
|
|
4253
|
-
});
|
|
4254
|
-
if (index < playlists.length) {
|
|
4255
|
-
const uri = playlists[index];
|
|
4256
|
-
return this.processFullVideo(
|
|
4257
|
-
uri,
|
|
4258
|
-
index,
|
|
4259
|
-
workspaceId,
|
|
4260
|
-
date,
|
|
4261
|
-
shiftId.toString(),
|
|
4262
|
-
includeCycleTime,
|
|
4263
|
-
includeMetadata
|
|
4264
|
-
);
|
|
4265
|
-
}
|
|
4266
|
-
} catch (error) {
|
|
4267
|
-
console.error(`[getClipByIndex] Error getting clip at index ${index} for category ${category}:`, error);
|
|
4268
|
-
}
|
|
4269
|
-
return null;
|
|
4270
|
-
}
|
|
4271
|
-
/**
|
|
4272
|
-
* Get one sample video from each category for preloading
|
|
4273
|
-
*/
|
|
4274
|
-
async getSampleVideos(workspaceId, date, shiftId) {
|
|
4275
|
-
const basePrefix = `sop_violations/${workspaceId}/${date}/${shiftId}/`;
|
|
4276
|
-
const samples = {};
|
|
4277
|
-
const sopCategories = this.getSOPCategories(workspaceId);
|
|
4278
|
-
const categoriesToSample = sopCategories ? sopCategories.map((cat) => cat.id) : ["low_value", "best_cycle_time", "worst_cycle_time", "long_cycle_time", "cycle_completion"];
|
|
4279
|
-
console.log(`[S3ClipsService] Getting sample videos for categories:`, categoriesToSample);
|
|
4280
|
-
const samplePromises = categoriesToSample.map(async (category) => {
|
|
4281
|
-
const categoryPrefix = `${basePrefix}${category}/`;
|
|
4282
|
-
try {
|
|
4283
|
-
const command = new ListObjectsV2Command({
|
|
4284
|
-
Bucket: this.config.s3Config.bucketName,
|
|
4285
|
-
Prefix: categoryPrefix,
|
|
4286
|
-
MaxKeys: 20
|
|
4287
|
-
// Small limit since we only need one
|
|
4288
|
-
});
|
|
4289
|
-
const response = await this.s3Client.send(command);
|
|
4290
|
-
if (response.Contents) {
|
|
4291
|
-
const playlistObj = response.Contents.find((obj) => obj.Key?.endsWith("playlist.m3u8"));
|
|
4292
|
-
if (playlistObj && playlistObj.Key) {
|
|
4293
|
-
const s3Uri = `s3://${this.config.s3Config.bucketName}/${playlistObj.Key}`;
|
|
4294
|
-
const parsedInfo = parseS3Uri(s3Uri, sopCategories);
|
|
4295
|
-
if (parsedInfo) {
|
|
4296
|
-
return {
|
|
4297
|
-
category,
|
|
4298
|
-
sample: {
|
|
4299
|
-
id: `${workspaceId}-${date}-${shiftId}-${category}-sample`,
|
|
4300
|
-
src: this.s3UriToCloudfront(s3Uri),
|
|
4301
|
-
// Pre-convert to CloudFront URL
|
|
4302
|
-
...parsedInfo,
|
|
4303
|
-
originalUri: s3Uri
|
|
4304
|
-
}
|
|
4305
|
-
};
|
|
4306
|
-
}
|
|
4307
|
-
}
|
|
4308
|
-
}
|
|
4309
|
-
} catch (error) {
|
|
4310
|
-
console.error(`Error getting sample for category ${category}:`, error);
|
|
4311
|
-
}
|
|
4312
|
-
return { category, sample: null };
|
|
4313
|
-
});
|
|
4314
|
-
const sampleResults = await Promise.all(samplePromises);
|
|
4315
|
-
for (const { category, sample } of sampleResults) {
|
|
4316
|
-
if (sample) {
|
|
4317
|
-
samples[category] = sample;
|
|
4318
|
-
}
|
|
4319
|
-
}
|
|
4320
|
-
return samples;
|
|
4321
|
-
}
|
|
4322
|
-
/**
|
|
4323
|
-
* Processes a single video completely
|
|
4360
|
+
* Process full video (for compatibility)
|
|
4324
4361
|
*/
|
|
4325
4362
|
async processFullVideo(uri, index, workspaceId, date, shiftId, includeCycleTime, includeMetadata = false) {
|
|
4326
4363
|
const sopCategories = this.getSOPCategories(workspaceId);
|
|
@@ -4329,35 +4366,35 @@ var S3ClipsService = class {
|
|
|
4329
4366
|
console.warn(`Skipping URI due to parsing failure: ${uri}`);
|
|
4330
4367
|
return null;
|
|
4331
4368
|
}
|
|
4332
|
-
|
|
4333
|
-
|
|
4369
|
+
const video = {
|
|
4370
|
+
id: `${workspaceId}-${date}-${shiftId}-${index}`,
|
|
4371
|
+
src: uri,
|
|
4372
|
+
// Already signed from API
|
|
4373
|
+
...parsedInfo,
|
|
4374
|
+
originalUri: uri
|
|
4375
|
+
};
|
|
4334
4376
|
if (includeMetadata) {
|
|
4335
4377
|
const metadata = await this.getFullMetadata(uri);
|
|
4336
4378
|
if (metadata) {
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
}
|
|
4340
|
-
creationTimestamp = metadata.upload_timestamp || metadata.original_task_metadata?.timestamp || metadata.creation_timestamp || metadata[""];
|
|
4379
|
+
video.cycle_time_seconds = metadata.original_task_metadata?.cycle_time;
|
|
4380
|
+
video.creation_timestamp = metadata.upload_timestamp || metadata.original_task_metadata?.timestamp || metadata.creation_timestamp;
|
|
4341
4381
|
}
|
|
4342
|
-
} 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")) {
|
|
4343
|
-
cycleTimeSeconds = null;
|
|
4344
4382
|
}
|
|
4345
|
-
|
|
4346
|
-
const { type: videoType, timestamp: videoTimestamp } = parsedInfo;
|
|
4347
|
-
return {
|
|
4348
|
-
id: `${workspaceId}-${date}-${shiftId}-${videoType}-${videoTimestamp.replace(/:/g, "")}-${index}`,
|
|
4349
|
-
src: cloudfrontPlaylistUrl,
|
|
4350
|
-
// Direct CloudFront playlist URL
|
|
4351
|
-
...parsedInfo,
|
|
4352
|
-
cycle_time_seconds: cycleTimeSeconds || void 0,
|
|
4353
|
-
creation_timestamp: creationTimestamp
|
|
4354
|
-
};
|
|
4383
|
+
return video;
|
|
4355
4384
|
}
|
|
4356
4385
|
/**
|
|
4357
|
-
*
|
|
4386
|
+
* Fetch clips (main method for compatibility)
|
|
4358
4387
|
*/
|
|
4359
4388
|
async fetchClips(params) {
|
|
4360
|
-
const {
|
|
4389
|
+
const {
|
|
4390
|
+
workspaceId,
|
|
4391
|
+
date: inputDate,
|
|
4392
|
+
shift,
|
|
4393
|
+
category,
|
|
4394
|
+
limit,
|
|
4395
|
+
offset = 0,
|
|
4396
|
+
mode
|
|
4397
|
+
} = params;
|
|
4361
4398
|
if (!workspaceId) {
|
|
4362
4399
|
throw new Error("Valid Workspace ID is required");
|
|
4363
4400
|
}
|
|
@@ -4375,157 +4412,66 @@ var S3ClipsService = class {
|
|
|
4375
4412
|
const { shiftId: currentShiftId } = getCurrentShift2(this.config.dateTimeConfig?.defaultTimezone);
|
|
4376
4413
|
shiftId = currentShiftId;
|
|
4377
4414
|
}
|
|
4378
|
-
console.log(`S3ClipsService
|
|
4415
|
+
console.log(`[S3ClipsService] Fetching clips for workspace ${workspaceId}`);
|
|
4379
4416
|
if (mode === "summary") {
|
|
4380
4417
|
const counts = await this.getClipCounts(workspaceId, date, shiftId.toString());
|
|
4381
4418
|
return { counts, samples: {} };
|
|
4382
4419
|
}
|
|
4383
4420
|
const effectiveLimit = limit || this.defaultLimitPerCategory;
|
|
4384
|
-
const
|
|
4421
|
+
const result = await this.getVideosPage(
|
|
4385
4422
|
workspaceId,
|
|
4386
4423
|
date,
|
|
4387
4424
|
shiftId,
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
if (!parsedInfo) return false;
|
|
4400
|
-
if (category === "long_cycle_time") {
|
|
4401
|
-
return parsedInfo.type === "long_cycle_time" || parsedInfo.type === "bottleneck" && parsedInfo.description.toLowerCase().includes("cycle time");
|
|
4402
|
-
}
|
|
4403
|
-
return parsedInfo.type === category;
|
|
4404
|
-
});
|
|
4405
|
-
filteredUris = filteredUris.slice(offset, offset + effectiveLimit);
|
|
4406
|
-
}
|
|
4407
|
-
const videoPromises = filteredUris.map(async (uri, index) => {
|
|
4408
|
-
return this.processFullVideo(
|
|
4409
|
-
uri,
|
|
4410
|
-
index,
|
|
4425
|
+
category || "all",
|
|
4426
|
+
effectiveLimit
|
|
4427
|
+
);
|
|
4428
|
+
return result.videos;
|
|
4429
|
+
}
|
|
4430
|
+
/**
|
|
4431
|
+
* Get videos page using pagination API
|
|
4432
|
+
*/
|
|
4433
|
+
async getVideosPage(workspaceId, date, shiftId, category, pageSize = 5, startAfter) {
|
|
4434
|
+
try {
|
|
4435
|
+
return await this.apiClient.getVideosPage(
|
|
4411
4436
|
workspaceId,
|
|
4412
4437
|
date,
|
|
4413
|
-
shiftId
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4438
|
+
shiftId,
|
|
4439
|
+
category,
|
|
4440
|
+
pageSize,
|
|
4441
|
+
startAfter
|
|
4417
4442
|
);
|
|
4418
|
-
})
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
if (timestampStart || timestampEnd) {
|
|
4422
|
-
videos = videos.filter((video) => {
|
|
4423
|
-
if (!video.creation_timestamp) return false;
|
|
4424
|
-
const videoTimestamp = new Date(video.creation_timestamp).getTime();
|
|
4425
|
-
if (timestampStart && timestampEnd) {
|
|
4426
|
-
const start = new Date(timestampStart).getTime();
|
|
4427
|
-
const end = new Date(timestampEnd).getTime();
|
|
4428
|
-
return videoTimestamp >= start && videoTimestamp <= end;
|
|
4429
|
-
} else if (timestampStart) {
|
|
4430
|
-
const start = new Date(timestampStart).getTime();
|
|
4431
|
-
return videoTimestamp >= start;
|
|
4432
|
-
} else if (timestampEnd) {
|
|
4433
|
-
const end = new Date(timestampEnd).getTime();
|
|
4434
|
-
return videoTimestamp <= end;
|
|
4435
|
-
}
|
|
4436
|
-
return true;
|
|
4437
|
-
});
|
|
4438
|
-
}
|
|
4439
|
-
videos.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
4440
|
-
const cacheKey = `videos:${workspaceId}:${date}:${shiftId}:${category || "all"}`;
|
|
4441
|
-
if (videos.length > 0) {
|
|
4442
|
-
smartVideoCache.setVideos(cacheKey, videos);
|
|
4443
|
+
} catch (error) {
|
|
4444
|
+
console.error("[S3ClipsService] Error fetching videos page:", error);
|
|
4445
|
+
return { videos: [], hasMore: false };
|
|
4443
4446
|
}
|
|
4444
|
-
return videos;
|
|
4445
4447
|
}
|
|
4446
4448
|
/**
|
|
4447
|
-
*
|
|
4448
|
-
* This method replaces the need for full video indexing
|
|
4449
|
-
* @param workspaceId - Workspace ID
|
|
4450
|
-
* @param date - Date in YYYY-MM-DD format
|
|
4451
|
-
* @param shiftId - Shift ID (0 for day, 1 for night)
|
|
4452
|
-
* @param category - Category to fetch videos from
|
|
4453
|
-
* @param pageSize - Number of videos to fetch per page (default 5)
|
|
4454
|
-
* @param startAfter - Optional key to start after for pagination
|
|
4455
|
-
* @returns Page of videos with continuation token
|
|
4449
|
+
* Create empty video index for compatibility
|
|
4456
4450
|
*/
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
console.log(`[S3ClipsService] Fetching page of ${pageSize} videos for category '${category}'`);
|
|
4469
|
-
const command = new ListObjectsV2Command({
|
|
4470
|
-
Bucket: this.config.s3Config.bucketName,
|
|
4471
|
-
Prefix: categoryPrefix,
|
|
4472
|
-
MaxKeys: pageSize * 10,
|
|
4473
|
-
// Fetch extra to account for non-playlist files
|
|
4474
|
-
StartAfter: startAfter
|
|
4475
|
-
});
|
|
4476
|
-
const response = await this.s3Client.send(command);
|
|
4477
|
-
const videos = [];
|
|
4478
|
-
if (response.Contents) {
|
|
4479
|
-
const sopCategories = this.getSOPCategories(workspaceId);
|
|
4480
|
-
for (const obj of response.Contents) {
|
|
4481
|
-
if (videos.length >= pageSize) break;
|
|
4482
|
-
if (obj.Key && obj.Key.endsWith("playlist.m3u8")) {
|
|
4483
|
-
if (obj.Key.includes("missed_qchecks")) continue;
|
|
4484
|
-
const s3Uri = `s3://${this.config.s3Config.bucketName}/${obj.Key}`;
|
|
4485
|
-
const parsedInfo = parseS3Uri(s3Uri, sopCategories);
|
|
4486
|
-
if (parsedInfo) {
|
|
4487
|
-
const cloudfrontUrl = this.s3UriToCloudfront(s3Uri);
|
|
4488
|
-
videos.push({
|
|
4489
|
-
id: `${workspaceId}-${date}-${shiftId}-${category}-${videos.length}`,
|
|
4490
|
-
src: cloudfrontUrl,
|
|
4491
|
-
...parsedInfo,
|
|
4492
|
-
originalUri: s3Uri
|
|
4493
|
-
});
|
|
4494
|
-
}
|
|
4495
|
-
}
|
|
4496
|
-
}
|
|
4497
|
-
const lastKey = response.Contents[response.Contents.length - 1]?.Key;
|
|
4498
|
-
const hasMore = !!response.IsTruncated || response.Contents.length === pageSize * 10;
|
|
4499
|
-
console.log(`[S3ClipsService] Fetched ${videos.length} videos, hasMore: ${hasMore}`);
|
|
4500
|
-
return {
|
|
4501
|
-
videos,
|
|
4502
|
-
nextToken: hasMore ? lastKey : void 0,
|
|
4503
|
-
hasMore
|
|
4504
|
-
};
|
|
4505
|
-
}
|
|
4506
|
-
return { videos: [], hasMore: false };
|
|
4507
|
-
} catch (error) {
|
|
4508
|
-
console.error(`[S3ClipsService] Error fetching videos page:`, error);
|
|
4509
|
-
return { videos: [], hasMore: false };
|
|
4510
|
-
}
|
|
4511
|
-
},
|
|
4512
|
-
"VideosPage"
|
|
4513
|
-
);
|
|
4451
|
+
createEmptyVideoIndex(workspaceId, date, shiftId) {
|
|
4452
|
+
return {
|
|
4453
|
+
byCategory: /* @__PURE__ */ new Map(),
|
|
4454
|
+
allVideos: [],
|
|
4455
|
+
counts: {},
|
|
4456
|
+
workspaceId,
|
|
4457
|
+
date,
|
|
4458
|
+
shiftId,
|
|
4459
|
+
lastUpdated: /* @__PURE__ */ new Date(),
|
|
4460
|
+
_debugId: `empty_${Date.now()}`
|
|
4461
|
+
};
|
|
4514
4462
|
}
|
|
4515
4463
|
/**
|
|
4516
|
-
* Cleanup
|
|
4464
|
+
* Cleanup
|
|
4517
4465
|
*/
|
|
4518
4466
|
dispose() {
|
|
4519
|
-
console.log("[S3ClipsService] Disposing service
|
|
4520
|
-
this.
|
|
4467
|
+
console.log("[S3ClipsService] Disposing service");
|
|
4468
|
+
this.apiClient.dispose();
|
|
4521
4469
|
}
|
|
4522
4470
|
/**
|
|
4523
|
-
* Get
|
|
4471
|
+
* Get statistics
|
|
4524
4472
|
*/
|
|
4525
4473
|
getStats() {
|
|
4526
|
-
return
|
|
4527
|
-
requestCache: this.requestCache.getStats()
|
|
4528
|
-
};
|
|
4474
|
+
return this.apiClient.getStats();
|
|
4529
4475
|
}
|
|
4530
4476
|
};
|
|
4531
4477
|
|
|
@@ -12114,6 +12060,140 @@ function useWorkspaceNavigation() {
|
|
|
12114
12060
|
getWorkspaceNavigationParams: getWorkspaceNavigationParams2
|
|
12115
12061
|
};
|
|
12116
12062
|
}
|
|
12063
|
+
function useWorkspaceHealth(options = {}) {
|
|
12064
|
+
const [workspaces, setWorkspaces] = useState([]);
|
|
12065
|
+
const [summary, setSummary] = useState(null);
|
|
12066
|
+
const [loading, setLoading] = useState(true);
|
|
12067
|
+
const [error, setError] = useState(null);
|
|
12068
|
+
const unsubscribeRef = useRef(null);
|
|
12069
|
+
const refreshIntervalRef = useRef(null);
|
|
12070
|
+
const {
|
|
12071
|
+
enableRealtime = true,
|
|
12072
|
+
refreshInterval = 3e4,
|
|
12073
|
+
// 30 seconds default
|
|
12074
|
+
...filterOptions
|
|
12075
|
+
} = options;
|
|
12076
|
+
const fetchData = useCallback(async () => {
|
|
12077
|
+
try {
|
|
12078
|
+
setError(null);
|
|
12079
|
+
workspaceHealthService.clearCache();
|
|
12080
|
+
const [workspacesData, summaryData] = await Promise.all([
|
|
12081
|
+
workspaceHealthService.getWorkspaceHealthStatus(filterOptions),
|
|
12082
|
+
workspaceHealthService.getHealthSummary(filterOptions.lineId, filterOptions.companyId)
|
|
12083
|
+
]);
|
|
12084
|
+
setWorkspaces(workspacesData);
|
|
12085
|
+
setSummary(summaryData);
|
|
12086
|
+
} catch (err) {
|
|
12087
|
+
console.error("Error fetching workspace health:", err);
|
|
12088
|
+
setError(err);
|
|
12089
|
+
} finally {
|
|
12090
|
+
setLoading(false);
|
|
12091
|
+
}
|
|
12092
|
+
}, [filterOptions.lineId, filterOptions.companyId, filterOptions.status, filterOptions.searchTerm, filterOptions.sortBy, filterOptions.sortOrder]);
|
|
12093
|
+
const handleRealtimeUpdate = useCallback(async (data) => {
|
|
12094
|
+
try {
|
|
12095
|
+
const [workspacesData, summaryData] = await Promise.all([
|
|
12096
|
+
workspaceHealthService.getWorkspaceHealthStatus(filterOptions),
|
|
12097
|
+
workspaceHealthService.getHealthSummary(filterOptions.lineId, filterOptions.companyId)
|
|
12098
|
+
]);
|
|
12099
|
+
setWorkspaces(workspacesData);
|
|
12100
|
+
setSummary(summaryData);
|
|
12101
|
+
} catch (err) {
|
|
12102
|
+
console.error("Error updating real-time health data:", err);
|
|
12103
|
+
}
|
|
12104
|
+
}, [filterOptions]);
|
|
12105
|
+
useEffect(() => {
|
|
12106
|
+
fetchData();
|
|
12107
|
+
if (refreshInterval > 0) {
|
|
12108
|
+
refreshIntervalRef.current = setInterval(fetchData, 1e4);
|
|
12109
|
+
}
|
|
12110
|
+
if (enableRealtime) {
|
|
12111
|
+
unsubscribeRef.current = workspaceHealthService.subscribeToHealthUpdates(
|
|
12112
|
+
handleRealtimeUpdate,
|
|
12113
|
+
{ lineId: filterOptions.lineId, companyId: filterOptions.companyId }
|
|
12114
|
+
);
|
|
12115
|
+
}
|
|
12116
|
+
return () => {
|
|
12117
|
+
if (refreshIntervalRef.current) {
|
|
12118
|
+
clearInterval(refreshIntervalRef.current);
|
|
12119
|
+
}
|
|
12120
|
+
if (unsubscribeRef.current) {
|
|
12121
|
+
unsubscribeRef.current();
|
|
12122
|
+
}
|
|
12123
|
+
};
|
|
12124
|
+
}, [fetchData, enableRealtime, refreshInterval, handleRealtimeUpdate, filterOptions.lineId, filterOptions.companyId]);
|
|
12125
|
+
return {
|
|
12126
|
+
workspaces,
|
|
12127
|
+
summary,
|
|
12128
|
+
loading,
|
|
12129
|
+
error,
|
|
12130
|
+
refetch: fetchData
|
|
12131
|
+
};
|
|
12132
|
+
}
|
|
12133
|
+
function useWorkspaceHealthById(workspaceId, options = {}) {
|
|
12134
|
+
const [workspace, setWorkspace] = useState(null);
|
|
12135
|
+
const [metrics2, setMetrics] = useState(null);
|
|
12136
|
+
const [loading, setLoading] = useState(true);
|
|
12137
|
+
const [error, setError] = useState(null);
|
|
12138
|
+
const unsubscribeRef = useRef(null);
|
|
12139
|
+
const refreshIntervalRef = useRef(null);
|
|
12140
|
+
const { enableRealtime = true, refreshInterval = 3e4 } = options;
|
|
12141
|
+
const fetchData = useCallback(async () => {
|
|
12142
|
+
if (!workspaceId) {
|
|
12143
|
+
setWorkspace(null);
|
|
12144
|
+
setMetrics(null);
|
|
12145
|
+
setLoading(false);
|
|
12146
|
+
return;
|
|
12147
|
+
}
|
|
12148
|
+
try {
|
|
12149
|
+
setLoading(true);
|
|
12150
|
+
setError(null);
|
|
12151
|
+
const [workspaceData, metricsData] = await Promise.all([
|
|
12152
|
+
workspaceHealthService.getWorkspaceHealthById(workspaceId),
|
|
12153
|
+
workspaceHealthService.getHealthMetrics(workspaceId)
|
|
12154
|
+
]);
|
|
12155
|
+
setWorkspace(workspaceData);
|
|
12156
|
+
setMetrics(metricsData);
|
|
12157
|
+
} catch (err) {
|
|
12158
|
+
console.error("Error fetching workspace health by ID:", err);
|
|
12159
|
+
setError(err);
|
|
12160
|
+
} finally {
|
|
12161
|
+
setLoading(false);
|
|
12162
|
+
}
|
|
12163
|
+
}, [workspaceId]);
|
|
12164
|
+
const handleRealtimeUpdate = useCallback((data) => {
|
|
12165
|
+
if (data.workspace_id === workspaceId) {
|
|
12166
|
+
const updatedWorkspace = workspaceHealthService["processHealthStatus"](data);
|
|
12167
|
+
setWorkspace(updatedWorkspace);
|
|
12168
|
+
}
|
|
12169
|
+
}, [workspaceId]);
|
|
12170
|
+
useEffect(() => {
|
|
12171
|
+
fetchData();
|
|
12172
|
+
if (refreshInterval > 0) {
|
|
12173
|
+
refreshIntervalRef.current = setInterval(fetchData, 1e4);
|
|
12174
|
+
}
|
|
12175
|
+
if (enableRealtime && workspaceId) {
|
|
12176
|
+
unsubscribeRef.current = workspaceHealthService.subscribeToHealthUpdates(
|
|
12177
|
+
handleRealtimeUpdate
|
|
12178
|
+
);
|
|
12179
|
+
}
|
|
12180
|
+
return () => {
|
|
12181
|
+
if (refreshIntervalRef.current) {
|
|
12182
|
+
clearInterval(refreshIntervalRef.current);
|
|
12183
|
+
}
|
|
12184
|
+
if (unsubscribeRef.current) {
|
|
12185
|
+
unsubscribeRef.current();
|
|
12186
|
+
}
|
|
12187
|
+
};
|
|
12188
|
+
}, [fetchData, enableRealtime, refreshInterval, handleRealtimeUpdate, workspaceId]);
|
|
12189
|
+
return {
|
|
12190
|
+
workspace,
|
|
12191
|
+
metrics: metrics2,
|
|
12192
|
+
loading,
|
|
12193
|
+
error,
|
|
12194
|
+
refetch: fetchData
|
|
12195
|
+
};
|
|
12196
|
+
}
|
|
12117
12197
|
function useDateFormatter() {
|
|
12118
12198
|
const { defaultTimezone, defaultLocale, dateFormatOptions, timeFormatOptions, dateTimeFormatOptions } = useDateTimeConfig();
|
|
12119
12199
|
const formatDate = useCallback(
|
|
@@ -23522,6 +23602,165 @@ var TicketHistory = ({ companyId }) => {
|
|
|
23522
23602
|
] });
|
|
23523
23603
|
};
|
|
23524
23604
|
var TicketHistory_default = TicketHistory;
|
|
23605
|
+
var HealthStatusIndicator = ({
|
|
23606
|
+
status,
|
|
23607
|
+
lastUpdated,
|
|
23608
|
+
showLabel = false,
|
|
23609
|
+
showTime = true,
|
|
23610
|
+
size = "md",
|
|
23611
|
+
className = "",
|
|
23612
|
+
inline = true,
|
|
23613
|
+
pulse = true
|
|
23614
|
+
}) => {
|
|
23615
|
+
const getStatusConfig = () => {
|
|
23616
|
+
switch (status) {
|
|
23617
|
+
case "healthy":
|
|
23618
|
+
return {
|
|
23619
|
+
color: "text-green-500",
|
|
23620
|
+
bgColor: "bg-green-500",
|
|
23621
|
+
borderColor: "border-green-500",
|
|
23622
|
+
label: "Healthy",
|
|
23623
|
+
icon: CheckCircle,
|
|
23624
|
+
shouldPulse: pulse
|
|
23625
|
+
};
|
|
23626
|
+
case "unhealthy":
|
|
23627
|
+
return {
|
|
23628
|
+
color: "text-red-500",
|
|
23629
|
+
bgColor: "bg-red-500",
|
|
23630
|
+
borderColor: "border-red-500",
|
|
23631
|
+
label: "Unhealthy",
|
|
23632
|
+
icon: XCircle,
|
|
23633
|
+
shouldPulse: false
|
|
23634
|
+
};
|
|
23635
|
+
case "warning":
|
|
23636
|
+
return {
|
|
23637
|
+
color: "text-yellow-500",
|
|
23638
|
+
bgColor: "bg-yellow-500",
|
|
23639
|
+
borderColor: "border-yellow-500",
|
|
23640
|
+
label: "Warning",
|
|
23641
|
+
icon: AlertTriangle,
|
|
23642
|
+
shouldPulse: true
|
|
23643
|
+
};
|
|
23644
|
+
case "unknown":
|
|
23645
|
+
default:
|
|
23646
|
+
return {
|
|
23647
|
+
color: "text-gray-400",
|
|
23648
|
+
bgColor: "bg-gray-400",
|
|
23649
|
+
borderColor: "border-gray-400",
|
|
23650
|
+
label: "Unknown",
|
|
23651
|
+
icon: Activity,
|
|
23652
|
+
shouldPulse: false
|
|
23653
|
+
};
|
|
23654
|
+
}
|
|
23655
|
+
};
|
|
23656
|
+
const config = getStatusConfig();
|
|
23657
|
+
config.icon;
|
|
23658
|
+
const sizeClasses = {
|
|
23659
|
+
sm: {
|
|
23660
|
+
dot: "h-2 w-2",
|
|
23661
|
+
icon: "h-3 w-3",
|
|
23662
|
+
text: "text-xs",
|
|
23663
|
+
spacing: "gap-1"
|
|
23664
|
+
},
|
|
23665
|
+
md: {
|
|
23666
|
+
dot: "h-3 w-3",
|
|
23667
|
+
icon: "h-4 w-4",
|
|
23668
|
+
text: "text-sm",
|
|
23669
|
+
spacing: "gap-1.5"
|
|
23670
|
+
},
|
|
23671
|
+
lg: {
|
|
23672
|
+
dot: "h-4 w-4",
|
|
23673
|
+
icon: "h-5 w-5",
|
|
23674
|
+
text: "text-base",
|
|
23675
|
+
spacing: "gap-2"
|
|
23676
|
+
}
|
|
23677
|
+
};
|
|
23678
|
+
const currentSize = sizeClasses[size];
|
|
23679
|
+
const containerClasses = clsx(
|
|
23680
|
+
"flex items-center",
|
|
23681
|
+
currentSize.spacing,
|
|
23682
|
+
inline ? "inline-flex" : "flex",
|
|
23683
|
+
className
|
|
23684
|
+
);
|
|
23685
|
+
const dotClasses = clsx(
|
|
23686
|
+
"rounded-full",
|
|
23687
|
+
currentSize.dot,
|
|
23688
|
+
config.bgColor,
|
|
23689
|
+
config.shouldPulse && "animate-pulse"
|
|
23690
|
+
);
|
|
23691
|
+
return /* @__PURE__ */ jsxs("div", { className: containerClasses, children: [
|
|
23692
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex items-center justify-center", children: [
|
|
23693
|
+
/* @__PURE__ */ jsx("div", { className: dotClasses }),
|
|
23694
|
+
config.shouldPulse && status === "healthy" && /* @__PURE__ */ jsx(
|
|
23695
|
+
"div",
|
|
23696
|
+
{
|
|
23697
|
+
className: clsx(
|
|
23698
|
+
"absolute rounded-full opacity-25",
|
|
23699
|
+
currentSize.dot,
|
|
23700
|
+
config.bgColor,
|
|
23701
|
+
"animate-ping"
|
|
23702
|
+
)
|
|
23703
|
+
}
|
|
23704
|
+
)
|
|
23705
|
+
] }),
|
|
23706
|
+
showLabel && /* @__PURE__ */ jsx("span", { className: clsx(currentSize.text, "font-medium", config.color), children: config.label }),
|
|
23707
|
+
showTime && lastUpdated && /* @__PURE__ */ jsx("span", { className: clsx(currentSize.text, "text-gray-500 dark:text-gray-400"), children: lastUpdated })
|
|
23708
|
+
] });
|
|
23709
|
+
};
|
|
23710
|
+
var DetailedHealthStatus = ({
|
|
23711
|
+
workspaceName,
|
|
23712
|
+
lineName,
|
|
23713
|
+
consecutiveMisses,
|
|
23714
|
+
showDetails = true,
|
|
23715
|
+
...indicatorProps
|
|
23716
|
+
}) => {
|
|
23717
|
+
const getStatusConfig = () => {
|
|
23718
|
+
switch (indicatorProps.status) {
|
|
23719
|
+
case "healthy":
|
|
23720
|
+
return {
|
|
23721
|
+
bgClass: "bg-green-50 dark:bg-green-900/20",
|
|
23722
|
+
borderClass: "border-green-200 dark:border-green-800"
|
|
23723
|
+
};
|
|
23724
|
+
case "unhealthy":
|
|
23725
|
+
return {
|
|
23726
|
+
bgClass: "bg-red-50 dark:bg-red-900/20",
|
|
23727
|
+
borderClass: "border-red-200 dark:border-red-800"
|
|
23728
|
+
};
|
|
23729
|
+
case "warning":
|
|
23730
|
+
return {
|
|
23731
|
+
bgClass: "bg-yellow-50 dark:bg-yellow-900/20",
|
|
23732
|
+
borderClass: "border-yellow-200 dark:border-yellow-800"
|
|
23733
|
+
};
|
|
23734
|
+
default:
|
|
23735
|
+
return {
|
|
23736
|
+
bgClass: "bg-gray-50 dark:bg-gray-900/20",
|
|
23737
|
+
borderClass: "border-gray-200 dark:border-gray-800"
|
|
23738
|
+
};
|
|
23739
|
+
}
|
|
23740
|
+
};
|
|
23741
|
+
const config = getStatusConfig();
|
|
23742
|
+
return /* @__PURE__ */ jsx(
|
|
23743
|
+
"div",
|
|
23744
|
+
{
|
|
23745
|
+
className: clsx(
|
|
23746
|
+
"rounded-lg border p-3",
|
|
23747
|
+
config.bgClass,
|
|
23748
|
+
config.borderClass
|
|
23749
|
+
),
|
|
23750
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between", children: [
|
|
23751
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
|
|
23752
|
+
showDetails && workspaceName && /* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: workspaceName }),
|
|
23753
|
+
showDetails && lineName && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-0.5", children: lineName }),
|
|
23754
|
+
/* @__PURE__ */ jsx("div", { className: "mt-2", children: /* @__PURE__ */ jsx(HealthStatusIndicator, { ...indicatorProps, showLabel: true }) })
|
|
23755
|
+
] }),
|
|
23756
|
+
showDetails && consecutiveMisses !== void 0 && consecutiveMisses > 0 && /* @__PURE__ */ jsxs("div", { className: "ml-3 text-right", children: [
|
|
23757
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-500 dark:text-gray-400", children: "Missed" }),
|
|
23758
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-bold text-gray-900 dark:text-gray-100", children: consecutiveMisses })
|
|
23759
|
+
] })
|
|
23760
|
+
] })
|
|
23761
|
+
}
|
|
23762
|
+
);
|
|
23763
|
+
};
|
|
23525
23764
|
var LinePdfExportButton = ({
|
|
23526
23765
|
targetElement,
|
|
23527
23766
|
fileName = "line-export",
|
|
@@ -24644,6 +24883,8 @@ var WorkspaceCard = ({
|
|
|
24644
24883
|
cycleTime,
|
|
24645
24884
|
operators,
|
|
24646
24885
|
status = "normal",
|
|
24886
|
+
healthStatus,
|
|
24887
|
+
healthLastUpdated,
|
|
24647
24888
|
onCardClick,
|
|
24648
24889
|
headerActions,
|
|
24649
24890
|
footerContent,
|
|
@@ -24736,6 +24977,19 @@ var WorkspaceCard = ({
|
|
|
24736
24977
|
] })
|
|
24737
24978
|
] })
|
|
24738
24979
|
] }),
|
|
24980
|
+
healthStatus && /* @__PURE__ */ jsx("div", { className: "pt-2 mt-auto border-t dark:border-gray-700", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
24981
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: "System Health" }),
|
|
24982
|
+
/* @__PURE__ */ jsx(
|
|
24983
|
+
HealthStatusIndicator,
|
|
24984
|
+
{
|
|
24985
|
+
status: healthStatus,
|
|
24986
|
+
lastUpdated: healthLastUpdated,
|
|
24987
|
+
showTime: false,
|
|
24988
|
+
size: "sm",
|
|
24989
|
+
pulse: healthStatus === "healthy"
|
|
24990
|
+
}
|
|
24991
|
+
)
|
|
24992
|
+
] }) }),
|
|
24739
24993
|
chartData && chartData.data && chartData.data.length > 0 && /* @__PURE__ */ jsxs(
|
|
24740
24994
|
"div",
|
|
24741
24995
|
{
|
|
@@ -26394,6 +26648,194 @@ var BackButtonMinimal = ({
|
|
|
26394
26648
|
}
|
|
26395
26649
|
);
|
|
26396
26650
|
};
|
|
26651
|
+
var InlineEditableText = ({
|
|
26652
|
+
value,
|
|
26653
|
+
onSave,
|
|
26654
|
+
placeholder = "Click to edit",
|
|
26655
|
+
className = "",
|
|
26656
|
+
editIconClassName = "",
|
|
26657
|
+
inputClassName = "",
|
|
26658
|
+
debounceDelay = 750,
|
|
26659
|
+
// 750ms for quick auto-save
|
|
26660
|
+
disabled = false
|
|
26661
|
+
}) => {
|
|
26662
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
26663
|
+
const [editValue, setEditValue] = useState(value);
|
|
26664
|
+
const [saveStatus, setSaveStatus] = useState("idle");
|
|
26665
|
+
const [lastSavedValue, setLastSavedValue] = useState(value);
|
|
26666
|
+
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
|
26667
|
+
const inputRef = useRef(null);
|
|
26668
|
+
const containerRef = useRef(null);
|
|
26669
|
+
const debounceTimeout = useRef(void 0);
|
|
26670
|
+
const saveStatusTimeout = useRef(void 0);
|
|
26671
|
+
useEffect(() => {
|
|
26672
|
+
if (!isEditing) {
|
|
26673
|
+
setEditValue(value);
|
|
26674
|
+
setLastSavedValue(value);
|
|
26675
|
+
}
|
|
26676
|
+
}, [value, isEditing]);
|
|
26677
|
+
useEffect(() => {
|
|
26678
|
+
if (isEditing && inputRef.current) {
|
|
26679
|
+
requestAnimationFrame(() => {
|
|
26680
|
+
inputRef.current?.focus();
|
|
26681
|
+
inputRef.current?.select();
|
|
26682
|
+
});
|
|
26683
|
+
}
|
|
26684
|
+
}, [isEditing]);
|
|
26685
|
+
useEffect(() => {
|
|
26686
|
+
return () => {
|
|
26687
|
+
if (debounceTimeout.current) clearTimeout(debounceTimeout.current);
|
|
26688
|
+
if (saveStatusTimeout.current) clearTimeout(saveStatusTimeout.current);
|
|
26689
|
+
};
|
|
26690
|
+
}, []);
|
|
26691
|
+
const performSave = useCallback(async (valueToSave, shouldClose = false) => {
|
|
26692
|
+
const trimmedValue = valueToSave.trim();
|
|
26693
|
+
if (trimmedValue === lastSavedValue.trim()) {
|
|
26694
|
+
setHasUnsavedChanges(false);
|
|
26695
|
+
if (shouldClose) {
|
|
26696
|
+
setIsEditing(false);
|
|
26697
|
+
setSaveStatus("idle");
|
|
26698
|
+
}
|
|
26699
|
+
return;
|
|
26700
|
+
}
|
|
26701
|
+
setSaveStatus("saving");
|
|
26702
|
+
setHasUnsavedChanges(false);
|
|
26703
|
+
try {
|
|
26704
|
+
await onSave(trimmedValue);
|
|
26705
|
+
setLastSavedValue(trimmedValue);
|
|
26706
|
+
setSaveStatus("saved");
|
|
26707
|
+
if (!shouldClose && inputRef.current) {
|
|
26708
|
+
requestAnimationFrame(() => {
|
|
26709
|
+
inputRef.current?.focus();
|
|
26710
|
+
});
|
|
26711
|
+
}
|
|
26712
|
+
if (saveStatusTimeout.current) clearTimeout(saveStatusTimeout.current);
|
|
26713
|
+
saveStatusTimeout.current = setTimeout(() => {
|
|
26714
|
+
setSaveStatus("idle");
|
|
26715
|
+
}, 2e3);
|
|
26716
|
+
if (shouldClose) {
|
|
26717
|
+
setIsEditing(false);
|
|
26718
|
+
}
|
|
26719
|
+
} catch (error) {
|
|
26720
|
+
console.error("Failed to save:", error);
|
|
26721
|
+
setSaveStatus("error");
|
|
26722
|
+
setHasUnsavedChanges(true);
|
|
26723
|
+
if (saveStatusTimeout.current) clearTimeout(saveStatusTimeout.current);
|
|
26724
|
+
saveStatusTimeout.current = setTimeout(() => {
|
|
26725
|
+
setSaveStatus("idle");
|
|
26726
|
+
}, 3e3);
|
|
26727
|
+
if (!shouldClose && inputRef.current) {
|
|
26728
|
+
requestAnimationFrame(() => {
|
|
26729
|
+
inputRef.current?.focus();
|
|
26730
|
+
});
|
|
26731
|
+
}
|
|
26732
|
+
}
|
|
26733
|
+
}, [lastSavedValue, onSave]);
|
|
26734
|
+
useEffect(() => {
|
|
26735
|
+
const handleClickOutside = (event) => {
|
|
26736
|
+
if (isEditing && containerRef.current && !containerRef.current.contains(event.target)) {
|
|
26737
|
+
if (debounceTimeout.current) {
|
|
26738
|
+
clearTimeout(debounceTimeout.current);
|
|
26739
|
+
}
|
|
26740
|
+
performSave(editValue, true);
|
|
26741
|
+
}
|
|
26742
|
+
};
|
|
26743
|
+
if (isEditing) {
|
|
26744
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
26745
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
26746
|
+
}
|
|
26747
|
+
}, [isEditing, editValue, performSave]);
|
|
26748
|
+
const handleInputChange = (e) => {
|
|
26749
|
+
const newValue = e.target.value;
|
|
26750
|
+
setEditValue(newValue);
|
|
26751
|
+
if (newValue.trim() !== lastSavedValue.trim()) {
|
|
26752
|
+
setHasUnsavedChanges(true);
|
|
26753
|
+
setSaveStatus("idle");
|
|
26754
|
+
} else {
|
|
26755
|
+
setHasUnsavedChanges(false);
|
|
26756
|
+
}
|
|
26757
|
+
if (debounceTimeout.current) {
|
|
26758
|
+
clearTimeout(debounceTimeout.current);
|
|
26759
|
+
}
|
|
26760
|
+
if (newValue.trim() !== lastSavedValue.trim()) {
|
|
26761
|
+
debounceTimeout.current = setTimeout(() => {
|
|
26762
|
+
performSave(newValue, false);
|
|
26763
|
+
}, debounceDelay);
|
|
26764
|
+
}
|
|
26765
|
+
};
|
|
26766
|
+
const handleKeyDown = (e) => {
|
|
26767
|
+
if (e.key === "Enter") {
|
|
26768
|
+
e.preventDefault();
|
|
26769
|
+
if (debounceTimeout.current) {
|
|
26770
|
+
clearTimeout(debounceTimeout.current);
|
|
26771
|
+
}
|
|
26772
|
+
performSave(editValue, true);
|
|
26773
|
+
} else if (e.key === "Escape") {
|
|
26774
|
+
e.preventDefault();
|
|
26775
|
+
if (debounceTimeout.current) {
|
|
26776
|
+
clearTimeout(debounceTimeout.current);
|
|
26777
|
+
}
|
|
26778
|
+
setEditValue(lastSavedValue);
|
|
26779
|
+
setHasUnsavedChanges(false);
|
|
26780
|
+
setSaveStatus("idle");
|
|
26781
|
+
setIsEditing(false);
|
|
26782
|
+
}
|
|
26783
|
+
};
|
|
26784
|
+
const handleClick = () => {
|
|
26785
|
+
if (!disabled && !isEditing) {
|
|
26786
|
+
setIsEditing(true);
|
|
26787
|
+
setSaveStatus("idle");
|
|
26788
|
+
}
|
|
26789
|
+
};
|
|
26790
|
+
if (isEditing) {
|
|
26791
|
+
return /* @__PURE__ */ jsx("div", { ref: containerRef, className: "inline-flex items-center gap-1.5", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
26792
|
+
/* @__PURE__ */ jsx(
|
|
26793
|
+
"input",
|
|
26794
|
+
{
|
|
26795
|
+
ref: inputRef,
|
|
26796
|
+
type: "text",
|
|
26797
|
+
value: editValue,
|
|
26798
|
+
onChange: handleInputChange,
|
|
26799
|
+
onKeyDown: handleKeyDown,
|
|
26800
|
+
className: `px-2 py-1 pr-7 text-sm border rounded-md transition-colors duration-200
|
|
26801
|
+
${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"}
|
|
26802
|
+
focus:outline-none focus:ring-2 bg-white
|
|
26803
|
+
${inputClassName}`,
|
|
26804
|
+
placeholder,
|
|
26805
|
+
autoComplete: "off"
|
|
26806
|
+
}
|
|
26807
|
+
),
|
|
26808
|
+
/* @__PURE__ */ jsxs("div", { className: "absolute right-1.5 top-1/2 -translate-y-1/2", children: [
|
|
26809
|
+
saveStatus === "saving" && /* @__PURE__ */ jsx(Loader2, { className: "w-3.5 h-3.5 text-blue-500 animate-spin" }),
|
|
26810
|
+
saveStatus === "saved" && /* @__PURE__ */ jsx(Check, { className: "w-3.5 h-3.5 text-green-500" }),
|
|
26811
|
+
saveStatus === "error" && /* @__PURE__ */ jsx(AlertCircle, { className: "w-3.5 h-3.5 text-red-500" }),
|
|
26812
|
+
saveStatus === "idle" && hasUnsavedChanges && /* @__PURE__ */ jsx("div", { className: "w-1.5 h-1.5 bg-yellow-400 rounded-full" })
|
|
26813
|
+
] })
|
|
26814
|
+
] }) });
|
|
26815
|
+
}
|
|
26816
|
+
return /* @__PURE__ */ jsxs(
|
|
26817
|
+
"div",
|
|
26818
|
+
{
|
|
26819
|
+
onClick: handleClick,
|
|
26820
|
+
className: `inline-flex items-center gap-1.5 cursor-pointer px-2 py-1 rounded-md
|
|
26821
|
+
transition-all duration-200 hover:bg-gray-50 group
|
|
26822
|
+
${disabled ? "cursor-not-allowed opacity-50" : ""}
|
|
26823
|
+
${className}`,
|
|
26824
|
+
children: [
|
|
26825
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-gray-900", children: editValue || placeholder }),
|
|
26826
|
+
/* @__PURE__ */ jsx(
|
|
26827
|
+
Edit2,
|
|
26828
|
+
{
|
|
26829
|
+
className: `w-3.5 h-3.5 text-gray-400 transition-opacity duration-200
|
|
26830
|
+
opacity-0 group-hover:opacity-100
|
|
26831
|
+
${disabled ? "hidden" : ""}
|
|
26832
|
+
${editIconClassName}`
|
|
26833
|
+
}
|
|
26834
|
+
)
|
|
26835
|
+
]
|
|
26836
|
+
}
|
|
26837
|
+
);
|
|
26838
|
+
};
|
|
26397
26839
|
var BottlenecksContent = ({
|
|
26398
26840
|
workspaceId,
|
|
26399
26841
|
workspaceName,
|
|
@@ -28139,6 +28581,451 @@ var KPISection = memo(({
|
|
|
28139
28581
|
return true;
|
|
28140
28582
|
});
|
|
28141
28583
|
KPISection.displayName = "KPISection";
|
|
28584
|
+
var WorkspaceHealthCard = ({
|
|
28585
|
+
workspace,
|
|
28586
|
+
onClick,
|
|
28587
|
+
showDetails = true,
|
|
28588
|
+
className = ""
|
|
28589
|
+
}) => {
|
|
28590
|
+
const getStatusConfig = () => {
|
|
28591
|
+
switch (workspace.status) {
|
|
28592
|
+
case "healthy":
|
|
28593
|
+
return {
|
|
28594
|
+
gradient: "from-emerald-50 to-green-50 dark:from-emerald-950/30 dark:to-green-950/30",
|
|
28595
|
+
border: "border-emerald-200 dark:border-emerald-800",
|
|
28596
|
+
icon: CheckCircle2,
|
|
28597
|
+
iconColor: "text-emerald-600 dark:text-emerald-400",
|
|
28598
|
+
badge: "bg-emerald-100 text-emerald-700 dark:bg-emerald-900/50 dark:text-emerald-300",
|
|
28599
|
+
statusText: "Online",
|
|
28600
|
+
pulse: true
|
|
28601
|
+
};
|
|
28602
|
+
case "unhealthy":
|
|
28603
|
+
return {
|
|
28604
|
+
gradient: "from-rose-50 to-red-50 dark:from-rose-950/30 dark:to-red-950/30",
|
|
28605
|
+
border: "border-rose-200 dark:border-rose-800",
|
|
28606
|
+
icon: XCircle,
|
|
28607
|
+
iconColor: "text-rose-600 dark:text-rose-400",
|
|
28608
|
+
badge: "bg-rose-100 text-rose-700 dark:bg-rose-900/50 dark:text-rose-300",
|
|
28609
|
+
statusText: "Offline",
|
|
28610
|
+
pulse: false
|
|
28611
|
+
};
|
|
28612
|
+
case "warning":
|
|
28613
|
+
return {
|
|
28614
|
+
gradient: "from-amber-50 to-yellow-50 dark:from-amber-950/30 dark:to-yellow-950/30",
|
|
28615
|
+
border: "border-amber-200 dark:border-amber-800",
|
|
28616
|
+
icon: AlertTriangle,
|
|
28617
|
+
iconColor: "text-amber-600 dark:text-amber-400",
|
|
28618
|
+
badge: "bg-amber-100 text-amber-700 dark:bg-amber-900/50 dark:text-amber-300",
|
|
28619
|
+
statusText: "Degraded",
|
|
28620
|
+
pulse: true
|
|
28621
|
+
};
|
|
28622
|
+
default:
|
|
28623
|
+
return {
|
|
28624
|
+
gradient: "from-gray-50 to-slate-50 dark:from-gray-950/30 dark:to-slate-950/30",
|
|
28625
|
+
border: "border-gray-200 dark:border-gray-800",
|
|
28626
|
+
icon: Activity,
|
|
28627
|
+
iconColor: "text-gray-500 dark:text-gray-400",
|
|
28628
|
+
badge: "bg-gray-100 text-gray-700 dark:bg-gray-900/50 dark:text-gray-300",
|
|
28629
|
+
statusText: "Unknown",
|
|
28630
|
+
pulse: false
|
|
28631
|
+
};
|
|
28632
|
+
}
|
|
28633
|
+
};
|
|
28634
|
+
const config = getStatusConfig();
|
|
28635
|
+
const StatusIcon = config.icon;
|
|
28636
|
+
workspace.isStale ? WifiOff : Wifi;
|
|
28637
|
+
const handleClick = () => {
|
|
28638
|
+
if (onClick) {
|
|
28639
|
+
onClick(workspace);
|
|
28640
|
+
}
|
|
28641
|
+
};
|
|
28642
|
+
const handleKeyDown = (event) => {
|
|
28643
|
+
if (onClick && (event.key === "Enter" || event.key === " ")) {
|
|
28644
|
+
event.preventDefault();
|
|
28645
|
+
onClick(workspace);
|
|
28646
|
+
}
|
|
28647
|
+
};
|
|
28648
|
+
const formatTimeAgo = (timeString) => {
|
|
28649
|
+
return timeString.replace("about ", "").replace(" ago", "");
|
|
28650
|
+
};
|
|
28651
|
+
return /* @__PURE__ */ jsx(
|
|
28652
|
+
Card2,
|
|
28653
|
+
{
|
|
28654
|
+
className: clsx(
|
|
28655
|
+
"relative overflow-hidden transition-all duration-300",
|
|
28656
|
+
"bg-gradient-to-br",
|
|
28657
|
+
config.gradient,
|
|
28658
|
+
"border",
|
|
28659
|
+
config.border,
|
|
28660
|
+
"shadow-sm hover:shadow-md",
|
|
28661
|
+
onClick && "cursor-pointer hover:scale-[1.01]",
|
|
28662
|
+
workspace.isStale && "opacity-90",
|
|
28663
|
+
className
|
|
28664
|
+
),
|
|
28665
|
+
onClick: handleClick,
|
|
28666
|
+
onKeyDown: handleKeyDown,
|
|
28667
|
+
tabIndex: onClick ? 0 : void 0,
|
|
28668
|
+
role: onClick ? "button" : void 0,
|
|
28669
|
+
"aria-label": `Workspace ${workspace.workspace_display_name} status: ${workspace.status}`,
|
|
28670
|
+
children: /* @__PURE__ */ jsxs("div", { className: "p-4", children: [
|
|
28671
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between mb-3", children: [
|
|
28672
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
|
|
28673
|
+
/* @__PURE__ */ 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)}` }),
|
|
28674
|
+
showDetails && workspace.line_name && /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400", children: workspace.line_name })
|
|
28675
|
+
] }),
|
|
28676
|
+
/* @__PURE__ */ jsxs("div", { className: clsx(
|
|
28677
|
+
"flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium",
|
|
28678
|
+
config.badge
|
|
28679
|
+
), children: [
|
|
28680
|
+
/* @__PURE__ */ jsx(StatusIcon, { className: "h-3.5 w-3.5" }),
|
|
28681
|
+
/* @__PURE__ */ jsx("span", { children: config.statusText })
|
|
28682
|
+
] })
|
|
28683
|
+
] }),
|
|
28684
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
28685
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
28686
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
28687
|
+
/* @__PURE__ */ jsx(Clock, { className: "h-3.5 w-3.5 text-gray-400" }),
|
|
28688
|
+
/* @__PURE__ */ jsxs("span", { className: "text-sm text-gray-600 dark:text-gray-400 whitespace-nowrap", children: [
|
|
28689
|
+
"Last seen: ",
|
|
28690
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: formatTimeAgo(workspace.timeSinceLastUpdate) })
|
|
28691
|
+
] })
|
|
28692
|
+
] }),
|
|
28693
|
+
workspace.isStale && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
28694
|
+
/* @__PURE__ */ jsx(WifiOff, { className: "h-3.5 w-3.5 text-amber-500" }),
|
|
28695
|
+
/* @__PURE__ */ jsx("span", { className: "text-amber-600 dark:text-amber-400 text-xs", children: "No recent updates" })
|
|
28696
|
+
] })
|
|
28697
|
+
] }),
|
|
28698
|
+
config.pulse && !workspace.isStale && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
28699
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex h-2 w-2", children: [
|
|
28700
|
+
/* @__PURE__ */ jsx("span", { className: clsx(
|
|
28701
|
+
"animate-ping absolute inline-flex h-full w-full rounded-full opacity-75",
|
|
28702
|
+
workspace.status === "healthy" ? "bg-emerald-400" : "bg-amber-400"
|
|
28703
|
+
) }),
|
|
28704
|
+
/* @__PURE__ */ jsx("span", { className: clsx(
|
|
28705
|
+
"relative inline-flex rounded-full h-2 w-2",
|
|
28706
|
+
workspace.status === "healthy" ? "bg-emerald-500" : "bg-amber-500"
|
|
28707
|
+
) })
|
|
28708
|
+
] }),
|
|
28709
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: "Live" })
|
|
28710
|
+
] })
|
|
28711
|
+
] })
|
|
28712
|
+
] })
|
|
28713
|
+
}
|
|
28714
|
+
);
|
|
28715
|
+
};
|
|
28716
|
+
var CompactWorkspaceHealthCard = ({
|
|
28717
|
+
workspace,
|
|
28718
|
+
onClick,
|
|
28719
|
+
className = ""
|
|
28720
|
+
}) => {
|
|
28721
|
+
const getStatusConfig = () => {
|
|
28722
|
+
switch (workspace.status) {
|
|
28723
|
+
case "healthy":
|
|
28724
|
+
return {
|
|
28725
|
+
dot: "bg-emerald-500",
|
|
28726
|
+
icon: CheckCircle2,
|
|
28727
|
+
iconColor: "text-emerald-600 dark:text-emerald-400",
|
|
28728
|
+
bg: "hover:bg-emerald-50 dark:hover:bg-emerald-950/20"
|
|
28729
|
+
};
|
|
28730
|
+
case "unhealthy":
|
|
28731
|
+
return {
|
|
28732
|
+
dot: "bg-rose-500",
|
|
28733
|
+
icon: XCircle,
|
|
28734
|
+
iconColor: "text-rose-600 dark:text-rose-400",
|
|
28735
|
+
bg: "hover:bg-rose-50 dark:hover:bg-rose-950/20"
|
|
28736
|
+
};
|
|
28737
|
+
case "warning":
|
|
28738
|
+
return {
|
|
28739
|
+
dot: "bg-amber-500",
|
|
28740
|
+
icon: AlertTriangle,
|
|
28741
|
+
iconColor: "text-amber-600 dark:text-amber-400",
|
|
28742
|
+
bg: "hover:bg-amber-50 dark:hover:bg-amber-950/20"
|
|
28743
|
+
};
|
|
28744
|
+
default:
|
|
28745
|
+
return {
|
|
28746
|
+
dot: "bg-gray-400",
|
|
28747
|
+
icon: Activity,
|
|
28748
|
+
iconColor: "text-gray-500 dark:text-gray-400",
|
|
28749
|
+
bg: "hover:bg-gray-50 dark:hover:bg-gray-950/20"
|
|
28750
|
+
};
|
|
28751
|
+
}
|
|
28752
|
+
};
|
|
28753
|
+
const config = getStatusConfig();
|
|
28754
|
+
const StatusIcon = config.icon;
|
|
28755
|
+
const handleClick = () => {
|
|
28756
|
+
if (onClick) {
|
|
28757
|
+
onClick(workspace);
|
|
28758
|
+
}
|
|
28759
|
+
};
|
|
28760
|
+
return /* @__PURE__ */ jsxs(
|
|
28761
|
+
"div",
|
|
28762
|
+
{
|
|
28763
|
+
className: clsx(
|
|
28764
|
+
"flex items-center justify-between px-4 py-3 rounded-lg border",
|
|
28765
|
+
"bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700",
|
|
28766
|
+
"transition-all duration-200",
|
|
28767
|
+
onClick && `cursor-pointer ${config.bg}`,
|
|
28768
|
+
className
|
|
28769
|
+
),
|
|
28770
|
+
onClick: handleClick,
|
|
28771
|
+
role: onClick ? "button" : void 0,
|
|
28772
|
+
tabIndex: onClick ? 0 : void 0,
|
|
28773
|
+
children: [
|
|
28774
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
28775
|
+
/* @__PURE__ */ jsx(StatusIcon, { className: clsx("h-5 w-5", config.iconColor) }),
|
|
28776
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
28777
|
+
/* @__PURE__ */ 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)}` }),
|
|
28778
|
+
workspace.line_name && /* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: workspace.line_name })
|
|
28779
|
+
] })
|
|
28780
|
+
] }),
|
|
28781
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
28782
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: workspace.timeSinceLastUpdate }),
|
|
28783
|
+
/* @__PURE__ */ jsx("div", { className: clsx("h-2 w-2 rounded-full", config.dot) })
|
|
28784
|
+
] })
|
|
28785
|
+
]
|
|
28786
|
+
}
|
|
28787
|
+
);
|
|
28788
|
+
};
|
|
28789
|
+
var HealthStatusGrid = ({
|
|
28790
|
+
workspaces,
|
|
28791
|
+
onWorkspaceClick,
|
|
28792
|
+
viewMode: initialViewMode = "grid",
|
|
28793
|
+
showFilters = true,
|
|
28794
|
+
groupBy: initialGroupBy = "none",
|
|
28795
|
+
className = ""
|
|
28796
|
+
}) => {
|
|
28797
|
+
const [viewMode, setViewMode] = useState(initialViewMode);
|
|
28798
|
+
const [searchTerm, setSearchTerm] = useState("");
|
|
28799
|
+
const [statusFilter, setStatusFilter] = useState("all");
|
|
28800
|
+
const [groupBy, setGroupBy] = useState(initialGroupBy);
|
|
28801
|
+
const [expandedGroups, setExpandedGroups] = useState(/* @__PURE__ */ new Set());
|
|
28802
|
+
const filteredWorkspaces = useMemo(() => {
|
|
28803
|
+
let filtered = [...workspaces];
|
|
28804
|
+
if (searchTerm) {
|
|
28805
|
+
const search = searchTerm.toLowerCase();
|
|
28806
|
+
filtered = filtered.filter(
|
|
28807
|
+
(w) => w.workspace_display_name?.toLowerCase().includes(search) || w.line_name?.toLowerCase().includes(search) || w.company_name?.toLowerCase().includes(search)
|
|
28808
|
+
);
|
|
28809
|
+
}
|
|
28810
|
+
if (statusFilter !== "all") {
|
|
28811
|
+
filtered = filtered.filter((w) => w.status === statusFilter);
|
|
28812
|
+
}
|
|
28813
|
+
return filtered;
|
|
28814
|
+
}, [workspaces, searchTerm, statusFilter]);
|
|
28815
|
+
const groupedWorkspaces = useMemo(() => {
|
|
28816
|
+
if (groupBy === "none") {
|
|
28817
|
+
return { "All Workspaces": filteredWorkspaces };
|
|
28818
|
+
}
|
|
28819
|
+
const groups = {};
|
|
28820
|
+
filteredWorkspaces.forEach((workspace) => {
|
|
28821
|
+
let key = "Unknown";
|
|
28822
|
+
switch (groupBy) {
|
|
28823
|
+
case "line":
|
|
28824
|
+
key = workspace.line_name || "Unknown Line";
|
|
28825
|
+
break;
|
|
28826
|
+
case "status":
|
|
28827
|
+
key = workspace.status;
|
|
28828
|
+
break;
|
|
28829
|
+
}
|
|
28830
|
+
if (!groups[key]) {
|
|
28831
|
+
groups[key] = [];
|
|
28832
|
+
}
|
|
28833
|
+
groups[key].push(workspace);
|
|
28834
|
+
});
|
|
28835
|
+
const sortedGroups = {};
|
|
28836
|
+
Object.keys(groups).sort().forEach((key) => {
|
|
28837
|
+
sortedGroups[key] = groups[key];
|
|
28838
|
+
});
|
|
28839
|
+
return sortedGroups;
|
|
28840
|
+
}, [filteredWorkspaces, groupBy]);
|
|
28841
|
+
useEffect(() => {
|
|
28842
|
+
if (groupBy !== "none") {
|
|
28843
|
+
setExpandedGroups(new Set(Object.keys(groupedWorkspaces)));
|
|
28844
|
+
}
|
|
28845
|
+
}, [groupBy, groupedWorkspaces]);
|
|
28846
|
+
const toggleGroup = (groupName) => {
|
|
28847
|
+
const newExpanded = new Set(expandedGroups);
|
|
28848
|
+
if (newExpanded.has(groupName)) {
|
|
28849
|
+
newExpanded.delete(groupName);
|
|
28850
|
+
} else {
|
|
28851
|
+
newExpanded.add(groupName);
|
|
28852
|
+
}
|
|
28853
|
+
setExpandedGroups(newExpanded);
|
|
28854
|
+
};
|
|
28855
|
+
const getStatusCounts = () => {
|
|
28856
|
+
const counts = {
|
|
28857
|
+
healthy: 0,
|
|
28858
|
+
unhealthy: 0,
|
|
28859
|
+
warning: 0,
|
|
28860
|
+
unknown: 0
|
|
28861
|
+
};
|
|
28862
|
+
workspaces.forEach((w) => {
|
|
28863
|
+
counts[w.status]++;
|
|
28864
|
+
});
|
|
28865
|
+
return counts;
|
|
28866
|
+
};
|
|
28867
|
+
const statusCounts = getStatusCounts();
|
|
28868
|
+
return /* @__PURE__ */ jsxs("div", { className: clsx("space-y-4", className), children: [
|
|
28869
|
+
showFilters && /* @__PURE__ */ jsxs("div", { className: "bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4", children: [
|
|
28870
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row gap-3 flex-wrap", children: [
|
|
28871
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1 min-w-[200px]", children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
28872
|
+
/* @__PURE__ */ jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400" }),
|
|
28873
|
+
/* @__PURE__ */ jsx(
|
|
28874
|
+
"input",
|
|
28875
|
+
{
|
|
28876
|
+
type: "text",
|
|
28877
|
+
placeholder: "Search workspaces...",
|
|
28878
|
+
value: searchTerm,
|
|
28879
|
+
onChange: (e) => setSearchTerm(e.target.value),
|
|
28880
|
+
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"
|
|
28881
|
+
}
|
|
28882
|
+
)
|
|
28883
|
+
] }) }),
|
|
28884
|
+
/* @__PURE__ */ jsxs(Select, { value: statusFilter, onValueChange: (value) => setStatusFilter(value), children: [
|
|
28885
|
+
/* @__PURE__ */ jsx(SelectTrigger, { className: "w-full sm:w-[180px] bg-white border-gray-200", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "All statuses" }) }),
|
|
28886
|
+
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
28887
|
+
/* @__PURE__ */ jsxs(SelectItem, { value: "all", children: [
|
|
28888
|
+
"All (",
|
|
28889
|
+
workspaces.length,
|
|
28890
|
+
")"
|
|
28891
|
+
] }),
|
|
28892
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "healthy", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
28893
|
+
/* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-green-500" }),
|
|
28894
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
28895
|
+
"Healthy (",
|
|
28896
|
+
statusCounts.healthy,
|
|
28897
|
+
")"
|
|
28898
|
+
] })
|
|
28899
|
+
] }) }),
|
|
28900
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "unhealthy", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
28901
|
+
/* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-red-500" }),
|
|
28902
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
28903
|
+
"Unhealthy (",
|
|
28904
|
+
statusCounts.unhealthy,
|
|
28905
|
+
")"
|
|
28906
|
+
] })
|
|
28907
|
+
] }) }),
|
|
28908
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "warning", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
28909
|
+
/* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-yellow-500" }),
|
|
28910
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
28911
|
+
"Warning (",
|
|
28912
|
+
statusCounts.warning,
|
|
28913
|
+
")"
|
|
28914
|
+
] })
|
|
28915
|
+
] }) }),
|
|
28916
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "unknown", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
28917
|
+
/* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-gray-400" }),
|
|
28918
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
28919
|
+
"Unknown (",
|
|
28920
|
+
statusCounts.unknown,
|
|
28921
|
+
")"
|
|
28922
|
+
] })
|
|
28923
|
+
] }) })
|
|
28924
|
+
] })
|
|
28925
|
+
] }),
|
|
28926
|
+
/* @__PURE__ */ jsxs(Select, { value: groupBy, onValueChange: (value) => setGroupBy(value), children: [
|
|
28927
|
+
/* @__PURE__ */ jsx(SelectTrigger, { className: "w-full sm:w-[160px] bg-white border-gray-200", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Group by" }) }),
|
|
28928
|
+
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
28929
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "none", children: "No grouping" }),
|
|
28930
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "line", children: "Group by Line" }),
|
|
28931
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "status", children: "Group by Status" })
|
|
28932
|
+
] })
|
|
28933
|
+
] }),
|
|
28934
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
28935
|
+
/* @__PURE__ */ jsx(
|
|
28936
|
+
"button",
|
|
28937
|
+
{
|
|
28938
|
+
onClick: () => setViewMode("grid"),
|
|
28939
|
+
className: clsx(
|
|
28940
|
+
"p-2 rounded-lg transition-colors",
|
|
28941
|
+
viewMode === "grid" ? "bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400" : "text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700"
|
|
28942
|
+
),
|
|
28943
|
+
"aria-label": "Grid view",
|
|
28944
|
+
children: /* @__PURE__ */ jsx(Grid3x3, { className: "h-5 w-5" })
|
|
28945
|
+
}
|
|
28946
|
+
),
|
|
28947
|
+
/* @__PURE__ */ jsx(
|
|
28948
|
+
"button",
|
|
28949
|
+
{
|
|
28950
|
+
onClick: () => setViewMode("list"),
|
|
28951
|
+
className: clsx(
|
|
28952
|
+
"p-2 rounded-lg transition-colors",
|
|
28953
|
+
viewMode === "list" ? "bg-blue-100 dark:bg-blue-900/30 text-blue-600 dark:text-blue-400" : "text-gray-600 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700"
|
|
28954
|
+
),
|
|
28955
|
+
"aria-label": "List view",
|
|
28956
|
+
children: /* @__PURE__ */ jsx(List, { className: "h-5 w-5" })
|
|
28957
|
+
}
|
|
28958
|
+
)
|
|
28959
|
+
] })
|
|
28960
|
+
] }),
|
|
28961
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-3 text-sm text-gray-500 dark:text-gray-400", children: [
|
|
28962
|
+
"Showing ",
|
|
28963
|
+
filteredWorkspaces.length,
|
|
28964
|
+
" of ",
|
|
28965
|
+
workspaces.length,
|
|
28966
|
+
" workspaces"
|
|
28967
|
+
] })
|
|
28968
|
+
] }),
|
|
28969
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-6", children: Object.entries(groupedWorkspaces).map(([groupName, groupWorkspaces]) => {
|
|
28970
|
+
const isExpanded = groupBy === "none" || expandedGroups.has(groupName);
|
|
28971
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
28972
|
+
groupBy !== "none" && /* @__PURE__ */ jsxs(
|
|
28973
|
+
"div",
|
|
28974
|
+
{
|
|
28975
|
+
className: "flex items-center justify-between cursor-pointer group",
|
|
28976
|
+
onClick: () => toggleGroup(groupName),
|
|
28977
|
+
children: [
|
|
28978
|
+
/* @__PURE__ */ jsxs("h3", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-2", children: [
|
|
28979
|
+
groupName,
|
|
28980
|
+
/* @__PURE__ */ jsxs("span", { className: "text-sm font-normal text-gray-500 dark:text-gray-400", children: [
|
|
28981
|
+
"(",
|
|
28982
|
+
groupWorkspaces.length,
|
|
28983
|
+
")"
|
|
28984
|
+
] })
|
|
28985
|
+
] }),
|
|
28986
|
+
/* @__PURE__ */ jsx(
|
|
28987
|
+
ChevronDown,
|
|
28988
|
+
{
|
|
28989
|
+
className: clsx(
|
|
28990
|
+
"h-5 w-5 text-gray-400 transition-transform",
|
|
28991
|
+
isExpanded && "rotate-180"
|
|
28992
|
+
)
|
|
28993
|
+
}
|
|
28994
|
+
)
|
|
28995
|
+
]
|
|
28996
|
+
}
|
|
28997
|
+
),
|
|
28998
|
+
isExpanded && /* @__PURE__ */ jsx(
|
|
28999
|
+
"div",
|
|
29000
|
+
{
|
|
29001
|
+
className: clsx(
|
|
29002
|
+
viewMode === "grid" ? "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4" : "space-y-2"
|
|
29003
|
+
),
|
|
29004
|
+
children: groupWorkspaces.map(
|
|
29005
|
+
(workspace) => viewMode === "grid" ? /* @__PURE__ */ jsx(
|
|
29006
|
+
WorkspaceHealthCard,
|
|
29007
|
+
{
|
|
29008
|
+
workspace,
|
|
29009
|
+
onClick: onWorkspaceClick,
|
|
29010
|
+
showDetails: true
|
|
29011
|
+
},
|
|
29012
|
+
workspace.workspace_id
|
|
29013
|
+
) : /* @__PURE__ */ jsx(
|
|
29014
|
+
CompactWorkspaceHealthCard,
|
|
29015
|
+
{
|
|
29016
|
+
workspace,
|
|
29017
|
+
onClick: onWorkspaceClick
|
|
29018
|
+
},
|
|
29019
|
+
workspace.workspace_id
|
|
29020
|
+
)
|
|
29021
|
+
)
|
|
29022
|
+
}
|
|
29023
|
+
)
|
|
29024
|
+
] }, groupName);
|
|
29025
|
+
}) }),
|
|
29026
|
+
filteredWorkspaces.length === 0 && /* @__PURE__ */ jsx("div", { className: "text-center py-12", children: /* @__PURE__ */ jsx("p", { className: "text-gray-500 dark:text-gray-400", children: searchTerm || statusFilter !== "all" ? "No workspaces found matching your filters." : "No workspaces available." }) })
|
|
29027
|
+
] });
|
|
29028
|
+
};
|
|
28142
29029
|
var ISTTimer2 = ISTTimer_default;
|
|
28143
29030
|
var DashboardHeader = memo(({ lineTitle, className = "", headerControls }) => {
|
|
28144
29031
|
const getShiftName = () => {
|
|
@@ -28636,6 +29523,17 @@ var SideNavBar = memo(({
|
|
|
28636
29523
|
});
|
|
28637
29524
|
onMobileMenuClose?.();
|
|
28638
29525
|
}, [navigate, onMobileMenuClose]);
|
|
29526
|
+
const handleHealthClick = useCallback(() => {
|
|
29527
|
+
navigate("/health", {
|
|
29528
|
+
trackingEvent: {
|
|
29529
|
+
name: "Health Status Page Clicked",
|
|
29530
|
+
properties: {
|
|
29531
|
+
source: "side_nav"
|
|
29532
|
+
}
|
|
29533
|
+
}
|
|
29534
|
+
});
|
|
29535
|
+
onMobileMenuClose?.();
|
|
29536
|
+
}, [navigate, onMobileMenuClose]);
|
|
28639
29537
|
const handleLogoClick = useCallback(() => {
|
|
28640
29538
|
navigate("/");
|
|
28641
29539
|
onMobileMenuClose?.();
|
|
@@ -28649,6 +29547,7 @@ var SideNavBar = memo(({
|
|
|
28649
29547
|
const profileButtonClasses = useMemo(() => getButtonClasses("/profile"), [getButtonClasses, pathname]);
|
|
28650
29548
|
const helpButtonClasses = useMemo(() => getButtonClasses("/help"), [getButtonClasses, pathname]);
|
|
28651
29549
|
const skusButtonClasses = useMemo(() => getButtonClasses("/skus"), [getButtonClasses, pathname]);
|
|
29550
|
+
const healthButtonClasses = useMemo(() => getButtonClasses("/health"), [getButtonClasses, pathname]);
|
|
28652
29551
|
const NavigationContent = () => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
28653
29552
|
/* @__PURE__ */ jsx("div", { className: "w-full py-6 px-4 flex-shrink-0", children: /* @__PURE__ */ jsx(
|
|
28654
29553
|
"button",
|
|
@@ -28793,6 +29692,21 @@ var SideNavBar = memo(({
|
|
|
28793
29692
|
/* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium leading-tight", children: "Help" })
|
|
28794
29693
|
]
|
|
28795
29694
|
}
|
|
29695
|
+
),
|
|
29696
|
+
/* @__PURE__ */ jsxs(
|
|
29697
|
+
"button",
|
|
29698
|
+
{
|
|
29699
|
+
onClick: handleHealthClick,
|
|
29700
|
+
className: healthButtonClasses,
|
|
29701
|
+
"aria-label": "System Health",
|
|
29702
|
+
tabIndex: 0,
|
|
29703
|
+
role: "tab",
|
|
29704
|
+
"aria-selected": pathname === "/health" || pathname.startsWith("/health/"),
|
|
29705
|
+
children: [
|
|
29706
|
+
/* @__PURE__ */ jsx(HeartIcon, { className: "w-5 h-5 mb-1" }),
|
|
29707
|
+
/* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium leading-tight", children: "Health" })
|
|
29708
|
+
]
|
|
29709
|
+
}
|
|
28796
29710
|
)
|
|
28797
29711
|
] })
|
|
28798
29712
|
] }),
|
|
@@ -35418,6 +36332,7 @@ var TargetsViewUI = ({
|
|
|
35418
36332
|
onSaveLine,
|
|
35419
36333
|
onToggleBulkConfigure,
|
|
35420
36334
|
onBulkConfigure,
|
|
36335
|
+
onUpdateWorkspaceDisplayName,
|
|
35421
36336
|
// SKU props
|
|
35422
36337
|
skuEnabled = false,
|
|
35423
36338
|
skus = [],
|
|
@@ -35583,7 +36498,18 @@ var TargetsViewUI = ({
|
|
|
35583
36498
|
{
|
|
35584
36499
|
className: "px-6 py-4 hover:bg-gray-50 transition-all duration-200",
|
|
35585
36500
|
children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-12 gap-6 items-center", children: [
|
|
35586
|
-
/* @__PURE__ */ jsx("div", { className: "col-span-2", children: /* @__PURE__ */ jsx(
|
|
36501
|
+
/* @__PURE__ */ jsx("div", { className: "col-span-2", children: onUpdateWorkspaceDisplayName ? /* @__PURE__ */ jsx(
|
|
36502
|
+
InlineEditableText,
|
|
36503
|
+
{
|
|
36504
|
+
value: formattedName,
|
|
36505
|
+
onSave: async (newName) => {
|
|
36506
|
+
await onUpdateWorkspaceDisplayName(workspace.id, newName);
|
|
36507
|
+
},
|
|
36508
|
+
placeholder: "Workspace name",
|
|
36509
|
+
className: "font-medium text-gray-900",
|
|
36510
|
+
inputClassName: "min-w-[120px]"
|
|
36511
|
+
}
|
|
36512
|
+
) : /* @__PURE__ */ jsx("span", { className: "font-medium text-gray-900", children: formattedName }) }),
|
|
35587
36513
|
/* @__PURE__ */ jsx("div", { className: "col-span-2", children: /* @__PURE__ */ jsxs(
|
|
35588
36514
|
"select",
|
|
35589
36515
|
{
|
|
@@ -36324,6 +37250,17 @@ var TargetsView = ({
|
|
|
36324
37250
|
router.push("/");
|
|
36325
37251
|
}
|
|
36326
37252
|
};
|
|
37253
|
+
const handleUpdateWorkspaceDisplayName = useCallback(async (workspaceId, displayName) => {
|
|
37254
|
+
try {
|
|
37255
|
+
await workspaceService.updateWorkspaceDisplayName(workspaceId, displayName);
|
|
37256
|
+
await forceRefreshWorkspaceDisplayNames();
|
|
37257
|
+
toast.success("Workspace name updated successfully");
|
|
37258
|
+
} catch (error) {
|
|
37259
|
+
console.error("Error updating workspace display name:", error);
|
|
37260
|
+
toast.error("Failed to update workspace name");
|
|
37261
|
+
throw error;
|
|
37262
|
+
}
|
|
37263
|
+
}, []);
|
|
36327
37264
|
return /* @__PURE__ */ jsx(
|
|
36328
37265
|
TargetsViewUI_default,
|
|
36329
37266
|
{
|
|
@@ -36346,6 +37283,7 @@ var TargetsView = ({
|
|
|
36346
37283
|
onSaveLine: handleSaveLine,
|
|
36347
37284
|
onToggleBulkConfigure: handleToggleBulkConfigure,
|
|
36348
37285
|
onBulkConfigure: handleBulkConfigure,
|
|
37286
|
+
onUpdateWorkspaceDisplayName: handleUpdateWorkspaceDisplayName,
|
|
36349
37287
|
skuEnabled,
|
|
36350
37288
|
skus,
|
|
36351
37289
|
onUpdateSelectedSKU: updateSelectedSKU,
|
|
@@ -36444,6 +37382,14 @@ var WorkspaceDetailView = ({
|
|
|
36444
37382
|
const [usingFallbackData, setUsingFallbackData] = useState(false);
|
|
36445
37383
|
const [showIdleTime, setShowIdleTime] = useState(false);
|
|
36446
37384
|
const dashboardConfig = useDashboardConfig();
|
|
37385
|
+
const {
|
|
37386
|
+
workspace: workspaceHealth,
|
|
37387
|
+
loading: healthLoading,
|
|
37388
|
+
error: healthError
|
|
37389
|
+
} = useWorkspaceHealthById(workspaceId, {
|
|
37390
|
+
enableRealtime: true,
|
|
37391
|
+
refreshInterval: 3e4
|
|
37392
|
+
});
|
|
36447
37393
|
const {
|
|
36448
37394
|
status: prefetchStatus,
|
|
36449
37395
|
data: prefetchData,
|
|
@@ -36796,10 +37742,7 @@ var WorkspaceDetailView = ({
|
|
|
36796
37742
|
"aria-label": "Navigate back to previous page"
|
|
36797
37743
|
}
|
|
36798
37744
|
) }),
|
|
36799
|
-
/* @__PURE__ */
|
|
36800
|
-
/* @__PURE__ */ jsx("h1", { className: "text-3xl font-semibold text-gray-900", children: formattedWorkspaceName }),
|
|
36801
|
-
/* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-green-500 animate-pulse ring-2 ring-green-500/30 ring-offset-1" })
|
|
36802
|
-
] }),
|
|
37745
|
+
/* @__PURE__ */ jsx("div", { className: "absolute left-1/2 transform -translate-x-1/2", children: /* @__PURE__ */ jsx("h1", { className: "text-3xl font-semibold text-gray-900", children: formattedWorkspaceName }) }),
|
|
36803
37746
|
/* @__PURE__ */ jsx("div", { className: "w-full h-8" })
|
|
36804
37747
|
] }),
|
|
36805
37748
|
activeTab !== "monthly_history" && /* @__PURE__ */ jsx("div", { className: "mt-3 bg-blue-50 px-3 py-2 rounded-lg", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-4", children: [
|
|
@@ -36827,6 +37770,19 @@ var WorkspaceDetailView = ({
|
|
|
36827
37770
|
workspace.shift_type,
|
|
36828
37771
|
" Shift"
|
|
36829
37772
|
] })
|
|
37773
|
+
] }),
|
|
37774
|
+
workspaceHealth && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
37775
|
+
/* @__PURE__ */ jsx("div", { className: "w-px h-4 bg-blue-300" }),
|
|
37776
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
37777
|
+
/* @__PURE__ */ jsx("div", { className: clsx(
|
|
37778
|
+
"h-1.5 w-1.5 rounded-full",
|
|
37779
|
+
workspaceHealth.status === "healthy" ? "bg-green-600" : workspaceHealth.status === "unhealthy" ? "bg-red-600" : workspaceHealth.status === "warning" ? "bg-amber-600" : "bg-gray-500"
|
|
37780
|
+
) }),
|
|
37781
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-blue-700", children: [
|
|
37782
|
+
"Last update: ",
|
|
37783
|
+
workspaceHealth.timeSinceLastUpdate
|
|
37784
|
+
] })
|
|
37785
|
+
] })
|
|
36830
37786
|
] })
|
|
36831
37787
|
] }) }),
|
|
36832
37788
|
/* @__PURE__ */ jsxs("div", { className: "mt-1 sm:mt-1.5 lg:mt-2 flex items-center justify-between", children: [
|
|
@@ -37363,6 +38319,255 @@ var SKUManagementView = () => {
|
|
|
37363
38319
|
)
|
|
37364
38320
|
] });
|
|
37365
38321
|
};
|
|
38322
|
+
var WorkspaceHealthView = ({
|
|
38323
|
+
lineId,
|
|
38324
|
+
companyId,
|
|
38325
|
+
onNavigate,
|
|
38326
|
+
className = ""
|
|
38327
|
+
}) => {
|
|
38328
|
+
const router = useRouter();
|
|
38329
|
+
const [viewMode, setViewMode] = useState("grid");
|
|
38330
|
+
const [groupBy, setGroupBy] = useState("line");
|
|
38331
|
+
const operationalDate = getOperationalDate();
|
|
38332
|
+
const currentHour = (/* @__PURE__ */ new Date()).getHours();
|
|
38333
|
+
const isNightShift = currentHour >= 18 || currentHour < 6;
|
|
38334
|
+
const shiftType = isNightShift ? "Night" : "Day";
|
|
38335
|
+
const formatDate = (date) => {
|
|
38336
|
+
const d = new Date(date);
|
|
38337
|
+
return d.toLocaleDateString("en-IN", {
|
|
38338
|
+
month: "long",
|
|
38339
|
+
day: "numeric",
|
|
38340
|
+
year: "numeric",
|
|
38341
|
+
timeZone: "Asia/Kolkata"
|
|
38342
|
+
});
|
|
38343
|
+
};
|
|
38344
|
+
const getShiftIcon = (shift) => {
|
|
38345
|
+
return shift === "Night" ? /* @__PURE__ */ jsx(Moon, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(Sun, { className: "h-4 w-4" });
|
|
38346
|
+
};
|
|
38347
|
+
const {
|
|
38348
|
+
workspaces,
|
|
38349
|
+
summary,
|
|
38350
|
+
loading,
|
|
38351
|
+
error,
|
|
38352
|
+
refetch
|
|
38353
|
+
} = useWorkspaceHealth({
|
|
38354
|
+
lineId,
|
|
38355
|
+
companyId,
|
|
38356
|
+
enableRealtime: true,
|
|
38357
|
+
refreshInterval: 1e4
|
|
38358
|
+
// Refresh every 10 seconds for more responsive updates
|
|
38359
|
+
});
|
|
38360
|
+
const handleWorkspaceClick = useCallback(
|
|
38361
|
+
(workspace) => {
|
|
38362
|
+
const url = `/workspace/${workspace.workspace_id}`;
|
|
38363
|
+
if (onNavigate) {
|
|
38364
|
+
onNavigate(url);
|
|
38365
|
+
} else {
|
|
38366
|
+
router.push(url);
|
|
38367
|
+
}
|
|
38368
|
+
},
|
|
38369
|
+
[router, onNavigate]
|
|
38370
|
+
);
|
|
38371
|
+
const handleExport = useCallback(() => {
|
|
38372
|
+
const csv = [
|
|
38373
|
+
["Workspace", "Line", "Company", "Status", "Last Heartbeat", "Consecutive Misses"],
|
|
38374
|
+
...workspaces.map((w) => [
|
|
38375
|
+
w.workspace_display_name || "",
|
|
38376
|
+
w.line_name || "",
|
|
38377
|
+
w.company_name || "",
|
|
38378
|
+
w.status,
|
|
38379
|
+
w.last_heartbeat,
|
|
38380
|
+
w.consecutive_misses?.toString() || "0"
|
|
38381
|
+
])
|
|
38382
|
+
].map((row) => row.join(",")).join("\n");
|
|
38383
|
+
const blob = new Blob([csv], { type: "text/csv" });
|
|
38384
|
+
const url = window.URL.createObjectURL(blob);
|
|
38385
|
+
const a = document.createElement("a");
|
|
38386
|
+
a.href = url;
|
|
38387
|
+
a.download = `workspace-health-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.csv`;
|
|
38388
|
+
document.body.appendChild(a);
|
|
38389
|
+
a.click();
|
|
38390
|
+
document.body.removeChild(a);
|
|
38391
|
+
window.URL.revokeObjectURL(url);
|
|
38392
|
+
}, [workspaces]);
|
|
38393
|
+
const getStatusIcon = (status) => {
|
|
38394
|
+
switch (status) {
|
|
38395
|
+
case "healthy":
|
|
38396
|
+
return /* @__PURE__ */ jsx(CheckCircle, { className: "h-5 w-5 text-green-500" });
|
|
38397
|
+
case "unhealthy":
|
|
38398
|
+
return /* @__PURE__ */ jsx(XCircle, { className: "h-5 w-5 text-red-500" });
|
|
38399
|
+
case "warning":
|
|
38400
|
+
return /* @__PURE__ */ jsx(AlertTriangle, { className: "h-5 w-5 text-yellow-500" });
|
|
38401
|
+
default:
|
|
38402
|
+
return /* @__PURE__ */ jsx(Activity, { className: "h-5 w-5 text-gray-400" });
|
|
38403
|
+
}
|
|
38404
|
+
};
|
|
38405
|
+
const getUptimeColor = (percentage) => {
|
|
38406
|
+
if (percentage >= 99) return "text-green-600 dark:text-green-400";
|
|
38407
|
+
if (percentage >= 95) return "text-yellow-600 dark:text-yellow-400";
|
|
38408
|
+
return "text-red-600 dark:text-red-400";
|
|
38409
|
+
};
|
|
38410
|
+
if (loading && !summary) {
|
|
38411
|
+
return /* @__PURE__ */ jsx("div", { className: "min-h-screen bg-gray-50 dark:bg-gray-900 p-4", children: /* @__PURE__ */ jsx(LoadingState, {}) });
|
|
38412
|
+
}
|
|
38413
|
+
if (error) {
|
|
38414
|
+
return /* @__PURE__ */ jsx("div", { className: "min-h-screen bg-gray-50 dark:bg-gray-900 p-4", children: /* @__PURE__ */ jsx("div", { className: "max-w-7xl mx-auto", children: /* @__PURE__ */ jsx(Card2, { className: "border-red-200 dark:border-red-800", children: /* @__PURE__ */ jsxs(CardContent2, { className: "p-8 text-center", children: [
|
|
38415
|
+
/* @__PURE__ */ jsx(XCircle, { className: "h-12 w-12 text-red-500 mx-auto mb-4" }),
|
|
38416
|
+
/* @__PURE__ */ jsx("h2", { className: "text-xl font-semibold text-gray-900 dark:text-gray-100 mb-2", children: "Error Loading Health Status" }),
|
|
38417
|
+
/* @__PURE__ */ jsx("p", { className: "text-gray-600 dark:text-gray-400 mb-4", children: error.message || "Unable to load workspace health status" }),
|
|
38418
|
+
/* @__PURE__ */ jsx(
|
|
38419
|
+
"button",
|
|
38420
|
+
{
|
|
38421
|
+
onClick: () => refetch(),
|
|
38422
|
+
className: "px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors",
|
|
38423
|
+
children: "Try Again"
|
|
38424
|
+
}
|
|
38425
|
+
)
|
|
38426
|
+
] }) }) }) });
|
|
38427
|
+
}
|
|
38428
|
+
return /* @__PURE__ */ jsxs("div", { className: clsx("min-h-screen bg-slate-50", className), children: [
|
|
38429
|
+
/* @__PURE__ */ 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: [
|
|
38430
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex items-center", children: [
|
|
38431
|
+
/* @__PURE__ */ jsx("div", { className: "absolute left-0 z-10", children: /* @__PURE__ */ jsx(
|
|
38432
|
+
BackButtonMinimal,
|
|
38433
|
+
{
|
|
38434
|
+
onClick: () => router.push("/"),
|
|
38435
|
+
text: "Back",
|
|
38436
|
+
size: "default",
|
|
38437
|
+
"aria-label": "Navigate back to dashboard"
|
|
38438
|
+
}
|
|
38439
|
+
) }),
|
|
38440
|
+
/* @__PURE__ */ jsx("div", { className: "absolute left-1/2 transform -translate-x-1/2", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
38441
|
+
/* @__PURE__ */ jsx("h1", { className: "text-3xl font-semibold text-gray-900", children: "System Health" }),
|
|
38442
|
+
/* @__PURE__ */ jsxs("div", { className: "relative flex h-2.5 w-2.5", children: [
|
|
38443
|
+
/* @__PURE__ */ jsx("span", { className: "animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75" }),
|
|
38444
|
+
/* @__PURE__ */ jsx("span", { className: "relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500" })
|
|
38445
|
+
] })
|
|
38446
|
+
] }) }),
|
|
38447
|
+
/* @__PURE__ */ jsxs("div", { className: "absolute right-0 flex gap-2", children: [
|
|
38448
|
+
/* @__PURE__ */ jsx(
|
|
38449
|
+
"button",
|
|
38450
|
+
{
|
|
38451
|
+
onClick: () => {
|
|
38452
|
+
refetch();
|
|
38453
|
+
},
|
|
38454
|
+
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
38455
|
+
"aria-label": "Refresh",
|
|
38456
|
+
children: /* @__PURE__ */ jsx(RefreshCw, { className: "h-5 w-5" })
|
|
38457
|
+
}
|
|
38458
|
+
),
|
|
38459
|
+
/* @__PURE__ */ jsx(
|
|
38460
|
+
"button",
|
|
38461
|
+
{
|
|
38462
|
+
onClick: handleExport,
|
|
38463
|
+
className: "p-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors",
|
|
38464
|
+
"aria-label": "Export CSV",
|
|
38465
|
+
children: /* @__PURE__ */ jsx(Download, { className: "h-5 w-5" })
|
|
38466
|
+
}
|
|
38467
|
+
)
|
|
38468
|
+
] }),
|
|
38469
|
+
/* @__PURE__ */ jsx("div", { className: "w-full h-8" })
|
|
38470
|
+
] }),
|
|
38471
|
+
/* @__PURE__ */ jsx("div", { className: "mt-3 bg-blue-50 px-3 py-2 rounded-lg", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-center gap-4", children: [
|
|
38472
|
+
/* @__PURE__ */ jsx("div", { className: "text-lg font-medium text-blue-600", children: /* @__PURE__ */ jsx(LiveTimer, {}) }),
|
|
38473
|
+
/* @__PURE__ */ jsx("div", { className: "w-px h-4 bg-blue-300" }),
|
|
38474
|
+
/* @__PURE__ */ jsx("span", { className: "text-base font-medium text-blue-600", children: formatDate(operationalDate) }),
|
|
38475
|
+
/* @__PURE__ */ jsx("div", { className: "w-px h-4 bg-blue-300" }),
|
|
38476
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
38477
|
+
/* @__PURE__ */ jsx("div", { className: "text-blue-600", children: getShiftIcon(shiftType) }),
|
|
38478
|
+
/* @__PURE__ */ jsxs("span", { className: "text-base font-medium text-blue-600", children: [
|
|
38479
|
+
shiftType,
|
|
38480
|
+
" Shift"
|
|
38481
|
+
] })
|
|
38482
|
+
] })
|
|
38483
|
+
] }) })
|
|
38484
|
+
] }),
|
|
38485
|
+
/* @__PURE__ */ jsxs("div", { className: "max-w-7xl mx-auto p-4 space-y-6", children: [
|
|
38486
|
+
summary && /* @__PURE__ */ jsxs(
|
|
38487
|
+
motion.div,
|
|
38488
|
+
{
|
|
38489
|
+
initial: { opacity: 0, y: 20 },
|
|
38490
|
+
animate: { opacity: 1, y: 0 },
|
|
38491
|
+
transition: { duration: 0.3, delay: 0.1 },
|
|
38492
|
+
className: "grid grid-cols-2 sm:grid-cols-2 md:grid-cols-5 gap-2 sm:gap-3 lg:gap-4",
|
|
38493
|
+
children: [
|
|
38494
|
+
/* @__PURE__ */ jsxs(Card2, { className: "col-span-2 sm:col-span-2 md:col-span-2", children: [
|
|
38495
|
+
/* @__PURE__ */ jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsx(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400", children: "Overall System Status" }) }),
|
|
38496
|
+
/* @__PURE__ */ jsxs(CardContent2, { children: [
|
|
38497
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-baseline gap-2", children: [
|
|
38498
|
+
/* @__PURE__ */ jsxs("span", { className: clsx("text-3xl font-bold", getUptimeColor(summary.uptimePercentage)), children: [
|
|
38499
|
+
summary.uptimePercentage.toFixed(1),
|
|
38500
|
+
"%"
|
|
38501
|
+
] }),
|
|
38502
|
+
summary.uptimePercentage >= 99 ? /* @__PURE__ */ jsx(TrendingUp, { className: "h-5 w-5 text-green-500" }) : /* @__PURE__ */ jsx(TrendingDown, { className: "h-5 w-5 text-red-500" })
|
|
38503
|
+
] }),
|
|
38504
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: [
|
|
38505
|
+
summary.healthyWorkspaces,
|
|
38506
|
+
" of ",
|
|
38507
|
+
summary.totalWorkspaces,
|
|
38508
|
+
" workspaces healthy"
|
|
38509
|
+
] })
|
|
38510
|
+
] })
|
|
38511
|
+
] }),
|
|
38512
|
+
/* @__PURE__ */ jsxs(Card2, { children: [
|
|
38513
|
+
/* @__PURE__ */ jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
|
|
38514
|
+
getStatusIcon("healthy"),
|
|
38515
|
+
"Healthy"
|
|
38516
|
+
] }) }),
|
|
38517
|
+
/* @__PURE__ */ jsxs(CardContent2, { children: [
|
|
38518
|
+
/* @__PURE__ */ jsx("p", { className: "text-2xl font-bold text-green-600 dark:text-green-400", children: summary.healthyWorkspaces }),
|
|
38519
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Operating normally" })
|
|
38520
|
+
] })
|
|
38521
|
+
] }),
|
|
38522
|
+
/* @__PURE__ */ jsxs(Card2, { children: [
|
|
38523
|
+
/* @__PURE__ */ jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
|
|
38524
|
+
getStatusIcon("warning"),
|
|
38525
|
+
"Warning"
|
|
38526
|
+
] }) }),
|
|
38527
|
+
/* @__PURE__ */ jsxs(CardContent2, { children: [
|
|
38528
|
+
/* @__PURE__ */ jsx("p", { className: "text-2xl font-bold text-yellow-600 dark:text-yellow-400", children: summary.warningWorkspaces }),
|
|
38529
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Delayed updates" })
|
|
38530
|
+
] })
|
|
38531
|
+
] }),
|
|
38532
|
+
/* @__PURE__ */ jsxs(Card2, { children: [
|
|
38533
|
+
/* @__PURE__ */ jsx(CardHeader2, { className: "pb-3", children: /* @__PURE__ */ jsxs(CardTitle2, { className: "text-sm font-medium text-gray-500 dark:text-gray-400 flex items-center gap-2", children: [
|
|
38534
|
+
getStatusIcon("unhealthy"),
|
|
38535
|
+
"Unhealthy"
|
|
38536
|
+
] }) }),
|
|
38537
|
+
/* @__PURE__ */ jsxs(CardContent2, { children: [
|
|
38538
|
+
/* @__PURE__ */ jsx("p", { className: "text-2xl font-bold text-red-600 dark:text-red-400", children: summary.unhealthyWorkspaces }),
|
|
38539
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-gray-500 dark:text-gray-400 mt-1", children: "Requires attention" })
|
|
38540
|
+
] })
|
|
38541
|
+
] })
|
|
38542
|
+
]
|
|
38543
|
+
}
|
|
38544
|
+
),
|
|
38545
|
+
/* @__PURE__ */ jsx(
|
|
38546
|
+
motion.div,
|
|
38547
|
+
{
|
|
38548
|
+
initial: { opacity: 0, y: 20 },
|
|
38549
|
+
animate: { opacity: 1, y: 0 },
|
|
38550
|
+
transition: { duration: 0.3, delay: 0.2 },
|
|
38551
|
+
children: /* @__PURE__ */ jsx(
|
|
38552
|
+
HealthStatusGrid,
|
|
38553
|
+
{
|
|
38554
|
+
workspaces,
|
|
38555
|
+
onWorkspaceClick: handleWorkspaceClick,
|
|
38556
|
+
viewMode,
|
|
38557
|
+
showFilters: true,
|
|
38558
|
+
groupBy
|
|
38559
|
+
}
|
|
38560
|
+
)
|
|
38561
|
+
}
|
|
38562
|
+
)
|
|
38563
|
+
] })
|
|
38564
|
+
] });
|
|
38565
|
+
};
|
|
38566
|
+
var WorkspaceHealthView_default = withAuth(WorkspaceHealthView, {
|
|
38567
|
+
redirectTo: "/login",
|
|
38568
|
+
requireAuth: true
|
|
38569
|
+
});
|
|
38570
|
+
var AuthenticatedWorkspaceHealthView = WorkspaceHealthView;
|
|
37366
38571
|
var S3Service = class {
|
|
37367
38572
|
constructor(config) {
|
|
37368
38573
|
this.s3Client = null;
|
|
@@ -37818,4 +39023,4 @@ var streamProxyConfig = {
|
|
|
37818
39023
|
}
|
|
37819
39024
|
};
|
|
37820
39025
|
|
|
37821
|
-
export { ACTION_NAMES, AIAgentView_default as AIAgentView, AudioService, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedShiftsView, AuthenticatedTargetsView, BackButton, BackButtonMinimal, BarChart, BaseHistoryCalendar, BottlenecksContent, BreakNotificationPopup, CachePrefetchStatus, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, CongratulationsOverlay, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_SHIFT_CONFIG, DEFAULT_THEME_CONFIG, DEFAULT_VIDEO_CONFIG, DEFAULT_WORKSPACE_CONFIG, DEFAULT_WORKSPACE_POSITIONS, DashboardHeader, DashboardLayout, DashboardOverridesProvider, DashboardProvider, DateDisplay_default as DateDisplay, DateTimeDisplay, DebugAuth, DebugAuthView_default as DebugAuthView, EmptyStateMessage, EncouragementOverlay, FactoryView_default as FactoryView, GaugeChart, GridComponentsPlaceholder, HamburgerButton, Header, HelpView_default as HelpView, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, ISTTimer_default as ISTTimer, KPICard, KPIDetailView_default as KPIDetailView, KPIGrid, KPIHeader, KPISection, KPIsOverviewView_default as KPIsOverviewView, LINE_1_UUID, LINE_2_UUID, LargeOutputProgressChart, LeaderboardDetailView_default as LeaderboardDetailView, Legend6 as Legend, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LiveTimer, LoadingInline, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSkeleton, LoadingState, LoginPage, LoginView_default as LoginView, MainLayout, MetricCard_default as MetricCard, NoWorkspaceData, OptifyeAgentClient, OptifyeLogoLoader_default as OptifyeLogoLoader, OutputProgressChart, PageHeader, PieChart4 as PieChart, PrefetchConfigurationError, PrefetchError, PrefetchEvents, PrefetchStatus, PrefetchTimeoutError, ProfileView_default as ProfileView, RegistryProvider, S3Service, SKUManagementView, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SingleVideoStream_default as SingleVideoStream, Skeleton, SubscriptionManager, SubscriptionManagerProvider, SupabaseProvider, TargetWorkspaceGrid, TargetsView_default as TargetsView, ThreadSidebar, TicketHistory_default as TicketHistory, TicketHistoryService, TimeDisplay_default as TimeDisplay, TimePickerDropdown, VideoCard, VideoGridView, VideoPlayer, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceDisplayNameExample, WorkspaceGrid, WorkspaceGridItem, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, apiUtils, authCoreService, authOTPService, authRateLimitService, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearWorkspaceDisplayNamesCache, cn, createStreamProxyHandler, createSupabaseClient, createThrottledReload, dashboardService, deleteThread, forceRefreshWorkspaceDisplayNames, formatDateInZone, formatDateTimeInZone, formatISTDate, formatIdleTime, formatTimeInZone, fromUrlFriendlyName, getAllLineDisplayNames, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAnonClient, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getConfiguredLineIds, getCoreSessionRecordingProperties, getCoreSessionReplayUrl, getCurrentShift, getCurrentTimeInZone, getDashboardHeaderTimeInZone, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultLineId, getDefaultTabForWorkspace, getLineDisplayName, getManufacturingInsights, getMetricsTablePrefix, getOperationalDate, getS3SignedUrl, getS3VideoSrc, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getSubscriptionManager, getThreadMessages, getUserThreads, getUserThreadsPaginated, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, identifyCoreUser, initializeCoreMixpanel, isLegacyConfiguration, isPrefetchError, isTransitionPeriod, isUrlPermanentlyFailed, isValidFactoryViewConfiguration, isValidLineInfoPayload, isValidPrefetchParams, isValidPrefetchStatus, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, mergeWithDefaultConfig, migrateLegacyConfiguration, optifyeAgentClient, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, resetFailedUrl, resetSubscriptionManager, s3VideoPreloader, skuService, startCoreSessionRecording, stopCoreSessionRecording, storeWorkspaceMapping, streamProxyConfig, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, updateThreadTitle, useActiveBreaks, useAllWorkspaceMetrics, useAnalyticsConfig, useAudioService, useAuth, useAuthConfig, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHistoricWorkspaceMetrics, useHlsStream, useHlsStreamWithCropping, useHookOverride, useHourEndTimer, useHourlyTargetAchievements, useHourlyTargetMisses, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineWorkspaceMetrics, useMessages, useMetrics, useNavigation, useOverrides, usePageOverride, usePrefetchClipCounts, useRealtimeLineMetrics, useRegistry, useSKUs, useShiftConfig, useShifts, useSubscriptionManager, useSubscriptionManagerSafe, useSupabase, useSupabaseClient, useTargets, useTheme, useThemeConfig, useThreads, useTicketHistory, useVideoConfig, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, videoPrefetchManager, videoPreloader, whatsappService, withAuth, withRegistry, workspaceService };
|
|
39026
|
+
export { ACTION_NAMES, AIAgentView_default as AIAgentView, AudioService, AuthCallback, AuthCallbackView_default as AuthCallbackView, AuthProvider, AuthenticatedFactoryView, AuthenticatedHelpView, AuthenticatedHomeView, AuthenticatedShiftsView, AuthenticatedTargetsView, AuthenticatedWorkspaceHealthView, BackButton, BackButtonMinimal, BarChart, BaseHistoryCalendar, BottlenecksContent, BreakNotificationPopup, CachePrefetchStatus, Card2 as Card, CardContent2 as CardContent, CardDescription2 as CardDescription, CardFooter2 as CardFooter, CardHeader2 as CardHeader, CardTitle2 as CardTitle, CompactWorkspaceHealthCard, CongratulationsOverlay, CycleTimeChart, CycleTimeOverTimeChart, DEFAULT_ANALYTICS_CONFIG, DEFAULT_AUTH_CONFIG, DEFAULT_CONFIG, DEFAULT_DATABASE_CONFIG, DEFAULT_DATE_TIME_CONFIG, DEFAULT_ENDPOINTS_CONFIG, DEFAULT_ENTITY_CONFIG, DEFAULT_SHIFT_CONFIG, DEFAULT_THEME_CONFIG, DEFAULT_VIDEO_CONFIG, DEFAULT_WORKSPACE_CONFIG, DEFAULT_WORKSPACE_POSITIONS, DashboardHeader, DashboardLayout, DashboardOverridesProvider, DashboardProvider, DateDisplay_default as DateDisplay, DateTimeDisplay, DebugAuth, DebugAuthView_default as DebugAuthView, DetailedHealthStatus, EmptyStateMessage, EncouragementOverlay, FactoryView_default as FactoryView, GaugeChart, GridComponentsPlaceholder, HamburgerButton, Header, HealthStatusGrid, HealthStatusIndicator, HelpView_default as HelpView, HomeView_default as HomeView, HourlyOutputChart2 as HourlyOutputChart, ISTTimer_default as ISTTimer, InlineEditableText, KPICard, KPIDetailView_default as KPIDetailView, KPIGrid, KPIHeader, KPISection, KPIsOverviewView_default as KPIsOverviewView, LINE_1_UUID, LINE_2_UUID, LargeOutputProgressChart, LeaderboardDetailView_default as LeaderboardDetailView, Legend6 as Legend, LineChart, LineHistoryCalendar, LineMonthlyHistory, LineMonthlyPdfGenerator, LinePdfExportButton, LinePdfGenerator, LineWhatsAppShareButton, LiveTimer, LoadingInline, LoadingOverlay_default as LoadingOverlay, LoadingPage_default as LoadingPage, LoadingSkeleton, LoadingState, LoginPage, LoginView_default as LoginView, MainLayout, MetricCard_default as MetricCard, NoWorkspaceData, OptifyeAgentClient, OptifyeLogoLoader_default as OptifyeLogoLoader, OutputProgressChart, PageHeader, PieChart4 as PieChart, PrefetchConfigurationError, PrefetchError, PrefetchEvents, PrefetchStatus, PrefetchTimeoutError, ProfileView_default as ProfileView, RegistryProvider, S3ClipsService, S3Service, SKUManagementView, SOPComplianceChart, SSEChatClient, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, ShiftDisplay_default as ShiftDisplay, ShiftsView_default as ShiftsView, SideNavBar, SingleVideoStream_default as SingleVideoStream, Skeleton, SubscriptionManager, SubscriptionManagerProvider, SupabaseProvider, TargetWorkspaceGrid, TargetsView_default as TargetsView, ThreadSidebar, TicketHistory_default as TicketHistory, TicketHistoryService, TimeDisplay_default as TimeDisplay, TimePickerDropdown, VideoCard, VideoGridView, VideoPlayer, VideoPreloader, WORKSPACE_POSITIONS, WhatsAppShareButton, WorkspaceCard, WorkspaceDetailView_default as WorkspaceDetailView, WorkspaceDisplayNameExample, WorkspaceGrid, WorkspaceGridItem, WorkspaceHealthCard, WorkspaceHealthView_default as WorkspaceHealthView, WorkspaceHistoryCalendar, WorkspaceMetricCards, WorkspaceMonthlyDataFetcher, WorkspaceMonthlyPdfGenerator, WorkspacePdfExportButton, WorkspacePdfGenerator, WorkspaceWhatsAppShareButton, actionService, apiUtils, authCoreService, authOTPService, authRateLimitService, checkRateLimit2 as checkRateLimit, clearAllRateLimits2 as clearAllRateLimits, clearRateLimit2 as clearRateLimit, clearS3VideoCache, clearS3VideoFromCache, clearWorkspaceDisplayNamesCache, cn, createStreamProxyHandler, createSupabaseClient, createThrottledReload, dashboardService, deleteThread, forceRefreshWorkspaceDisplayNames, formatDateInZone, formatDateTimeInZone, formatISTDate, formatIdleTime, formatTimeInZone, fromUrlFriendlyName, getAllLineDisplayNames, getAllThreadMessages, getAllWorkspaceDisplayNamesAsync, getAnonClient, getCameraNumber, getCompanyMetricsTableName, getConfigurableShortWorkspaceDisplayName, getConfigurableWorkspaceDisplayName, getConfiguredLineIds, getCoreSessionRecordingProperties, getCoreSessionReplayUrl, getCurrentShift, getCurrentTimeInZone, getDashboardHeaderTimeInZone, getDaysDifferenceInZone, getDefaultCameraStreamUrl, getDefaultLineId, getDefaultTabForWorkspace, getLineDisplayName, getManufacturingInsights, getMetricsTablePrefix, getOperationalDate, getS3SignedUrl, getS3VideoSrc, getShortWorkspaceDisplayName, getShortWorkspaceDisplayNameAsync, getStoredWorkspaceMappings, getSubscriptionManager, getThreadMessages, getUserThreads, getUserThreadsPaginated, getWorkspaceDisplayName, getWorkspaceDisplayNameAsync, getWorkspaceDisplayNamesMap, getWorkspaceFromUrl, getWorkspaceNavigationParams, identifyCoreUser, initializeCoreMixpanel, isLegacyConfiguration, isPrefetchError, isTransitionPeriod, isUrlPermanentlyFailed, isValidFactoryViewConfiguration, isValidLineInfoPayload, isValidPrefetchParams, isValidPrefetchStatus, isValidWorkspaceDetailedMetricsPayload, isValidWorkspaceMetricsPayload, isWorkspaceDisplayNamesLoaded, isWorkspaceDisplayNamesLoading, mergeWithDefaultConfig, migrateLegacyConfiguration, optifyeAgentClient, parseS3Uri, preInitializeWorkspaceDisplayNames, preloadS3Video, preloadS3VideoUrl, preloadS3VideosUrl, preloadVideoUrl, preloadVideosUrl, qualityService, realtimeService, refreshWorkspaceDisplayNames, resetCoreMixpanel, resetFailedUrl, resetSubscriptionManager, s3VideoPreloader, shuffleArray, skuService, startCoreSessionRecording, stopCoreSessionRecording, storeWorkspaceMapping, streamProxyConfig, throttledReloadDashboard, toUrlFriendlyName, trackCoreEvent, trackCorePageView, updateThreadTitle, useActiveBreaks, useAllWorkspaceMetrics, useAnalyticsConfig, useAudioService, useAuth, useAuthConfig, useComponentOverride, useCustomConfig, useDashboardConfig, useDashboardMetrics, useDatabaseConfig, useDateFormatter, useDateTimeConfig, useEndpointsConfig, useEntityConfig, useFactoryOverviewMetrics, useFeatureFlags, useFormatNumber, useHistoricWorkspaceMetrics, useHlsStream, useHlsStreamWithCropping, useHookOverride, useHourEndTimer, useHourlyTargetAchievements, useHourlyTargetMisses, useLeaderboardMetrics, useLineDetailedMetrics, useLineKPIs, useLineMetrics, useLineWorkspaceMetrics, useMessages, useMetrics, useNavigation, useOverrides, usePageOverride, usePrefetchClipCounts, useRealtimeLineMetrics, useRegistry, useSKUs, useShiftConfig, useShifts, useSubscriptionManager, useSubscriptionManagerSafe, useSupabase, useSupabaseClient, useTargets, useTheme, useThemeConfig, useThreads, useTicketHistory, useVideoConfig, useWorkspaceConfig, useWorkspaceDetailedMetrics, useWorkspaceDisplayName, useWorkspaceDisplayNames, useWorkspaceDisplayNamesMap, useWorkspaceHealth, useWorkspaceHealthById, useWorkspaceMetrics, useWorkspaceNavigation, useWorkspaceOperators, videoPrefetchManager, videoPreloader, whatsappService, withAuth, withRegistry, workspaceHealthService, workspaceService };
|