Last active
May 14, 2025 22:56
-
-
Save davidlu1001/47fcb30b7f92a649ba897c180ffbc6d9 to your computer and use it in GitHub Desktop.
immuta-create-groups.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This script automates the creation of Immuta Groups from CSV or Excel files | |
// To use: | |
// 1. Navigate to the Immuta Groups page (People > Groups) | |
// 2. Open browser's developer console (F12 or right-click > Inspect > Console) | |
// 3. Paste this entire script and press Enter | |
// 4. Follow the on-screen prompts to select your CSV or Excel file | |
// Global variables | |
let isOfflineMode = false; | |
// First, let's try to load required libraries for handling Excel files | |
async function loadLibraries() { | |
// Check if the library is already loaded | |
if (window.XLSX) return true; | |
try { | |
// Load SheetJS (xlsx) library for Excel support | |
const script = document.createElement('script'); | |
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js'; | |
script.async = true; | |
document.head.appendChild(script); | |
// Wait for the script to load | |
return new Promise((resolve, reject) => { | |
script.onload = () => resolve(true); | |
script.onerror = () => { | |
console.warn('Failed to load XLSX library. Working in offline mode.'); | |
isOfflineMode = true; | |
resolve(false); | |
}; | |
// Timeout after 5 seconds | |
setTimeout(() => { | |
console.warn('Timeout loading XLSX library. Working in offline mode.'); | |
isOfflineMode = true; | |
resolve(false); | |
}, 5000); | |
}); | |
} catch (error) { | |
console.warn('Error loading XLSX library:', error.message); | |
isOfflineMode = true; | |
return false; | |
} | |
} | |
// Function to read CSV file | |
function parseCSV(text) { | |
// Split by newline and filter out empty lines | |
return text.split(/\r?\n/) | |
.map(line => line.trim()) | |
.filter(line => line.length > 0) | |
// Handle CSV with headers by taking the first column if comma-separated | |
.map(line => { | |
if (line.includes(',')) { | |
return line.split(',')[0].trim().replace(/^["'](.*)["']$/, '$1'); | |
} | |
return line; | |
}) | |
.filter((value, index, self) => self.indexOf(value) === index); // Remove duplicates | |
} | |
// Function to read Excel file | |
function parseExcel(data) { | |
try { | |
const workbook = XLSX.read(data, { type: 'array' }); | |
const firstSheetName = workbook.SheetNames[0]; | |
const worksheet = workbook.Sheets[firstSheetName]; | |
// Convert to array of objects | |
const rows = XLSX.utils.sheet_to_json(worksheet, { header: 1 }); | |
// Take first column values, skip header row if it exists | |
const hasHeader = typeof rows[0][0] === 'string' && | |
(rows[0][0].toLowerCase().includes('name') || | |
rows[0][0].toLowerCase().includes('group')); | |
const startRow = hasHeader ? 1 : 0; | |
// Extract group names from first column | |
return rows.slice(startRow) | |
.map(row => row[0]) | |
.filter(name => name && String(name).trim().length > 0) | |
.map(name => String(name).trim()) | |
.filter((value, index, self) => self.indexOf(value) === index); // Remove duplicates | |
} catch (error) { | |
console.error('Error parsing Excel file:', error); | |
return null; | |
} | |
} | |
// Simple function to try and extract text from binary Excel file in offline mode | |
function attemptSimpleExcelParse(arrayBuffer) { | |
try { | |
// Convert array buffer to a regular string | |
const textDecoder = new TextDecoder('utf-8'); | |
const text = textDecoder.decode(arrayBuffer); | |
// Look for patterns that might indicate cell values | |
const potentialNames = []; | |
// Basic extraction using regex to find potential group names | |
// This is a simple approach and won't work for all Excel files | |
const stringMatches = text.match(/[a-zA-Z0-9_\-\s]+[^\x00-\x7F]{1,4}/g) || []; | |
for (const match of stringMatches) { | |
// Clean up the match | |
const cleaned = match.replace(/[^\x20-\x7E]/g, '').trim(); | |
if (cleaned && cleaned.length > 1 && !/^\d+$/.test(cleaned)) { | |
potentialNames.push(cleaned); | |
} | |
} | |
// Remove duplicates | |
const uniqueNames = [...new Set(potentialNames)]; | |
// If we found some potential names, return them | |
if (uniqueNames.length > 0) { | |
return uniqueNames; | |
} | |
return null; | |
} catch (error) { | |
console.error('Error in simple Excel parsing:', error); | |
return null; | |
} | |
} | |
// Function to read file content (CSV or Excel) | |
async function readFile(file) { | |
return new Promise((resolve, reject) => { | |
const reader = new FileReader(); | |
reader.onload = async (event) => { | |
try { | |
const result = event.target.result; | |
if (file.name.toLowerCase().endsWith('.csv')) { | |
// For CSV files | |
const text = event.target.result; | |
const groupNames = parseCSV(text); | |
resolve(groupNames); | |
} else if (file.name.toLowerCase().endsWith('.xlsx') || file.name.toLowerCase().endsWith('.xls')) { | |
// For Excel files | |
if (!isOfflineMode) { | |
// Try to use the XLSX library if available | |
await loadLibraries(); | |
if (window.XLSX) { | |
const data = new Uint8Array(result); | |
const groupNames = parseExcel(data); | |
if (groupNames) { | |
resolve(groupNames); | |
return; | |
} | |
} | |
} | |
// If we're in offline mode or the XLSX parsing failed | |
const simpleResults = attemptSimpleExcelParse(result); | |
if (simpleResults && simpleResults.length > 0) { | |
// We managed to extract something | |
console.log('Using simplified Excel parsing in offline mode'); | |
resolve(simpleResults); | |
} else { | |
// Show the file conversion instructions | |
showFileConversionInstructions(); | |
reject(new Error('Unable to parse Excel file in offline mode. Please convert to CSV first.')); | |
} | |
} else { | |
reject(new Error('Unsupported file format. Please use CSV or Excel file.')); | |
} | |
} catch (error) { | |
reject(new Error(`Error parsing file: ${error.message}`)); | |
} | |
}; | |
reader.onerror = () => reject(new Error('Error reading file')); | |
if (file.name.toLowerCase().endsWith('.csv')) { | |
reader.readAsText(file); | |
} else { | |
reader.readAsArrayBuffer(file); | |
} | |
}); | |
} | |
// Function to show instructions for converting Excel to CSV | |
function showFileConversionInstructions() { | |
// Create styled container for instructions | |
const container = document.createElement('div'); | |
container.style.cssText = ` | |
position: fixed; | |
top: 50%; | |
left: 50%; | |
transform: translate(-50%, -50%); | |
z-index: 10000; | |
background: white; | |
padding: 25px; | |
border-radius: 8px; | |
box-shadow: 0 4px 20px rgba(0,0,0,0.2); | |
max-width: 600px; | |
width: 90%; | |
font-family: Arial, sans-serif; | |
line-height: 1.5; | |
`; | |
const content = ` | |
<h2 style="margin-top: 0; color: #d9534f;">Excel File Cannot Be Processed</h2> | |
<p>You appear to be in an offline or restricted environment where the Excel processing library cannot be loaded.</p> | |
<p><strong>Please convert your Excel file to CSV format and try again:</strong></p> | |
<ol> | |
<li>Open your Excel file</li> | |
<li>Go to File > Save As</li> | |
<li>Select "CSV (Comma delimited) (*.csv)" from the file type dropdown</li> | |
<li>Save the file</li> | |
<li>Return to Immuta and upload the CSV file instead</li> | |
</ol> | |
<p><strong>Important:</strong> Make sure your group names are in the first column of your spreadsheet before converting.</p> | |
<div style="text-align: right; margin-top: 20px;"> | |
<button id="close-instructions" style="padding: 8px 16px; background: #5bc0de; color: white; border: none; border-radius: 4px; cursor: pointer;">Close</button> | |
</div> | |
`; | |
container.innerHTML = content; | |
document.body.appendChild(container); | |
// Add event listener to close button | |
document.getElementById('close-instructions').addEventListener('click', () => { | |
container.remove(); | |
}); | |
} | |
// Function to find People menu in the left navigation panel | |
function findPeopleMenu() { | |
// Try different strategies to find the People menu | |
// 1. Try by specific selectors | |
const peopleSelectors = [ | |
'button[aria-label="People"]', | |
'button[data-track-id="people-admin-nav"]', | |
'a[aria-label="People"]', | |
'a[href*="/admin/people"]', | |
'a[href*="/people"]', | |
'li[data-section="people"]', | |
'.people-section', | |
]; | |
for (const selector of peopleSelectors) { | |
const element = document.querySelector(selector); | |
if (element && isElementVisible(element)) { | |
return element; | |
} | |
} | |
// 2. Try by text content in any element | |
const allElements = document.querySelectorAll('a, button, div, span, li'); | |
for (const el of allElements) { | |
if (el.textContent.trim() === 'People' && isElementVisible(el)) { | |
return el; | |
} | |
} | |
// 3. Try elements containing icon + text | |
const potentialPeopleElements = Array.from(allElements).filter(el => { | |
return (el.textContent.includes('People') || | |
el.innerHTML.includes('People')) && | |
isElementVisible(el); | |
}); | |
if (potentialPeopleElements.length > 0) { | |
return potentialPeopleElements[0]; | |
} | |
// 4. Look for any element in the left sidebar that might be the People section | |
const sidebarElements = document.querySelectorAll('.sidebar a, .sidebar button, .sidebar li, [class*="sidebar"] a, [class*="sidebar"] button, [class*="sidebar"] li, [class*="nav"] a, [class*="nav"] button, [class*="nav"] li'); | |
for (const el of sidebarElements) { | |
if (el.textContent.includes('People') && isElementVisible(el)) { | |
return el; | |
} | |
} | |
return null; | |
} | |
// Function to find Groups tab | |
function findGroupsTab() { | |
// Try different strategies to find the Groups tab | |
// 1. Try by specific selectors | |
const groupsSelectors = [ | |
'a[href*="/admin/groups"]', | |
'a[href*="/groups"]', | |
'button[data-track-id="groups-tab"]', | |
'li[data-tab="groups"]', | |
'.groups-tab', | |
]; | |
for (const selector of groupsSelectors) { | |
const element = document.querySelector(selector); | |
if (element && isElementVisible(element)) { | |
return element; | |
} | |
} | |
// 2. Try by text content in any element | |
const allElements = document.querySelectorAll('a, button, div, span, li'); | |
for (const el of allElements) { | |
if (el.textContent.trim() === 'Groups' && isElementVisible(el)) { | |
return el; | |
} | |
} | |
// 3. Try elements containing the text | |
const potentialGroupsElements = Array.from(allElements).filter(el => { | |
return (el.textContent.includes('Groups') || | |
el.innerHTML.includes('Groups')) && | |
isElementVisible(el); | |
}); | |
if (potentialGroupsElements.length > 0) { | |
return potentialGroupsElements[0]; | |
} | |
return null; | |
} | |
// Function to find the New Group button | |
function findNewGroupButton() { | |
// Try different selectors to find the button | |
const selectors = [ | |
'button#add-group', | |
'button[data-track-id="add-group"]', | |
'button[id*="add-group"]', | |
'button[class*="add-group"]', | |
'button.dp-button--solid.dp-button--sm.dp-button--icon', | |
'button[class*="dp-button--solid"][class*="dp-button--sm"]', | |
]; | |
// Try each selector | |
for (const selector of selectors) { | |
const button = document.querySelector(selector); | |
if (button && isElementVisible(button)) return button; | |
} | |
// Try finding by text content | |
const allButtons = Array.from(document.querySelectorAll('button')); | |
// First, try to find a button with exact text "New Group" | |
let button = allButtons.find(b => b.textContent.trim() === 'New Group' && isElementVisible(b)); | |
if (button) return button; | |
// Try with "Add Group" | |
button = allButtons.find(b => b.textContent.trim() === 'Add Group' && isElementVisible(b)); | |
if (button) return button; | |
// Then look for buttons containing these texts | |
button = allButtons.find(b => | |
(b.textContent.includes('New Group') || | |
b.textContent.includes('Add Group') || | |
b.innerHTML.includes('New Group') || | |
b.innerHTML.includes('Add Group')) && | |
isElementVisible(b) | |
); | |
if (button) return button; | |
// Look for buttons with icons that might be the add button | |
button = allButtons.find(b => { | |
return isElementVisible(b) && ( | |
b.classList.contains('dp-button--icon') || | |
b.classList.contains('dp-button--add') || | |
b.querySelector('i[class*="add"]') || | |
b.querySelector('span[class*="add"]') | |
); | |
}); | |
if (button) return button; | |
// Last resort - look for a span with a plus icon inside a button | |
const plusButtons = Array.from(document.querySelectorAll('button')).filter(b => { | |
return isElementVisible(b) && (b.textContent.includes('+') || b.innerHTML.includes('add')); | |
}); | |
if (plusButtons.length > 0) { | |
return plusButtons[0]; | |
} | |
return null; | |
} | |
// Function to find the input field in the dialog | |
function findGroupNameInput() { | |
// Try different selectors to find the input field | |
const selectors = [ | |
'input[name="newGroup"]', | |
'input[id*="group-name"]', | |
'input[placeholder*="Group Name"]', | |
'input[class*="dp-textfield--input"]', | |
'input[required]', | |
]; | |
// Try each selector | |
for (const selector of selectors) { | |
const inputs = document.querySelectorAll(selector); | |
if (inputs.length === 1) return inputs[0]; | |
if (inputs.length > 1) { | |
// If multiple inputs, try to find the one that's visible | |
for (const input of inputs) { | |
if (isElementVisible(input)) return input; | |
} | |
} | |
} | |
// Look for any input in a dialog/modal | |
const dialog = document.querySelector('.dp-dialog, .dp-modal, [class*="dialog"], [class*="modal"]'); | |
if (dialog) { | |
const inputs = dialog.querySelectorAll('input[type="text"]'); | |
for (const input of inputs) { | |
if (isElementVisible(input)) return input; | |
} | |
} | |
// Last resort - try any visible text input | |
const allInputs = document.querySelectorAll('input[type="text"]'); | |
for (const input of allInputs) { | |
if (isElementVisible(input)) return input; | |
} | |
return null; | |
} | |
// Function to find the Save button in the dialog | |
function findSaveButton() { | |
// Try different selectors to find the save button | |
const selectors = [ | |
'button#save-button', | |
'button[data-track-id="save"]', | |
'button[id*="save"]', | |
'button[class*="save"]', | |
'button[type="submit"]', | |
]; | |
// Try each selector | |
for (const selector of selectors) { | |
const button = document.querySelector(selector); | |
if (button && isElementVisible(button)) return button; | |
} | |
// Try finding by text content | |
const allButtons = Array.from(document.querySelectorAll('button')); | |
// First, try to find a button with exact text "Save" | |
let button = allButtons.find(b => b.textContent.trim() === 'Save' && isElementVisible(b)); | |
if (button) return button; | |
// Look for buttons containing "Save" | |
button = allButtons.find(b => | |
isElementVisible(b) && ( | |
b.textContent.includes('Save') || | |
b.innerHTML.includes('Save') | |
) | |
); | |
if (button) return button; | |
// Look for dialog buttons | |
const dialog = document.querySelector('.dp-dialog, .dp-modal, [class*="dialog"], [class*="modal"]'); | |
if (dialog) { | |
const buttons = Array.from(dialog.querySelectorAll('button')).filter(b => isElementVisible(b)); | |
// The save button is often the right-most or last button in a dialog | |
if (buttons.length >= 2) return buttons[buttons.length - 1]; | |
if (buttons.length === 1) return buttons[0]; | |
} | |
return null; | |
} | |
// Function to find the Cancel button in the dialog | |
function findCancelButton() { | |
// Try different selectors to find the cancel button | |
const selectors = [ | |
'button#cancel-button', | |
'button[data-track-id="cancel"]', | |
'button[id*="cancel"]', | |
'button[class*="cancel"]', | |
]; | |
// Try each selector | |
for (const selector of selectors) { | |
const button = document.querySelector(selector); | |
if (button && isElementVisible(button)) return button; | |
} | |
// Try finding by text content | |
const allButtons = Array.from(document.querySelectorAll('button')); | |
// First, try to find a button with exact text "Cancel" | |
let button = allButtons.find(b => b.textContent.trim() === 'Cancel' && isElementVisible(b)); | |
if (button) return button; | |
// Look for buttons containing "Cancel" | |
button = allButtons.find(b => | |
isElementVisible(b) && ( | |
b.textContent.includes('Cancel') || | |
b.innerHTML.includes('Cancel') | |
) | |
); | |
if (button) return button; | |
// Look for dialog buttons | |
const dialog = document.querySelector('.dp-dialog, .dp-modal, [class*="dialog"], [class*="modal"]'); | |
if (dialog) { | |
const buttons = Array.from(dialog.querySelectorAll('button')).filter(b => isElementVisible(b)); | |
// The cancel button is often the left-most or first button in a dialog | |
if (buttons.length >= 2) return buttons[0]; | |
} | |
return null; | |
} | |
// Helper function to check if an element is visible | |
function isElementVisible(element) { | |
if (!element) return false; | |
// Check computed style | |
const style = window.getComputedStyle(element); | |
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') { | |
return false; | |
} | |
// Check dimensions | |
const rect = element.getBoundingClientRect(); | |
if (rect.width <= 0 || rect.height <= 0) { | |
return false; | |
} | |
// Check if element is within viewport | |
if (rect.bottom < 0 || rect.top > window.innerHeight || | |
rect.right < 0 || rect.left > window.innerWidth) { | |
return false; | |
} | |
return true; | |
} | |
// Helper function to check if a dialog is open | |
function isDialogOpen() { | |
const possibleDialogs = document.querySelectorAll('.dp-dialog, .dp-modal, [class*="dialog"], [class*="modal"]'); | |
for (const dialog of possibleDialogs) { | |
if (isElementVisible(dialog)) { | |
return true; | |
} | |
} | |
return false; | |
} | |
// Function to wait for an element to be visible | |
async function waitForElement(findElementFn, timeoutMs = 5000, intervalMs = 100) { | |
const startTime = Date.now(); | |
while (Date.now() - startTime < timeoutMs) { | |
const element = findElementFn(); | |
if (element && isElementVisible(element)) { | |
return element; | |
} | |
await new Promise(resolve => setTimeout(resolve, intervalMs)); | |
} | |
return null; | |
} | |
// Function to navigate to the Groups page | |
async function navigateToGroupsPage() { | |
console.log('Navigating to the Groups page...'); | |
// First, try to find and click the People menu in the left sidebar | |
const peopleMenu = await waitForElement(findPeopleMenu, 5000); | |
if (!peopleMenu) { | |
throw new Error('People menu not found in the left navigation panel'); | |
} | |
console.log('Clicking People menu...'); | |
peopleMenu.click(); | |
// Wait for the People section to load | |
await new Promise(resolve => setTimeout(resolve, 2000)); | |
// Then, find and click the Groups tab | |
const groupsTab = await waitForElement(findGroupsTab, 5000); | |
if (!groupsTab) { | |
throw new Error('Groups tab not found in the People section'); | |
} | |
console.log('Clicking Groups tab...'); | |
groupsTab.click(); | |
// Wait for the Groups page to load | |
await new Promise(resolve => setTimeout(resolve, 2000)); | |
// Verify we're on the groups page by checking for the New Group button | |
const newGroupButton = await waitForElement(findNewGroupButton, 5000); | |
if (!newGroupButton) { | |
throw new Error('New Group button not found. Navigation to Groups page may have failed.'); | |
} | |
console.log('Successfully navigated to the Groups page'); | |
return true; | |
} | |
// Function to create a group with given name | |
async function createGroup(groupName) { | |
console.log(`Attempting to create group: "${groupName}"`); | |
try { | |
// Always navigate to the Groups page first for each group creation | |
// This is critical since Immuta may navigate away after each group creation | |
await navigateToGroupsPage(); | |
// Now find and click the New Group button | |
const addGroupBtn = await waitForElement(findNewGroupButton, 5000); | |
if (!addGroupBtn) { | |
throw new Error('New Group button not found after navigation.'); | |
} | |
console.log('Clicking New Group button...'); | |
addGroupBtn.click(); | |
// Wait for dialog to appear | |
await new Promise(resolve => setTimeout(resolve, 1500)); | |
// Check if a dialog is open | |
if (!isDialogOpen()) { | |
throw new Error('Dialog did not open after clicking New Group button'); | |
} | |
// Find and fill the group name input | |
const nameInput = await waitForElement(findGroupNameInput, 3000); | |
if (!nameInput) { | |
throw new Error('Group name input field not found in the dialog.'); | |
} | |
console.log('Entering group name...'); | |
// Set value and trigger input event | |
nameInput.value = groupName; | |
nameInput.dispatchEvent(new Event('input', { bubbles: true })); | |
nameInput.dispatchEvent(new Event('change', { bubbles: true })); | |
// Wait for input to register | |
await new Promise(resolve => setTimeout(resolve, 500)); | |
// Click Save button | |
const saveBtn = await waitForElement(findSaveButton, 3000); | |
if (!saveBtn) { | |
throw new Error('Save button not found in the dialog.'); | |
} | |
console.log('Clicking Save button...'); | |
saveBtn.click(); | |
// Wait for save operation to complete and dialog to close | |
let dialogClosed = false; | |
const startTime = Date.now(); | |
while (!dialogClosed && Date.now() - startTime < 5000) { | |
await new Promise(resolve => setTimeout(resolve, 500)); | |
dialogClosed = !isDialogOpen(); | |
} | |
if (!dialogClosed) { | |
// If dialog is still open, look for errors | |
const errorElements = document.querySelectorAll('.pxl-alert-warn, .pxl-alert-error, [class*="error"], [class*="alert"]'); | |
let errorMsg = 'Unknown error - dialog remained open'; | |
for (const errorEl of errorElements) { | |
if (isElementVisible(errorEl)) { | |
errorMsg = errorEl.textContent.trim(); | |
break; | |
} | |
} | |
throw new Error(`Error from Immuta: ${errorMsg}`); | |
} | |
// Wait a bit longer for any page updates | |
await new Promise(resolve => setTimeout(resolve, 1000)); | |
return true; | |
} catch (error) { | |
console.error(`Failed to create group "${groupName}": ${error.message}`); | |
// Try to close the dialog if it's still open | |
if (isDialogOpen()) { | |
try { | |
const cancelBtn = findCancelButton(); | |
if (cancelBtn) { | |
console.log('Closing dialog...'); | |
cancelBtn.click(); | |
await new Promise(resolve => setTimeout(resolve, 1000)); | |
} | |
} catch (e) { | |
console.warn('Could not close dialog:', e); | |
} | |
} | |
return false; | |
} | |
} | |
// Function to process multiple group names | |
async function processGroups(groupNames) { | |
if (!groupNames || groupNames.length === 0) { | |
console.error('No valid group names provided.'); | |
return; | |
} | |
// Create a results tracker | |
const results = { | |
total: groupNames.length, | |
success: 0, | |
failed: 0, | |
failedGroups: [] | |
}; | |
console.log(`Starting creation of ${groupNames.length} groups...`); | |
console.log('----------------------------------------'); | |
for (let i = 0; i < groupNames.length; i++) { | |
const groupName = groupNames[i]; | |
console.log(`Processing ${i+1}/${groupNames.length}: "${groupName}"`); | |
const success = await createGroup(groupName); | |
if (success) { | |
results.success++; | |
console.log(`✅ Group "${groupName}" created successfully`); | |
} else { | |
results.failed++; | |
results.failedGroups.push(groupName); | |
console.error(`❌ Failed to create group "${groupName}"`); | |
} | |
console.log('----------------------------------------'); | |
// Brief pause between operations to avoid overwhelming the server | |
await new Promise(resolve => setTimeout(resolve, 2000)); | |
} | |
// Display final summary | |
console.log('\n==== FINAL RESULTS ===='); | |
console.log(`Total groups processed: ${results.total}`); | |
console.log(`Successfully created: ${results.success}`); | |
console.log(`Failed to create: ${results.failed}`); | |
if (results.failed > 0) { | |
console.log('\nFailed groups:'); | |
results.failedGroups.forEach((name, i) => { | |
console.log(`${i+1}. "${name}"`); | |
}); | |
} | |
return results; | |
} | |
// Function to create file input and handle selection | |
function promptForFile() { | |
return new Promise((resolve) => { | |
// Create styled container for file input | |
const container = document.createElement('div'); | |
container.style.cssText = ` | |
position: fixed; | |
top: 20px; | |
left: 50%; | |
transform: translateX(-50%); | |
z-index: 10000; | |
background: white; | |
padding: 20px; | |
border-radius: 8px; | |
box-shadow: 0 4px 12px rgba(0,0,0,0.15); | |
max-width: 500px; | |
width: 90%; | |
font-family: Arial, sans-serif; | |
`; | |
// Add title and instruction | |
const title = document.createElement('h3'); | |
title.textContent = 'Import Groups to Immuta'; | |
title.style.margin = '0 0 10px 0'; | |
const instructions = document.createElement('p'); | |
instructions.innerHTML = 'Select a CSV or Excel file containing group names. The first column will be used for group names.<br><small style="color: #666;">Note: In restricted environments, CSV files are recommended.</small>'; | |
instructions.style.margin = '0 0 15px 0'; | |
instructions.style.fontSize = '14px'; | |
// Create file input | |
const fileInput = document.createElement('input'); | |
fileInput.type = 'file'; | |
fileInput.accept = '.csv,.xlsx,.xls'; | |
fileInput.style.width = '100%'; | |
fileInput.style.marginBottom = '15px'; | |
// Create buttons container | |
const buttonsContainer = document.createElement('div'); | |
buttonsContainer.style.display = 'flex'; | |
buttonsContainer.style.justifyContent = 'flex-end'; | |
buttonsContainer.style.gap = '10px'; | |
// Cancel button | |
const cancelButton = document.createElement('button'); | |
cancelButton.textContent = 'Cancel'; | |
cancelButton.style.cssText = ` | |
padding: 8px 16px; | |
border: 1px solid #ccc; | |
background: #f5f5f5; | |
border-radius: 4px; | |
cursor: pointer; | |
`; | |
// Handle file selection | |
fileInput.onchange = () => { | |
const file = fileInput.files[0]; | |
if (file) { | |
container.remove(); | |
resolve(file); | |
} | |
}; | |
// Handle cancel | |
cancelButton.onclick = () => { | |
container.remove(); | |
resolve(null); | |
}; | |
// Add elements to container | |
buttonsContainer.appendChild(cancelButton); | |
container.appendChild(title); | |
container.appendChild(instructions); | |
container.appendChild(fileInput); | |
container.appendChild(buttonsContainer); | |
// Add to body | |
document.body.appendChild(container); | |
}); | |
} | |
// Main function | |
async function main() { | |
console.clear(); | |
console.log('Immuta Group Creation Tool'); | |
console.log('========================='); | |
console.log('This tool will help you create multiple Immuta groups from a CSV or Excel file.'); | |
try { | |
// Check if we can load required libraries | |
const librariesLoaded = await loadLibraries(); | |
if (librariesLoaded) { | |
console.log('✅ Required libraries loaded successfully'); | |
} else { | |
console.log('⚠️ Running in offline mode - Excel support may be limited'); | |
console.log(' CSV files are recommended in this environment'); | |
} | |
// Prompt for file | |
console.log('Please select a CSV or Excel file...'); | |
const file = await promptForFile(); | |
if (!file) { | |
console.log('❌ Operation cancelled: No file selected'); | |
return; | |
} | |
console.log(`📁 File selected: ${file.name}`); | |
try { | |
// Read file and extract group names | |
const groupNames = await readFile(file); | |
if (!groupNames || groupNames.length === 0) { | |
console.error('❌ No valid group names found in the file.'); | |
console.log(' Please check that your file has group names in the first column.'); | |
return; | |
} | |
console.log(`📋 Found ${groupNames.length} group names:`); | |
groupNames.forEach((name, i) => { | |
if (i < 10 || i >= groupNames.length - 5) { | |
console.log(` ${i+1}. ${name}`); | |
} else if (i === 10) { | |
console.log(` ... (${groupNames.length - 15} more) ...`); | |
} | |
}); | |
// Confirm with user | |
const confirmMessage = `Do you want to create ${groupNames.length} groups in Immuta?`; | |
if (!confirm(confirmMessage)) { | |
console.log('❌ Operation cancelled by user'); | |
return; | |
} | |
// Process groups | |
const results = await processGroups(groupNames); | |
if (results && results.failed > 0 && results.failed < results.total) { | |
// Ask if user wants to retry failed groups | |
if (confirm(`Do you want to retry the ${results.failed} failed groups?`)) { | |
console.log('\nRetrying failed groups...'); | |
await processGroups(results.failedGroups); | |
} | |
} | |
console.log('\n✅ Group creation process completed!'); | |
} catch (error) { | |
if (error.message.includes('Unable to parse Excel file in offline mode')) { | |
console.error('❌ Excel parsing failed in offline mode.'); | |
console.log(' Please convert your Excel file to CSV format and try again.'); | |
} else { | |
console.error('❌ Error processing file:', error.message); | |
} | |
} | |
} catch (error) { | |
console.error('❌ Error:', error.message); | |
console.log('If the error persists, try refreshing the page and running the script again.'); | |
} | |
} | |
// Execute the main function | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment