shell-mirror 1.5.120 → 1.5.123
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/mac-agent/agent.js +19 -0
- package/package.json +1 -1
- package/public/app/dashboard.css +119 -0
- package/public/app/dashboard.js +48 -25
- package/public/app/terminal.html +51 -50
package/mac-agent/agent.js
CHANGED
|
@@ -61,6 +61,19 @@ if (process.env.WEBSOCKET_URL) {
|
|
|
61
61
|
const shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash';
|
|
62
62
|
logToFile(`🐚 Shell: ${shell}`);
|
|
63
63
|
|
|
64
|
+
// Enable case-insensitive tab completion for shells
|
|
65
|
+
function enableCaseInsensitiveCompletion(terminal, shellType) {
|
|
66
|
+
setTimeout(() => {
|
|
67
|
+
if (shellType === '/bin/zsh' || shellType.includes('zsh')) {
|
|
68
|
+
// For zsh: configure case-insensitive completion matcher
|
|
69
|
+
terminal.write('autoload -Uz compinit 2>/dev/null; compinit -i 2>/dev/null; zstyle \':completion:*\' matcher-list \'m:{a-zA-Z}={A-Za-z}\'; clear\n');
|
|
70
|
+
} else if (shellType.includes('bash')) {
|
|
71
|
+
// For bash: set completion-ignore-case
|
|
72
|
+
terminal.write('bind \'set completion-ignore-case on\' 2>/dev/null; clear\n');
|
|
73
|
+
}
|
|
74
|
+
}, 600); // After login scripts have run
|
|
75
|
+
}
|
|
76
|
+
|
|
64
77
|
// Circular buffer for session output persistence
|
|
65
78
|
class CircularBuffer {
|
|
66
79
|
constructor(size = 10000, maxTotalSize = 512 * 1024) { // 512KB max total size
|
|
@@ -235,6 +248,9 @@ class SessionManager {
|
|
|
235
248
|
terminal.write('\n');
|
|
236
249
|
}, 500);
|
|
237
250
|
|
|
251
|
+
// Enable case-insensitive tab completion
|
|
252
|
+
enableCaseInsensitiveCompletion(terminal, macShell);
|
|
253
|
+
|
|
238
254
|
terminal.on('exit', (code) => {
|
|
239
255
|
logToFile(`[SESSION] Terminal process exited for session ${sessionId} with code ${code}`);
|
|
240
256
|
session.status = 'crashed';
|
|
@@ -1160,6 +1176,9 @@ function startLocalServer() {
|
|
|
1160
1176
|
});
|
|
1161
1177
|
|
|
1162
1178
|
logToFile(`[LOCAL] Created new direct session: ${sessionId}`);
|
|
1179
|
+
|
|
1180
|
+
// Enable case-insensitive tab completion for direct sessions
|
|
1181
|
+
enableCaseInsensitiveCompletion(ptyProcess, shell);
|
|
1163
1182
|
}
|
|
1164
1183
|
|
|
1165
1184
|
localWs.send(JSON.stringify({
|
package/package.json
CHANGED
package/public/app/dashboard.css
CHANGED
|
@@ -647,6 +647,125 @@ body {
|
|
|
647
647
|
border-color: var(--accent);
|
|
648
648
|
}
|
|
649
649
|
|
|
650
|
+
/* Empty state - additional content */
|
|
651
|
+
.empty-state-note {
|
|
652
|
+
color: var(--text-muted);
|
|
653
|
+
font-size: 0.75rem;
|
|
654
|
+
margin: 16px 0;
|
|
655
|
+
text-align: center;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
.empty-state-troubleshooting {
|
|
659
|
+
margin-top: 24px;
|
|
660
|
+
padding-top: 16px;
|
|
661
|
+
border-top: 1px solid var(--border);
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
.troubleshooting-title {
|
|
665
|
+
color: var(--text-muted);
|
|
666
|
+
font-size: 0.75rem;
|
|
667
|
+
font-weight: 500;
|
|
668
|
+
display: block;
|
|
669
|
+
margin-bottom: 8px;
|
|
670
|
+
text-transform: uppercase;
|
|
671
|
+
letter-spacing: 0.5px;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
.empty-state-troubleshooting ul {
|
|
675
|
+
list-style: none;
|
|
676
|
+
margin: 0;
|
|
677
|
+
padding: 0;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
.empty-state-troubleshooting li {
|
|
681
|
+
color: var(--text-muted);
|
|
682
|
+
font-size: 0.75rem;
|
|
683
|
+
padding: 4px 0;
|
|
684
|
+
padding-left: 12px;
|
|
685
|
+
position: relative;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
.empty-state-troubleshooting li::before {
|
|
689
|
+
content: '•';
|
|
690
|
+
position: absolute;
|
|
691
|
+
left: 0;
|
|
692
|
+
color: var(--text-muted);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/* Collapsible help section */
|
|
696
|
+
.collapsible-help {
|
|
697
|
+
margin-top: 32px;
|
|
698
|
+
padding: 0 32px;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
.help-toggle {
|
|
702
|
+
width: 100%;
|
|
703
|
+
background: transparent;
|
|
704
|
+
border: none;
|
|
705
|
+
padding: 12px 0;
|
|
706
|
+
display: flex;
|
|
707
|
+
align-items: center;
|
|
708
|
+
justify-content: space-between;
|
|
709
|
+
color: var(--text-muted);
|
|
710
|
+
font-size: 0.8rem;
|
|
711
|
+
cursor: pointer;
|
|
712
|
+
transition: color 0.2s ease;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
.help-toggle:hover {
|
|
716
|
+
color: var(--text-secondary);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
.help-arrow {
|
|
720
|
+
transition: transform 0.2s ease;
|
|
721
|
+
color: var(--text-muted);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
.help-arrow.rotated {
|
|
725
|
+
transform: rotate(180deg);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
.help-content {
|
|
729
|
+
max-height: 0;
|
|
730
|
+
overflow: hidden;
|
|
731
|
+
transition: max-height 0.3s ease;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
.help-content.expanded {
|
|
735
|
+
max-height: 300px;
|
|
736
|
+
padding: 16px 0;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
.help-step {
|
|
740
|
+
display: flex;
|
|
741
|
+
flex-direction: column;
|
|
742
|
+
gap: 6px;
|
|
743
|
+
margin-bottom: 12px;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
.help-step-label {
|
|
747
|
+
font-size: 0.75rem;
|
|
748
|
+
font-weight: 500;
|
|
749
|
+
color: var(--text-muted);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
.help-step code {
|
|
753
|
+
background: var(--bg-tertiary);
|
|
754
|
+
padding: 8px 12px;
|
|
755
|
+
border-radius: 4px;
|
|
756
|
+
font-family: 'Monaco', 'Menlo', monospace;
|
|
757
|
+
font-size: 0.75rem;
|
|
758
|
+
color: var(--text-muted);
|
|
759
|
+
display: inline-block;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
.help-note {
|
|
763
|
+
color: var(--text-muted);
|
|
764
|
+
font-size: 0.7rem;
|
|
765
|
+
margin-top: 8px;
|
|
766
|
+
font-style: italic;
|
|
767
|
+
}
|
|
768
|
+
|
|
650
769
|
/* Modals */
|
|
651
770
|
.modal-overlay {
|
|
652
771
|
position: fixed;
|
package/public/app/dashboard.js
CHANGED
|
@@ -679,31 +679,7 @@ class ShellMirrorDashboard {
|
|
|
679
679
|
${agentCount > 0 ? agentsHtml : this.renderEmptyAgentState()}
|
|
680
680
|
</div>
|
|
681
681
|
</div>
|
|
682
|
-
|
|
683
|
-
<div class="usage-step">
|
|
684
|
-
<span class="usage-step-num">1</span>
|
|
685
|
-
<div class="usage-step-content">
|
|
686
|
-
<span class="usage-step-title">Install the agent (on Mac)</span>
|
|
687
|
-
<code>npm install -g shell-mirror</code>
|
|
688
|
-
</div>
|
|
689
|
-
</div>
|
|
690
|
-
<div class="usage-step">
|
|
691
|
-
<span class="usage-step-num">2</span>
|
|
692
|
-
<div class="usage-step-content">
|
|
693
|
-
<span class="usage-step-title">Run the agent</span>
|
|
694
|
-
<code>shell-mirror</code>
|
|
695
|
-
</div>
|
|
696
|
-
</div>
|
|
697
|
-
<p class="usage-note">The agent will automatically connect to your dashboard</p>
|
|
698
|
-
<div class="usage-troubleshooting">
|
|
699
|
-
<span class="usage-trouble-title">Troubleshooting</span>
|
|
700
|
-
<ul>
|
|
701
|
-
<li>Make sure you're logged in with the same Google account</li>
|
|
702
|
-
<li>Check that the agent is running in your terminal</li>
|
|
703
|
-
<li>Refresh this dashboard after starting the agent</li>
|
|
704
|
-
</ul>
|
|
705
|
-
</div>
|
|
706
|
-
</div>
|
|
682
|
+
${agentCount > 0 ? this.renderCollapsibleHelp() : ''}
|
|
707
683
|
</div>
|
|
708
684
|
`;
|
|
709
685
|
}
|
|
@@ -728,10 +704,57 @@ class ShellMirrorDashboard {
|
|
|
728
704
|
</div>
|
|
729
705
|
</div>
|
|
730
706
|
</div>
|
|
707
|
+
<p class="empty-state-note">The agent will automatically connect to your dashboard</p>
|
|
708
|
+
<div class="empty-state-troubleshooting">
|
|
709
|
+
<span class="troubleshooting-title">Troubleshooting</span>
|
|
710
|
+
<ul>
|
|
711
|
+
<li>Make sure you're logged in with the same Google account</li>
|
|
712
|
+
<li>Check that the agent is running in your terminal</li>
|
|
713
|
+
<li>Refresh this dashboard after starting the agent</li>
|
|
714
|
+
</ul>
|
|
715
|
+
</div>
|
|
716
|
+
</div>
|
|
717
|
+
`;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
renderCollapsibleHelp() {
|
|
721
|
+
return `
|
|
722
|
+
<div class="collapsible-help">
|
|
723
|
+
<button class="help-toggle" onclick="dashboard.toggleHelp()">
|
|
724
|
+
<span>Need help adding another agent?</span>
|
|
725
|
+
<svg class="help-arrow" width="12" height="12" viewBox="0 0 12 12">
|
|
726
|
+
<path d="M2 4l4 4 4-4" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round"/>
|
|
727
|
+
</svg>
|
|
728
|
+
</button>
|
|
729
|
+
<div class="help-content" id="help-content">
|
|
730
|
+
<div class="help-step">
|
|
731
|
+
<span class="help-step-label">1. Install</span>
|
|
732
|
+
<code>npm install -g shell-mirror</code>
|
|
733
|
+
</div>
|
|
734
|
+
<div class="help-step">
|
|
735
|
+
<span class="help-step-label">2. Run</span>
|
|
736
|
+
<code>shell-mirror</code>
|
|
737
|
+
</div>
|
|
738
|
+
<p class="help-note">The agent will automatically connect to your dashboard</p>
|
|
739
|
+
</div>
|
|
731
740
|
</div>
|
|
732
741
|
`;
|
|
733
742
|
}
|
|
734
743
|
|
|
744
|
+
toggleHelp() {
|
|
745
|
+
const content = document.getElementById('help-content');
|
|
746
|
+
const arrow = document.querySelector('.help-arrow');
|
|
747
|
+
const isExpanded = content.classList.contains('expanded');
|
|
748
|
+
|
|
749
|
+
if (isExpanded) {
|
|
750
|
+
content.classList.remove('expanded');
|
|
751
|
+
arrow.classList.remove('rotated');
|
|
752
|
+
} else {
|
|
753
|
+
content.classList.add('expanded');
|
|
754
|
+
arrow.classList.add('rotated');
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
735
758
|
renderQuickActions() {
|
|
736
759
|
return `
|
|
737
760
|
<div class="dashboard-card">
|
package/public/app/terminal.html
CHANGED
|
@@ -273,34 +273,35 @@
|
|
|
273
273
|
#agent-id-input { font-size: 1.2em; padding: 8px; width: 400px; margin-bottom: 1em; }
|
|
274
274
|
#connect-btn { font-size: 1.2em; padding: 10px 20px; }
|
|
275
275
|
|
|
276
|
-
/* Help Modal - Tab Navigation */
|
|
276
|
+
/* Help Modal - Tab Navigation (Dark Kraken Style) */
|
|
277
277
|
.help-tabs {
|
|
278
278
|
display: flex;
|
|
279
|
-
padding: 0
|
|
280
|
-
border-bottom: 1px solid #
|
|
281
|
-
background: #
|
|
279
|
+
padding: 0;
|
|
280
|
+
border-bottom: 1px solid #2a2b30;
|
|
281
|
+
background: #0a0b0d;
|
|
282
282
|
}
|
|
283
283
|
|
|
284
284
|
.help-tab {
|
|
285
|
+
flex: 1;
|
|
285
286
|
padding: 12px 20px;
|
|
286
287
|
background: transparent;
|
|
287
288
|
border: none;
|
|
288
|
-
border-bottom:
|
|
289
|
+
border-bottom: 2px solid transparent;
|
|
289
290
|
cursor: pointer;
|
|
290
|
-
font-size: 0.
|
|
291
|
-
color: #
|
|
292
|
-
transition: all 0.
|
|
291
|
+
font-size: 0.85rem;
|
|
292
|
+
color: #5a5f6a;
|
|
293
|
+
transition: all 0.15s ease;
|
|
293
294
|
}
|
|
294
295
|
|
|
295
296
|
.help-tab:hover {
|
|
296
|
-
color: #
|
|
297
|
-
background:
|
|
297
|
+
color: #8a8f98;
|
|
298
|
+
background: #141519;
|
|
298
299
|
}
|
|
299
300
|
|
|
300
301
|
.help-tab.active {
|
|
301
|
-
color: #
|
|
302
|
-
border-bottom-color: #
|
|
303
|
-
font-weight:
|
|
302
|
+
color: #fff;
|
|
303
|
+
border-bottom-color: #5d5fef;
|
|
304
|
+
font-weight: 500;
|
|
304
305
|
}
|
|
305
306
|
|
|
306
307
|
/* Help Modal - Tab Content */
|
|
@@ -385,66 +386,66 @@
|
|
|
385
386
|
<div id="terminal"></div>
|
|
386
387
|
</div>
|
|
387
388
|
|
|
388
|
-
<!-- Help Modal -->
|
|
389
|
-
<div id="help-modal" style="display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.
|
|
390
|
-
<div style="background:
|
|
389
|
+
<!-- Help Modal (Dark Kraken Style) -->
|
|
390
|
+
<div id="help-modal" style="display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.85); align-items: center; justify-content: center; z-index: 20000;">
|
|
391
|
+
<div style="background: #141519; border-radius: 8px; max-width: 500px; width: 90%; max-height: 80vh; overflow: hidden; border: 1px solid #2a2b30; box-shadow: 0 8px 32px rgba(0,0,0,0.5);">
|
|
391
392
|
<!-- Header -->
|
|
392
|
-
<div style="padding: 20px
|
|
393
|
-
<h3 style="margin: 0; font-size:
|
|
394
|
-
<button onclick="closeHelpModal()" style="background: none; border: none; font-size: 1.
|
|
393
|
+
<div style="padding: 16px 20px; border-bottom: 1px solid #2a2b30; display: flex; justify-content: space-between; align-items: center;">
|
|
394
|
+
<h3 style="margin: 0; font-size: 1rem; color: #fff; font-weight: 600;">Shell Mirror Terminal</h3>
|
|
395
|
+
<button onclick="closeHelpModal()" style="background: none; border: none; font-size: 1.2rem; cursor: pointer; padding: 4px 8px; border-radius: 4px; color: #5a5f6a; transition: all 0.15s;">×</button>
|
|
395
396
|
</div>
|
|
396
397
|
|
|
397
398
|
<!-- Tab Navigation -->
|
|
398
|
-
<div class="help-tabs">
|
|
399
|
-
<button class="help-tab active" onclick="showHelpTab('sessions')">Sessions</button>
|
|
400
|
-
<button class="help-tab" onclick="showHelpTab('help')">Troubleshooting</button>
|
|
399
|
+
<div class="help-tabs" style="display: flex; border-bottom: 1px solid #2a2b30; background: #0a0b0d;">
|
|
400
|
+
<button class="help-tab active" onclick="showHelpTab('sessions')" style="flex: 1; padding: 12px; background: none; border: none; color: #8a8f98; cursor: pointer; font-size: 0.85rem; border-bottom: 2px solid transparent;">Sessions</button>
|
|
401
|
+
<button class="help-tab" onclick="showHelpTab('help')" style="flex: 1; padding: 12px; background: none; border: none; color: #8a8f98; cursor: pointer; font-size: 0.85rem; border-bottom: 2px solid transparent;">Troubleshooting</button>
|
|
401
402
|
</div>
|
|
402
403
|
|
|
403
404
|
<!-- Tab Content -->
|
|
404
|
-
<div style="padding:
|
|
405
|
+
<div style="padding: 20px; max-height: 60vh; overflow-y: auto; color: #8a8f98; font-size: 0.85rem;">
|
|
405
406
|
<!-- Sessions Tab -->
|
|
406
407
|
<div id="tab-sessions" class="help-tab-content active">
|
|
407
|
-
<p style="margin-top: 0; margin-bottom: 16px;">Sessions let you run multiple terminals simultaneously.</p>
|
|
408
|
+
<p style="margin-top: 0; margin-bottom: 16px; color: #8a8f98;">Sessions let you run multiple terminals simultaneously.</p>
|
|
408
409
|
|
|
409
|
-
<h4 style="margin-top:
|
|
410
|
-
<ul style="list-style: none; padding: 0; margin: 0;">
|
|
411
|
-
<li>• Tap "Sessions" → "+ New Session"</li>
|
|
412
|
-
<li>• Each session runs independently</li>
|
|
410
|
+
<h4 style="margin-top: 16px; margin-bottom: 8px; color: #fff; font-size: 0.85rem; font-weight: 500;">Creating</h4>
|
|
411
|
+
<ul style="list-style: none; padding: 0; margin: 0; color: #5a5f6a;">
|
|
412
|
+
<li style="padding: 3px 0;">• Tap "Sessions" → "+ New Session"</li>
|
|
413
|
+
<li style="padding: 3px 0;">• Each session runs independently</li>
|
|
413
414
|
</ul>
|
|
414
415
|
|
|
415
|
-
<h4 style="margin-top:
|
|
416
|
-
<ul style="list-style: none; padding: 0; margin: 0;">
|
|
417
|
-
<li>• Tap "Sessions" dropdown</li>
|
|
418
|
-
<li>• Select any session</li>
|
|
419
|
-
<li>• Your processes keep running in background</li>
|
|
416
|
+
<h4 style="margin-top: 16px; margin-bottom: 8px; color: #fff; font-size: 0.85rem; font-weight: 500;">Switching</h4>
|
|
417
|
+
<ul style="list-style: none; padding: 0; margin: 0; color: #5a5f6a;">
|
|
418
|
+
<li style="padding: 3px 0;">• Tap "Sessions" dropdown</li>
|
|
419
|
+
<li style="padding: 3px 0;">• Select any session</li>
|
|
420
|
+
<li style="padding: 3px 0;">• Your processes keep running in background</li>
|
|
420
421
|
</ul>
|
|
421
422
|
|
|
422
|
-
<h4 style="margin-top:
|
|
423
|
-
<ul style="list-style: none; padding: 0; margin: 0;">
|
|
424
|
-
<li>• Max 10 sessions per Mac</li>
|
|
425
|
-
<li>• Auto-deleted after 24h inactive</li>
|
|
423
|
+
<h4 style="margin-top: 16px; margin-bottom: 8px; color: #fff; font-size: 0.85rem; font-weight: 500;">Limits</h4>
|
|
424
|
+
<ul style="list-style: none; padding: 0; margin: 0; color: #5a5f6a;">
|
|
425
|
+
<li style="padding: 3px 0;">• Max 10 sessions per Mac</li>
|
|
426
|
+
<li style="padding: 3px 0;">• Auto-deleted after 24h inactive</li>
|
|
426
427
|
</ul>
|
|
427
428
|
</div>
|
|
428
429
|
|
|
429
430
|
<!-- Troubleshooting Tab -->
|
|
430
431
|
<div id="tab-help" class="help-tab-content" style="display: none;">
|
|
431
|
-
<h4 style="margin-top: 0; margin-bottom: 8px;">Connection stuck?</h4>
|
|
432
|
-
<ul style="list-style: none; padding: 0; margin: 0 0
|
|
433
|
-
<li>• Wait 10 seconds for auto-retry</li>
|
|
434
|
-
<li>• Still red? Return to Dashboard → Reconnect</li>
|
|
432
|
+
<h4 style="margin-top: 0; margin-bottom: 8px; color: #fff; font-size: 0.85rem; font-weight: 500;">Connection stuck?</h4>
|
|
433
|
+
<ul style="list-style: none; padding: 0; margin: 0 0 16px 0; color: #5a5f6a;">
|
|
434
|
+
<li style="padding: 3px 0;">• Wait 10 seconds for auto-retry</li>
|
|
435
|
+
<li style="padding: 3px 0;">• Still red? Return to Dashboard → Reconnect</li>
|
|
435
436
|
</ul>
|
|
436
437
|
|
|
437
|
-
<h4 style="margin-top:
|
|
438
|
-
<ul style="list-style: none; padding: 0; margin: 0 0
|
|
439
|
-
<li>• App tries: Local network → WebRTC → Fallback</li>
|
|
440
|
-
<li>• First connect may take 30 seconds</li>
|
|
441
|
-
<li>• WebRTC works best (100-500ms)</li>
|
|
438
|
+
<h4 style="margin-top: 16px; margin-bottom: 8px; color: #fff; font-size: 0.85rem; font-weight: 500;">Slow connection?</h4>
|
|
439
|
+
<ul style="list-style: none; padding: 0; margin: 0 0 16px 0; color: #5a5f6a;">
|
|
440
|
+
<li style="padding: 3px 0;">• App tries: Local network → WebRTC → Fallback</li>
|
|
441
|
+
<li style="padding: 3px 0;">• First connect may take 30 seconds</li>
|
|
442
|
+
<li style="padding: 3px 0;">• WebRTC works best (100-500ms)</li>
|
|
442
443
|
</ul>
|
|
443
444
|
|
|
444
|
-
<h4 style="margin-top:
|
|
445
|
-
<ul style="list-style: none; padding: 0; margin: 0;">
|
|
446
|
-
<li>• May need IT to whitelist STUN servers</li>
|
|
447
|
-
<li>• Contact: stun.l.google.com port 19302</li>
|
|
445
|
+
<h4 style="margin-top: 16px; margin-bottom: 8px; color: #fff; font-size: 0.85rem; font-weight: 500;">Behind corporate firewall?</h4>
|
|
446
|
+
<ul style="list-style: none; padding: 0; margin: 0; color: #5a5f6a;">
|
|
447
|
+
<li style="padding: 3px 0;">• May need IT to whitelist STUN servers</li>
|
|
448
|
+
<li style="padding: 3px 0;">• Contact: stun.l.google.com port 19302</li>
|
|
448
449
|
</ul>
|
|
449
450
|
</div>
|
|
450
451
|
</div>
|