feat(site): implement dynamic site selection dropdown in sidebar and enable multi-site monitoring
This commit is contained in:
1 parent
be6bc14190
commit
4882108068
125 files changed
+5666
-4305
No files matched your search
@@ -40,7 +40,8 @@ const dashCode = readFile('backend/routes/dashboard.js') +
|
||||
readFile('backend/routes/dashboard/geo.js') +
|
||||
readFile('backend/routes/dashboard/tls.js') +
|
||||
readFile('backend/routes/dashboard/telemetry.js') +
|
||||
readFile('backend/routes/dashboard/events.js');
|
||||
readFile('backend/routes/dashboard/events.js') +
|
||||
readFile('backend/routes/dashboard/agents.js');
|
||||
const authCode = readFile('backend/routes/auth.js');
|
||||
|
||||
test('server.js tidak memanggil ingestionService', !serverCode.includes('ingestionService'));
|
||||
@@ -77,7 +78,7 @@ test("scheduler.js mendukung mode 'all'", schedulerCode.includes("
|
||||
test('collector.js export collectAllAgents()', collectorCode.includes('collectAllAgents'));
|
||||
test('collector.js export collectSpecificAgent()', collectorCode.includes('collectSpecificAgent'));
|
||||
test('collector.js tag data dengan agent_uuid', collectorCode.includes('agent_uuid: agentUuid'));
|
||||
test('collector.js tag data dengan site_uuid', collectorCode.includes('site_uuid: SITE_UUID') || collectorCode.includes('site_uuid: SITE_UUID'));
|
||||
test('collector.js tag data dengan site_uuid', collectorCode.includes('site_uuid: SITE_UUID') || collectorCode.includes('site_uuid: SITE_UUID') || collectorCode.includes('site_uuid: siteUuid'));
|
||||
console.log('');
|
||||
|
||||
// ─── TEST GROUP 4: Docker Compose 2 Container Groups ─────────────────────
|
||||
|
||||
+13
-2
@@ -18,11 +18,22 @@ console.log('\n╔════════════════════
|
||||
console.log('║ FINAL REVIEW — Original Request Deliverables ║');
|
||||
console.log('╚══════════════════════════════════════════════════════╝\n');
|
||||
|
||||
const dash = r('backend/routes/dashboard.js');
|
||||
const dash = r('backend/routes/dashboard.js') +
|
||||
r('backend/routes/dashboard/helpers.js') +
|
||||
r('backend/routes/dashboard/summary.js') +
|
||||
r('backend/routes/dashboard/apps.js') +
|
||||
r('backend/routes/dashboard/devices.js') +
|
||||
r('backend/routes/dashboard/flows.js') +
|
||||
r('backend/routes/dashboard/threats.js') +
|
||||
r('backend/routes/dashboard/geo.js') +
|
||||
r('backend/routes/dashboard/tls.js') +
|
||||
r('backend/routes/dashboard/telemetry.js') +
|
||||
r('backend/routes/dashboard/events.js') +
|
||||
r('backend/routes/dashboard/agents.js');
|
||||
const auth = r('backend/routes/auth.js');
|
||||
const server = r('backend/server.js');
|
||||
const sched = r('proxy/scheduler.js');
|
||||
const coll = r('proxy/collector.js');
|
||||
const coll = r('proxy/collector.js') + r('proxy/collectorHelperDpi2.js');
|
||||
const compose = r('docker-compose.yml');
|
||||
const envFile = r('.env.local');
|
||||
const proxyIdx = r('proxy/index.js');
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
// Script to patch api.ts with timeRange support
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const filePath = path.join(__dirname, '..', 'src', 'lib', 'api.ts');
|
||||
let content = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
// 1. Fix fetcher to accept timeRange
|
||||
const oldFetcher = `// Fetcher Function
|
||||
export async function fetcher<T>(endpoint: string): Promise<T> {
|
||||
const url = \`/api/dashboard\${endpoint}\`;
|
||||
// Tambahkan X-View-As-Agent header jika admin dalam mode view-as
|
||||
const viewAsHeaders = getViewAsHeaders();
|
||||
const res = await fetch(url, { headers: viewAsHeaders });
|
||||
if (!res.ok) {
|
||||
throw new Error(\`API error: \${res.status} \${res.statusText}\`);
|
||||
}
|
||||
const json = await res.json();
|
||||
if (!json.ok) {
|
||||
throw new Error(json.message || 'API responded with ok: false');
|
||||
}
|
||||
return json.data;
|
||||
}`;
|
||||
|
||||
const newFetcher = `// Fetcher Function — supports optional timeRange for time-filtered queries
|
||||
export async function fetcher<T>(endpoint: string, timeRange?: string): Promise<T> {
|
||||
// Append timeRange param if provided (appended before X-View-As header)
|
||||
let url = \`/api/dashboard\${endpoint}\`;
|
||||
if (timeRange) {
|
||||
const sep = url.includes('?') ? '&' : '?';
|
||||
url += sep + 'timeRange=' + timeRange;
|
||||
}
|
||||
// X-View-As-Agent header for admin view-as mode
|
||||
const viewAsHeaders = getViewAsHeaders();
|
||||
const res = await fetch(url, { headers: viewAsHeaders });
|
||||
if (!res.ok) {
|
||||
throw new Error(\`API error: \${res.status} \${res.statusText}\`);
|
||||
}
|
||||
const json = await res.json();
|
||||
if (!json.ok) {
|
||||
throw new Error(json.message || 'API responded with ok: false');
|
||||
}
|
||||
return json.data;
|
||||
}`;
|
||||
|
||||
// Normalize CRLF → LF for matching
|
||||
const normalized = content.replace(/\r\n/g, '\n');
|
||||
const oldNorm = oldFetcher.replace(/\r\n/g, '\n');
|
||||
const newNorm = newFetcher.replace(/\r\n/g, '\n');
|
||||
|
||||
if (!normalized.includes(oldNorm)) {
|
||||
console.error('ERROR: Could not find fetcher function in api.ts');
|
||||
console.log('Content around line 170:');
|
||||
console.log(normalized.split('\n').slice(168, 186).join('\n'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const patched = normalized.replace(oldNorm, newNorm);
|
||||
|
||||
// Write back with CRLF (Windows)
|
||||
fs.writeFileSync(filePath, patched.replace(/\n/g, '\r\n'), 'utf8');
|
||||
console.log('✓ api.ts patched successfully');
|
||||
console.log('✓ fetcher now accepts optional timeRange parameter');
|
||||
|
||||
// Verify
|
||||
const verify = fs.readFileSync(filePath, 'utf8');
|
||||
console.log('✓ timeRange in fetcher:', verify.includes('timeRange?: string') ? 'YES' : 'NO');
|
||||
@@ -1,273 +0,0 @@
|
||||
// Script to update all API hooks to accept timeRange parameter
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const filePath = path.join(__dirname, '..', 'src', 'lib', 'api.ts');
|
||||
let content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
||||
|
||||
let count = 0;
|
||||
|
||||
// Helper to replace hook signatures and add timeRange to useEffect deps
|
||||
function patchHook(content, hookSignature, fetchCall, newFetchCall, depArray, newDepArray) {
|
||||
if (content.includes(hookSignature)) {
|
||||
content = content.replace(hookSignature, hookSignature.replace(')', ', timeRange?: string)'));
|
||||
if (fetchCall && newFetchCall) {
|
||||
content = content.replace(fetchCall, newFetchCall);
|
||||
}
|
||||
if (depArray && newDepArray) {
|
||||
content = content.replace(depArray, newDepArray);
|
||||
}
|
||||
count++;
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
// useDashboardSummary
|
||||
content = content.replace(
|
||||
'export function useDashboardSummary() {',
|
||||
'export function useDashboardSummary(timeRange?: string) {'
|
||||
);
|
||||
content = content.replace(
|
||||
" fetcher<DashboardSummary>('/summary')\n .then(setData)",
|
||||
" fetcher<DashboardSummary>('/summary', timeRange)\n .then(setData)"
|
||||
);
|
||||
content = content.replace(
|
||||
" }, []);\n\n return { data, error, isLoading };\n}\n\nexport function useTimeline",
|
||||
" }, [timeRange]);\n\n return { data, error, isLoading };\n}\n\nexport function useTimeline"
|
||||
);
|
||||
|
||||
// useTimeline
|
||||
content = content.replace(
|
||||
'export function useTimeline(points: number = 60) {',
|
||||
'export function useTimeline(points: number = 60, timeRange?: string) {'
|
||||
);
|
||||
content = content.replace(
|
||||
" fetcher<TimelinePoint[]>(`/timeline?points=${points}`)",
|
||||
" fetcher<TimelinePoint[]>(`/timeline?points=${points}`, timeRange)"
|
||||
);
|
||||
content = content.replace(
|
||||
" }, [points]);\n\n return { data, error, isLoading };\n}\n\nexport function useTopApps",
|
||||
" }, [points, timeRange]);\n\n return { data, error, isLoading };\n}\n\nexport function useTopApps"
|
||||
);
|
||||
|
||||
// useTopApps
|
||||
content = content.replace(
|
||||
'export function useTopApps(limit: number = 10) {',
|
||||
'export function useTopApps(limit: number = 10, timeRange?: string) {'
|
||||
);
|
||||
content = content.replace(
|
||||
" fetcher<AppStat[]>(`/apps?limit=${limit}`)",
|
||||
" fetcher<AppStat[]>(`/apps?limit=${limit}`, timeRange)"
|
||||
);
|
||||
content = content.replace(
|
||||
" }, [limit]);\n\n return { data, error, isLoading };\n}\n\nexport function useTopProtocols",
|
||||
" }, [limit, timeRange]);\n\n return { data, error, isLoading };\n}\n\nexport function useTopProtocols"
|
||||
);
|
||||
|
||||
// useTopProtocols
|
||||
content = content.replace(
|
||||
'export function useTopProtocols() {',
|
||||
'export function useTopProtocols(timeRange?: string) {'
|
||||
);
|
||||
content = content.replace(
|
||||
" fetcher<ProtocolStat[]>('/protocols')",
|
||||
" fetcher<ProtocolStat[]>('/protocols', timeRange)"
|
||||
);
|
||||
content = content.replace(
|
||||
" }, []);\n\n return { data, error, isLoading };\n}\n\nexport function useDevices",
|
||||
" }, [timeRange]);\n\n return { data, error, isLoading };\n}\n\nexport function useDevices"
|
||||
);
|
||||
|
||||
// useDevices
|
||||
content = content.replace(
|
||||
'export function useDevices(limit: number = 50) {',
|
||||
'export function useDevices(limit: number = 50, timeRange?: string) {'
|
||||
);
|
||||
content = content.replace(
|
||||
" fetcher<DeviceStat[]>(`/devices?limit=${limit}`)",
|
||||
" fetcher<DeviceStat[]>(`/devices?limit=${limit}`, timeRange)"
|
||||
);
|
||||
content = content.replace(
|
||||
" }, [limit]);\n\n return { data, error, isLoading };\n}\n\nexport function useThreats",
|
||||
" }, [limit, timeRange]);\n\n return { data, error, isLoading };\n}\n\nexport function useThreats"
|
||||
);
|
||||
|
||||
// useThreats
|
||||
content = content.replace(
|
||||
'export function useThreats(limit: number = 20) {',
|
||||
'export function useThreats(limit: number = 20, timeRange?: string) {'
|
||||
);
|
||||
content = content.replace(
|
||||
" fetcher<ThreatStat[]>(`/threats?limit=${limit}`)",
|
||||
" fetcher<ThreatStat[]>(`/threats?limit=${limit}`, timeRange)"
|
||||
);
|
||||
content = content.replace(
|
||||
" }, [limit]);\n\n return { data, error, isLoading };\n}\n\nexport function useAppCategories",
|
||||
" }, [limit, timeRange]);\n\n return { data, error, isLoading };\n}\n\nexport function useAppCategories"
|
||||
);
|
||||
|
||||
// useAppCategories
|
||||
content = content.replace(
|
||||
'export function useAppCategories() {',
|
||||
'export function useAppCategories(timeRange?: string) {'
|
||||
);
|
||||
content = content.replace(
|
||||
" fetcher<AppCategoryStat[]>('/app-categories')",
|
||||
" fetcher<AppCategoryStat[]>('/app-categories', timeRange)"
|
||||
);
|
||||
content = content.replace(
|
||||
" }, []);\n\n return { data, error, isLoading };\n}\n\nexport function useContinents",
|
||||
" }, [timeRange]);\n\n return { data, error, isLoading };\n}\n\nexport function useContinents"
|
||||
);
|
||||
|
||||
// useContinents
|
||||
content = content.replace(
|
||||
'export function useContinents() {',
|
||||
'export function useContinents(timeRange?: string) {'
|
||||
);
|
||||
content = content.replace(
|
||||
" fetcher<ContinentStat[]>('/continents')",
|
||||
" fetcher<ContinentStat[]>('/continents', timeRange)"
|
||||
);
|
||||
content = content.replace(
|
||||
" }, []);\n\n return { data, error, isLoading };\n}\n\nexport function useFlows",
|
||||
" }, [timeRange]);\n\n return { data, error, isLoading };\n}\n\nexport function useFlows"
|
||||
);
|
||||
|
||||
// useFlows
|
||||
content = content.replace(
|
||||
'export function useFlows(limit: number = 50) {',
|
||||
'export function useFlows(limit: number = 50, timeRange?: string) {'
|
||||
);
|
||||
content = content.replace(
|
||||
" fetcher<FlowStat[]>(`/flows?limit=${limit}`)",
|
||||
" fetcher<FlowStat[]>(`/flows?limit=${limit}`, timeRange)"
|
||||
);
|
||||
content = content.replace(
|
||||
" }, [limit]);\n\n return { data, error, isLoading };\n}\n\nexport function useCountries",
|
||||
" }, [limit, timeRange]);\n\n return { data, error, isLoading };\n}\n\nexport function useCountries"
|
||||
);
|
||||
|
||||
// useCountries
|
||||
content = content.replace(
|
||||
'export function useCountries(limit: number = 50) {',
|
||||
'export function useCountries(limit: number = 50, timeRange?: string) {'
|
||||
);
|
||||
content = content.replace(
|
||||
" fetcher<CountryStat[]>(`/countries?limit=${limit}`)",
|
||||
" fetcher<CountryStat[]>(`/countries?limit=${limit}`, timeRange)"
|
||||
);
|
||||
content = content.replace(
|
||||
" }, [limit]);\n\n return { data, error, isLoading };\n}\n\nexport function useDNS",
|
||||
" }, [limit, timeRange]);\n\n return { data, error, isLoading };\n}\n\nexport function useDNS"
|
||||
);
|
||||
|
||||
// useDNS
|
||||
content = content.replace(
|
||||
'export function useDNS(limit: number = 50) {',
|
||||
'export function useDNS(limit: number = 50, timeRange?: string) {'
|
||||
);
|
||||
content = content.replace(
|
||||
" fetcher<DnsStat[]>(`/dns?limit=${limit}`)",
|
||||
" fetcher<DnsStat[]>(`/dns?limit=${limit}`, timeRange)"
|
||||
);
|
||||
content = content.replace(
|
||||
" }, [limit]);\n\n return { data, error, isLoading };\n}\n\nexport function useEvents",
|
||||
" }, [limit, timeRange]);\n\n return { data, error, isLoading };\n}\n\nexport function useEvents"
|
||||
);
|
||||
|
||||
// useEvents
|
||||
content = content.replace(
|
||||
'export function useEvents(limit: number = 50) {',
|
||||
'export function useEvents(limit: number = 50, timeRange?: string) {'
|
||||
);
|
||||
content = content.replace(
|
||||
" fetcher<EventStat[]>(`/events?limit=${limit}`)",
|
||||
" fetcher<EventStat[]>(`/events?limit=${limit}`, timeRange)"
|
||||
);
|
||||
content = content.replace(
|
||||
" }, [limit]);\n\n return { data, error, isLoading };\n}\n\nexport function useTlsVersions",
|
||||
" }, [limit, timeRange]);\n\n return { data, error, isLoading };\n}\n\nexport function useTlsVersions"
|
||||
);
|
||||
|
||||
// useTlsVersions
|
||||
content = content.replace(
|
||||
'export function useTlsVersions(limit: number = 10) {',
|
||||
'export function useTlsVersions(limit: number = 10, timeRange?: string) {'
|
||||
);
|
||||
content = content.replace(
|
||||
" fetcher<TlsVersionStat[]>(`/tls-versions?limit=${limit}`)",
|
||||
" fetcher<TlsVersionStat[]>(`/tls-versions?limit=${limit}`, timeRange)"
|
||||
);
|
||||
content = content.replace(
|
||||
" }, [limit]);\n\n return { data, error, isLoading };\n}\n\nexport function useTlsCiphers",
|
||||
" }, [limit, timeRange]);\n\n return { data, error, isLoading };\n}\n\nexport function useTlsCiphers"
|
||||
);
|
||||
|
||||
// useTlsCiphers
|
||||
content = content.replace(
|
||||
'export function useTlsCiphers(limit: number = 15) {',
|
||||
'export function useTlsCiphers(limit: number = 15, timeRange?: string) {'
|
||||
);
|
||||
content = content.replace(
|
||||
" fetcher<TlsCipherStat[]>(`/tls-ciphers?limit=${limit}`)",
|
||||
" fetcher<TlsCipherStat[]>(`/tls-ciphers?limit=${limit}`, timeRange)"
|
||||
);
|
||||
content = content.replace(
|
||||
" }, [limit]);\n\n return { data, error, isLoading };\n}\n\nexport function useTlsSecurity",
|
||||
" }, [limit, timeRange]);\n\n return { data, error, isLoading };\n}\n\nexport function useTlsSecurity"
|
||||
);
|
||||
|
||||
// useTlsSecurity
|
||||
content = content.replace(
|
||||
'export function useTlsSecurity(limit: number = 10) {',
|
||||
'export function useTlsSecurity(limit: number = 10, timeRange?: string) {'
|
||||
);
|
||||
content = content.replace(
|
||||
" fetcher<TlsSecurityStat[]>(`/tls-security?limit=${limit}`)",
|
||||
" fetcher<TlsSecurityStat[]>(`/tls-security?limit=${limit}`, timeRange)"
|
||||
);
|
||||
content = content.replace(
|
||||
" }, [limit]);\n\n return { data, error, isLoading };\n}\n\nexport function useIntelligenceStats",
|
||||
" }, [limit, timeRange]);\n\n return { data, error, isLoading };\n}\n\nexport function useIntelligenceStats"
|
||||
);
|
||||
|
||||
// useIntelligenceStats
|
||||
content = content.replace(
|
||||
'export function useIntelligenceStats() {',
|
||||
'export function useIntelligenceStats(timeRange?: string) {'
|
||||
);
|
||||
content = content.replace(
|
||||
" fetcher<IntelligenceStats>('/intelligence/stats')",
|
||||
" fetcher<IntelligenceStats>('/intelligence/stats', timeRange)"
|
||||
);
|
||||
content = content.replace(
|
||||
" }, []);\n\n return { data, error, isLoading };\n}\n\n// ─── LOOKUP APPLICATIONS",
|
||||
" }, [timeRange]);\n\n return { data, error, isLoading };\n}\n\n// ─── LOOKUP APPLICATIONS"
|
||||
);
|
||||
|
||||
// Write back with CRLF
|
||||
fs.writeFileSync(filePath, content.replace(/\n/g, '\r\n'), 'utf8');
|
||||
|
||||
// Verify
|
||||
const verify = fs.readFileSync(filePath, 'utf8');
|
||||
const hooks = [
|
||||
'useDashboardSummary', 'useTimeline', 'useTopApps', 'useTopProtocols',
|
||||
'useDevices', 'useThreats', 'useAppCategories', 'useContinents',
|
||||
'useFlows', 'useCountries', 'useDNS', 'useEvents',
|
||||
'useTlsVersions', 'useTlsCiphers', 'useTlsSecurity', 'useIntelligenceStats'
|
||||
];
|
||||
|
||||
console.log('\n=== api.ts Patch Results ===');
|
||||
let allOk = true;
|
||||
for (const hook of hooks) {
|
||||
const pattern = hook + '(';
|
||||
const idx = verify.indexOf('export function ' + hook);
|
||||
if (idx < 0) { console.log('✗ MISSING:', hook); allOk = false; continue; }
|
||||
const slice = verify.slice(idx, idx + 80);
|
||||
const hasTimeRange = slice.includes('timeRange');
|
||||
console.log((hasTimeRange ? '✓' : '✗') + ' ' + hook + ' timeRange param');
|
||||
if (!hasTimeRange) allOk = false;
|
||||
}
|
||||
|
||||
console.log('\n' + (allOk ? '✓ ALL HOOKS UPDATED' : '✗ SOME HOOKS MISSING'));
|
||||
@@ -1,92 +0,0 @@
|
||||
// Script to update page files to use api-with-context instead of api
|
||||
// This ensures all page hooks automatically use timeRange from TimeFilterContext
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Files that import hooks from @/lib/api (not fetch functions, just hooks)
|
||||
const pageFiles = [
|
||||
'src/app/(dashboard)/page.tsx',
|
||||
'src/app/(dashboard)/threats/page.tsx',
|
||||
'src/app/(dashboard)/flows/page.tsx',
|
||||
'src/app/(dashboard)/devices/page.tsx',
|
||||
'src/app/(dashboard)/apps/page.tsx',
|
||||
'src/app/(dashboard)/dns/page.tsx',
|
||||
'src/app/(dashboard)/events/page.tsx',
|
||||
'src/app/(dashboard)/geography/page.tsx',
|
||||
'src/app/(dashboard)/intelligence/page.tsx',
|
||||
'src/app/(dashboard)/network-intelligence/page.tsx',
|
||||
'src/app/(dashboard)/security-audit/page.tsx',
|
||||
];
|
||||
|
||||
// Hooks that are context-aware (exist in api-with-context.ts)
|
||||
const contextHooks = [
|
||||
'useDashboardSummary', 'useTimeline', 'useTopApps', 'useTopProtocols',
|
||||
'useDevices', 'useThreats', 'useAppCategories', 'useContinents',
|
||||
'useFlows', 'useCountries', 'useDNS', 'useEvents',
|
||||
'useTlsVersions', 'useTlsCiphers', 'useTlsSecurity', 'useIntelligenceStats',
|
||||
];
|
||||
|
||||
// Types/fetch functions that should remain in @/lib/api
|
||||
const apiOnly = [
|
||||
'fetcher', 'fetchAgentDetails', 'fetchDeviceDetails', 'fetchAppDetails',
|
||||
'fetchSecurityDevices', 'useLookupApplications',
|
||||
// Type re-exports
|
||||
'DashboardSummary', 'TimelinePoint', 'AppStat', 'ProtocolStat', 'DeviceStat',
|
||||
'ThreatStat', 'AppCategoryStat', 'ContinentStat', 'FlowStat', 'CountryStat',
|
||||
'DnsStat', 'EventStat', 'TlsVersionStat', 'TlsCipherStat', 'TlsSecurityStat',
|
||||
'IntelligenceStats', 'AgentDetails', 'DeviceDetails', 'AppDetails', 'SecurityDevice',
|
||||
];
|
||||
|
||||
let totalChanges = 0;
|
||||
|
||||
for (const relPath of pageFiles) {
|
||||
const filePath = path.join(process.cwd(), relPath);
|
||||
if (!fs.existsSync(filePath)) {
|
||||
console.log('SKIP (not found):', relPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
let content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
||||
const original = content;
|
||||
|
||||
// Find all imports from @/lib/api (not api-with-context, not api-advanced)
|
||||
const importRegex = /import \{([^}]+)\} from ["']@\/lib\/api["'];?/g;
|
||||
let match;
|
||||
|
||||
while ((match = importRegex.exec(content)) !== null) {
|
||||
const fullImport = match[0];
|
||||
const importedItems = match[1].split(',').map(s => s.trim()).filter(Boolean);
|
||||
|
||||
// Categorize: hooks go to api-with-context, types/fetchers stay in api
|
||||
const hooksToMove = importedItems.filter(item =>
|
||||
contextHooks.includes(item) || contextHooks.includes(item.split(' ')[0]) // handle "as" aliases
|
||||
);
|
||||
const typesToKeep = importedItems.filter(item =>
|
||||
!contextHooks.includes(item) && !contextHooks.includes(item.split(' ')[0])
|
||||
);
|
||||
|
||||
if (hooksToMove.length === 0) continue; // nothing to move
|
||||
|
||||
let newImports = '';
|
||||
|
||||
if (typesToKeep.length > 0) {
|
||||
newImports += `import { ${typesToKeep.join(', ')} } from "@/lib/api";\n`;
|
||||
}
|
||||
if (hooksToMove.length > 0) {
|
||||
newImports += `import { ${hooksToMove.join(', ')} } from "@/lib/api-with-context";`;
|
||||
}
|
||||
|
||||
content = content.replace(fullImport, newImports);
|
||||
totalChanges++;
|
||||
}
|
||||
|
||||
if (content !== original) {
|
||||
fs.writeFileSync(filePath, content.replace(/\n/g, '\r\n'), 'utf8');
|
||||
console.log('✓ Updated:', relPath);
|
||||
} else {
|
||||
console.log(' No changes needed:', relPath);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`\nTotal files updated: ${totalChanges}`);
|
||||
Reference in new issue
Block a user