Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 42 additions & 8 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,29 @@ <h2>Data Package Builder</h2>
<button type="button" id="package-reset" class="btn btn-secondary">Reset</button>
</div>
</form>

<!-- MDM Deployment Section -->
<div id="mdm-deployment-section" class="form-section" style="display: none;">
<h3>📦 MDM Deployment (Base64)</h3>
<p class="form-description">For Android Managed Configurations - Copy this base64-encoded string into your MDM's <code>enterpriseConfigurationDataPackage</code> field</p>

<div class="form-group">
<label for="mdm-base64">Base64-Encoded Data Package:</label>
<textarea id="mdm-base64" readonly rows="8" class="mdm-base64-textarea" aria-label="Base64-encoded data package"></textarea>
<div class="help-text" id="mdm-char-count">0 characters</div>
</div>

<div class="qr-actions">
<button type="button" id="mdm-copy-base64" class="btn btn-primary">
<span class="btn-icon">📋</span>
Copy to Clipboard
</button>
<button type="button" id="mdm-show-base64" class="btn btn-secondary">
<span class="btn-icon">👁️</span>
Show/Hide
</button>
</div>
</div>
</div>
</div>

Expand Down Expand Up @@ -645,20 +668,22 @@ <h4>TAK Config</h4>
</div>
<div class="help-tab-item">
<h4>Data Package Builder</h4>
<p>Create an import URI for data packages/config files at a URL. Red/green validation ensures a well‑formed URL before actions enable.</p>
<p>Build ATAK/WinTAK/iTAK packages with <code>config.pref</code> and <code>manifest.xml</code>. When <strong>client = iTAK</strong>, protocol tokens are <code>ssl</code> (HTTPS) and <code>tcp</code> (HTTP); QUIC is hidden.</p>
<p><strong>MDM Deployment:</strong> After building a package, a base64-encoded string is automatically generated for use with Mobile Device Management systems (ATAK v5.0+). Copy the base64 string and paste it into your MDM's <code>enterpriseConfigurationDataPackage</code> field to push ATAK configuration to managed devices.</p>
<div class="help-features">
<span class="feature-tag">Import</span>
<span class="feature-tag">Validation</span>
<span class="feature-tag">QR Code</span>
<span class="feature-tag">Packages</span>
<span class="feature-tag">ATAK/WinTAK/iTAK</span>
<span class="feature-tag">ZIP</span>
<span class="feature-tag">MDM</span>
</div>
</div>
<div class="help-tab-item">
<h4>URL Import</h4>
<p>Build ATAK/WinTAK/iTAK packages with <code>config.pref</code> and <code>manifest.xml</code>. When <strong>client = iTAK</strong>, protocol tokens are <code>ssl</code> (HTTPS) and <code>tcp</code> (HTTP); QUIC is hidden.</p>
<p>Create an import URI for data packages/config files at a URL. Red/green validation ensures a well‑formed URL before actions enable.</p>
<div class="help-features">
<span class="feature-tag">Packages</span>
<span class="feature-tag">ATAK/WinTAK/iTAK</span>
<span class="feature-tag">ZIP</span>
<span class="feature-tag">Import</span>
<span class="feature-tag">Validation</span>
<span class="feature-tag">QR Code</span>
</div>
</div>
<div class="help-tab-item">
Expand Down Expand Up @@ -694,6 +719,15 @@ <h4>Mass Deployment</h4>
<li>📱 <strong>Kiosk Mode:</strong> Use large displays for group onboarding</li>
</ul>
</div>
<div class="practice-item">
<h4>MDM Deployment (ATAK v5.0+)</h4>
<ul>
<li>📦 <strong>Base64 Export:</strong> After building a data package, copy the base64 string for MDM deployment</li>
<li>🔧 <strong>MDM Configuration:</strong> Paste the base64 string into your MDM's <code>enterpriseConfigurationDataPackage</code> field</li>
<li>✅ <strong>Character Count:</strong> Use the displayed character count to validate MDM field limits</li>
<li>🔄 <strong>Automatic Push:</strong> MDM will push the configuration to managed devices automatically</li>
</ul>
</div>
<div class="practice-item">
<h4>Configuration Management</h4>
<ul>
Expand Down
72 changes: 72 additions & 0 deletions src/js/__tests__/packageBuilder.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -456,4 +456,76 @@ describe('PackageBuilder', () => {
expect(configPrefCall[1]).toContain('tak.example.com:8089:quic');
});
});

describe('MDM Base64 Export', () => {
beforeEach(() => {
// Add MDM deployment section to DOM
document.body.innerHTML += `
<div id="mdm-deployment-section" style="display: none;">
<textarea id="mdm-base64"></textarea>
<div id="mdm-char-count"></div>
<button id="mdm-copy-base64">Copy</button>
<button id="mdm-show-base64">Show/Hide</button>
</div>
`;
window.PackageBuilder.init();
});

it('should have MDM deployment UI elements', () => {
// Verify all MDM elements exist
expect(document.getElementById('mdm-deployment-section')).toBeTruthy();
expect(document.getElementById('mdm-base64')).toBeTruthy();
expect(document.getElementById('mdm-char-count')).toBeTruthy();
expect(document.getElementById('mdm-copy-base64')).toBeTruthy();
expect(document.getElementById('mdm-show-base64')).toBeTruthy();
});

it('should hide MDM section on reset', () => {
const mdmSection = document.getElementById('mdm-deployment-section');
const mdmTextarea = document.getElementById('mdm-base64');

// Show section and add content
mdmSection.style.display = 'block';
mdmTextarea.value = 'test-base64-string';

// Click reset button
const resetBtn = document.getElementById('package-reset');
resetBtn.click();

// Verify MDM section is hidden and cleared
expect(mdmSection.style.display).toBe('none');
expect(mdmTextarea.value).toBe('');
});

it('should copy base64 to clipboard when copy button is clicked', async () => {
const mdmTextarea = document.getElementById('mdm-base64');
mdmTextarea.value = 'test-base64-string';

// Click copy button
const copyBtn = document.getElementById('mdm-copy-base64');
copyBtn.click();

// Wait for async operation
await new Promise(resolve => setTimeout(resolve, 100));

// Verify clipboard was called with correct value (clipboard is mocked in setup.js)
expect(navigator.clipboard.writeText).toHaveBeenCalledWith('test-base64-string');
});

it('should toggle textarea visibility when show/hide button is clicked', () => {
const showBtn = document.getElementById('mdm-show-base64');
const textarea = document.getElementById('mdm-base64');

// Initial state - expanded
textarea.rows = 8;

// Click to hide
showBtn.click();
expect(textarea.rows).toBe(2);

// Click to show
showBtn.click();
expect(textarea.rows).toBe(8);
});
});
});
107 changes: 107 additions & 0 deletions src/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -1457,17 +1457,80 @@ const PackageBuilder = (function () {
updateFilesList();
form.reset();
updatePackageNamePreview(); // Update preview after reset

// Hide MDM deployment section
const mdmSection = document.getElementById('mdm-deployment-section');
if (mdmSection) {
mdmSection.style.display = 'none';
}
const mdmTextarea = document.getElementById('mdm-base64');
if (mdmTextarea) {
mdmTextarea.value = '';
}
});

document.getElementById('package-build')?.addEventListener('click', buildPackage);

// Set up MDM deployment event handlers
setupMDMDeploymentHandlers();

// Set up dynamic package name preview
setupDynamicNaming();

// Set up QUIC port auto-switching for package form
setupPackageQuicSwitching();
}

/**
* Setup event handlers for MDM deployment section
*/
function setupMDMDeploymentHandlers () {
const copyBtn = document.getElementById('mdm-copy-base64');
const showBtn = document.getElementById('mdm-show-base64');
const textarea = document.getElementById('mdm-base64');

// Copy to clipboard
copyBtn?.addEventListener('click', async () => {
const base64String = textarea?.value || '';
if (!base64String) {
return;
}

try {
await navigator.clipboard.writeText(base64String);
(window.UIController || UIController).showNotification('Base64 string copied to clipboard!', 'success');
} catch (error) {
// eslint-disable-next-line no-console
console.error('Error copying to clipboard:', error);
(window.UIController || UIController).showNotification('Error copying to clipboard', 'error');
}
});

// Toggle show/hide (expand/collapse rows)
let isCollapsed = false;
showBtn?.addEventListener('click', () => {
if (!textarea) {
return;
}

if (isCollapsed) {
// Expand to show full content
textarea.rows = 8;
isCollapsed = false;
if (showBtn.querySelector('.btn-icon')) {
showBtn.querySelector('.btn-icon').textContent = '👁️';
}
} else {
// Collapse to show minimal content
textarea.rows = 2;
isCollapsed = true;
if (showBtn.querySelector('.btn-icon')) {
showBtn.querySelector('.btn-icon').textContent = '👁️‍🗨️';
}
}
});
}

/**
* Setup real-time validation for package form fields
*/
Expand Down Expand Up @@ -2060,6 +2123,9 @@ const PackageBuilder = (function () {
document.body.removeChild(a);
URL.revokeObjectURL(a.href);

// Generate base64 for MDM deployment
await generateBase64ForMDM(blob);

(window.UIController || UIController).showNotification(`Package "${filename}" downloaded successfully!`, 'success');
} catch (e) {
// eslint-disable-next-line no-console
Expand All @@ -2068,6 +2134,47 @@ const PackageBuilder = (function () {
}
}

/**
* Generate base64 encoding of the package for MDM deployment
* @param {Blob} blob - The ZIP file blob
*/
async function generateBase64ForMDM (blob) {
try {
// Convert blob to ArrayBuffer
const arrayBuffer = await blob.arrayBuffer();

// Convert ArrayBuffer to base64
const uint8Array = new Uint8Array(arrayBuffer);
let binaryString = '';
const chunkSize = 0x8000; // Process in 32KB chunks to avoid call stack size exceeded

for (let i = 0; i < uint8Array.length; i += chunkSize) {
const chunk = uint8Array.subarray(i, Math.min(i + chunkSize, uint8Array.length));
binaryString += String.fromCharCode.apply(null, chunk);
}

const base64String = window.btoa(binaryString);

// Display the base64 string
const mdmSection = document.getElementById('mdm-deployment-section');
const mdmTextarea = document.getElementById('mdm-base64');
const mdmCharCount = document.getElementById('mdm-char-count');

if (mdmSection && mdmTextarea && mdmCharCount) {
mdmTextarea.value = base64String;
mdmCharCount.textContent = `${base64String.length.toLocaleString()} characters`;
mdmSection.style.display = 'block';

// Scroll to the MDM section
mdmSection.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}
} catch (error) {
// eslint-disable-next-line no-console
console.error('Error generating base64:', error);
(window.UIController || UIController).showNotification('Error generating base64 for MDM deployment', 'error');
}
}

function buildConfigPref ({ deployment, connectString, caPass, clientPass, username, password, cacheCreds, callsign, team, role, client }) {
const isSoft = deployment === 'soft-cert';
const isITAK = client === 'itak';
Expand Down
46 changes: 46 additions & 0 deletions src/styles/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -3038,6 +3038,52 @@ input[type="file"]:active::-webkit-file-upload-button {
}
}

/* ==========================================================================
MDM Deployment Section
========================================================================== */
.mdm-base64-textarea {
width: 100%;
padding: var(--spacing-md);
font-family: 'Courier New', Courier, monospace;
font-size: 0.875rem;
line-height: 1.5;
background-color: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--border-radius);
resize: vertical;
min-height: 150px;
word-break: break-all;
overflow-wrap: break-word;
}

.mdm-base64-textarea:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
}

[data-theme="dark"] .mdm-base64-textarea {
background-color: #1e293b;
border-color: #475569;
color: #e2e8f0;
}

#mdm-deployment-section {
margin-top: var(--spacing-2xl);
padding-top: var(--spacing-2xl);
border-top: 2px solid var(--color-border);
}

#mdm-deployment-section h3 {
color: var(--color-text-primary);
margin-bottom: var(--spacing-md);
}

#mdm-char-count {
font-weight: 500;
color: var(--color-text-secondary);
}

/* ==========================================================================
Print Styles - Professional Print Layout
========================================================================== */
Expand Down