pac-proxy-cli 1.0.4 → 1.1.2
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/README.md +49 -34
- package/dist/assets/index-CweQSJse.js +28 -0
- package/dist/assets/index-H0IGlOgE.css +1 -0
- package/dist/index.html +2 -5
- package/lib/index.js +2 -3
- package/lib/local-store.js +39 -2
- package/lib/server.js +62 -1
- package/lib/sslocal-manager.js +146 -0
- package/package.json +7 -2
- package/dist/assets/index-DD-_gVYC.js +0 -27
- package/dist/assets/index-IXkmSGx6.css +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.toast-container[data-v-5cf00320]{position:fixed;top:1.25rem;right:1.25rem;z-index:9999;display:flex;flex-direction:column;gap:.5rem;pointer-events:none}.toast[data-v-5cf00320]{display:flex;align-items:center;gap:.5rem;padding:.65rem 1rem;border-radius:8px;font-size:.9rem;font-weight:500;box-shadow:0 4px 12px #00000026;pointer-events:auto;max-width:320px}.toast.success[data-v-5cf00320]{background:#f0fdf4;color:#166534;border:1px solid #bbf7d0}.toast.error[data-v-5cf00320]{background:#fef2f2;color:#991b1b;border:1px solid #fecaca}.toast.info[data-v-5cf00320]{background:#eff6ff;color:#1e40af;border:1px solid #bfdbfe}.toast-icon[data-v-5cf00320]{flex-shrink:0;width:1.1rem;height:1.1rem;display:flex}.toast-icon svg[data-v-5cf00320]{width:100%;height:100%}.toast-msg[data-v-5cf00320]{line-height:1.4}.toast-confirm-mask[data-v-5cf00320]{position:fixed;top:0;right:0;bottom:0;left:0;background:#00000059;display:flex;align-items:center;justify-content:center;z-index:10000;pointer-events:auto}.toast-confirm[data-v-5cf00320]{background:#fff;border-radius:10px;padding:1.5rem;min-width:280px;max-width:360px;box-shadow:0 8px 24px #0000002e}.toast-confirm-msg[data-v-5cf00320]{font-size:.95rem;color:#0f172a;margin-bottom:1.25rem;line-height:1.5}.toast-confirm-actions[data-v-5cf00320]{display:flex;gap:.75rem;justify-content:flex-end}.tc-btn[data-v-5cf00320]{padding:.45rem 1rem;border-radius:6px;font-size:.9rem;cursor:pointer;border:none;font-weight:500}.tc-btn.cancel[data-v-5cf00320]{background:#f1f5f9;color:#475569}.tc-btn.cancel[data-v-5cf00320]:hover{background:#e2e8f0}.tc-btn.ok[data-v-5cf00320]{background:#2563eb;color:#fff}.tc-btn.ok[data-v-5cf00320]:hover{background:#1d4ed8}.toast-enter-active[data-v-5cf00320]{animation:toast-in-5cf00320 .22s ease}.toast-leave-active[data-v-5cf00320]{animation:toast-out-5cf00320 .18s ease forwards}@keyframes toast-in-5cf00320{0%{opacity:0;transform:translate(1rem)}to{opacity:1;transform:translate(0)}}@keyframes toast-out-5cf00320{0%{opacity:1;transform:translate(0)}to{opacity:0;transform:translate(1rem)}}.app-layout[data-v-ee1bb6ce]{min-height:100vh;display:flex}.sidebar[data-v-ee1bb6ce]{position:fixed;left:0;top:0;bottom:0;width:200px;background:var(--bg-sidebar);display:flex;flex-direction:column;z-index:20}.sidebar-logo[data-v-ee1bb6ce]{display:flex;align-items:center;gap:.6rem;padding:1.25rem 1.25rem 1rem;color:#fff;font-weight:700;font-size:1rem;border-bottom:1px solid rgba(255,255,255,.08)}.sidebar-logo svg[data-v-ee1bb6ce]{width:1.25rem;height:1.25rem;flex-shrink:0;color:#60a5fa}.side-nav[data-v-ee1bb6ce]{flex:1;display:flex;flex-direction:column;padding:.75rem 0}.side-nav a[data-v-ee1bb6ce]{display:flex;align-items:center;gap:.6rem;color:var(--text-sidebar-muted);padding:.6rem 1.25rem;font-size:.9rem;border-left:3px solid transparent;transition:background .15s,color .15s;text-decoration:none}.side-nav a svg[data-v-ee1bb6ce]{width:1rem;height:1rem;flex-shrink:0}.side-nav a[data-v-ee1bb6ce]:hover{background:var(--bg-sidebar-hover);color:var(--text-sidebar);text-decoration:none}.side-nav a.active[data-v-ee1bb6ce]{background:#2563eb40;color:#93c5fd;border-left-color:#3b82f6}.sidebar-footer[data-v-ee1bb6ce]{padding:1rem 1.25rem;border-top:1px solid rgba(255,255,255,.08);display:flex;align-items:center;justify-content:space-between}.mode-badge[data-v-ee1bb6ce]{font-size:.75rem;padding:.2rem .5rem;border-radius:4px;font-weight:500}.mode-badge.local[data-v-ee1bb6ce]{background:#dbeafe26;color:#93c5fd}.mode-badge.remote[data-v-ee1bb6ce]{background:#e9d5ff26;color:#c4b5fd}.switch-mode-link[data-v-ee1bb6ce]{font-size:.78rem;color:var(--text-sidebar-muted);text-decoration:none}.switch-mode-link[data-v-ee1bb6ce]:hover{color:var(--text-sidebar);text-decoration:none}.content-wrap[data-v-ee1bb6ce]{margin-left:200px;flex:1;display:flex;flex-direction:column;min-height:100vh}.content-standalone[data-v-ee1bb6ce]{margin-left:0}.topbar[data-v-ee1bb6ce]{position:sticky;top:0;height:52px;display:flex;align-items:center;justify-content:space-between;padding:0 1.5rem;background:var(--bg-card);border-bottom:1px solid var(--border);z-index:10}.topbar-left .page-title[data-v-ee1bb6ce]{font-weight:600;font-size:.95rem;color:var(--text-primary)}.topbar-right[data-v-ee1bb6ce]{display:flex;align-items:center;gap:.75rem}.username[data-v-ee1bb6ce]{font-size:.875rem;color:var(--text-secondary)}.logout-btn[data-v-ee1bb6ce]{font-size:.85rem;color:var(--text-secondary);background:none;border:none;cursor:pointer;padding:.25rem 0}.logout-btn[data-v-ee1bb6ce]:hover{color:var(--btn-primary)}.main[data-v-ee1bb6ce]{flex:1;padding:1.5rem}.mode-select[data-v-b465f762]{min-height:100vh;display:flex;align-items:center;justify-content:center;background:var(--bg-main);padding:2rem}.card[data-v-b465f762]{background:var(--bg-card);border-radius:12px;padding:2.5rem;max-width:480px;width:100%;box-shadow:0 1px 3px #00000014}.card h1[data-v-b465f762]{font-size:1.5rem;margin-bottom:.5rem}.desc[data-v-b465f762]{color:var(--text-secondary);font-size:.95rem;margin-bottom:1.5rem}.options[data-v-b465f762]{display:flex;flex-direction:column;gap:1rem}.option[data-v-b465f762]{padding:1.25rem 1.5rem;border:2px solid var(--border);border-radius:var(--radius);background:#fff;text-align:left;transition:border-color .2s,background .2s}.option[data-v-b465f762]:hover{border-color:var(--btn-primary);background:#f8fafc}.option .title[data-v-b465f762]{display:block;font-weight:600;margin-bottom:.25rem}.option .sub[data-v-b465f762]{font-size:.9rem;color:var(--text-secondary)}.auth-page[data-v-d4d57b91]{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:2rem}.card[data-v-d4d57b91]{background:var(--bg-card);border-radius:12px;padding:2rem;max-width:400px;width:100%;box-shadow:0 1px 3px #00000014}.card h1[data-v-d4d57b91]{font-size:1.5rem;margin-bottom:.5rem}.desc[data-v-d4d57b91]{color:var(--text-secondary);font-size:.9rem;margin-bottom:1.5rem}.form[data-v-d4d57b91]{display:flex;flex-direction:column;gap:1rem}.field label[data-v-d4d57b91]{display:block;font-size:.9rem;margin-bottom:.35rem;color:var(--text-secondary)}.field input[data-v-d4d57b91]{width:100%;padding:.6rem .75rem;border:1px solid var(--border);border-radius:var(--radius)}.error[data-v-d4d57b91]{color:#dc2626;font-size:.9rem}.btn.primary[data-v-d4d57b91]{background:var(--btn-primary);color:#fff;padding:.65rem 1rem;font-weight:500}.btn.primary[data-v-d4d57b91]:hover:not(:disabled){background:var(--btn-primary-hover)}.btn.primary[data-v-d4d57b91]:disabled{opacity:.6;cursor:not-allowed}.footer[data-v-d4d57b91]{margin-top:1rem;font-size:.9rem;color:var(--text-secondary)}.auth-page[data-v-a1c8458c]{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:2rem}.card[data-v-a1c8458c]{background:var(--bg-card);border-radius:12px;padding:2rem;max-width:400px;width:100%;box-shadow:0 1px 3px #00000014}.card h1[data-v-a1c8458c]{font-size:1.5rem;margin-bottom:.5rem}.desc[data-v-a1c8458c]{color:var(--text-secondary);font-size:.9rem;margin-bottom:1.5rem}.form[data-v-a1c8458c]{display:flex;flex-direction:column;gap:1rem}.field label[data-v-a1c8458c]{display:block;font-size:.9rem;margin-bottom:.35rem;color:var(--text-secondary)}.field input[data-v-a1c8458c]{width:100%;padding:.6rem .75rem;border:1px solid var(--border);border-radius:var(--radius)}.error[data-v-a1c8458c]{color:#dc2626;font-size:.9rem}.success[data-v-a1c8458c]{color:#16a34a;font-size:.9rem}.btn.primary[data-v-a1c8458c]{background:var(--btn-primary);color:#fff;padding:.65rem 1rem;font-weight:500}.btn.primary[data-v-a1c8458c]:hover:not(:disabled){background:var(--btn-primary-hover)}.btn.primary[data-v-a1c8458c]:disabled{opacity:.6;cursor:not-allowed}.footer[data-v-a1c8458c]{margin-top:1rem;font-size:.9rem;color:var(--text-secondary)}.proxy-settings[data-v-44d33970]{display:flex;flex-direction:column;gap:1.25rem}.card[data-v-44d33970]{background:var(--bg-card);border-radius:var(--radius);padding:1.5rem;box-shadow:0 1px 3px #0000000f}.card h3[data-v-44d33970]{font-size:.95rem;font-weight:600;margin-bottom:.35rem}.desc[data-v-44d33970]{color:var(--text-secondary);font-size:.875rem;margin-bottom:1.25rem;line-height:1.5}.desc code[data-v-44d33970]{background:#f1f5f9;padding:.1rem .3rem;border-radius:4px;font-size:.82rem}.mode-cards[data-v-44d33970]{display:grid;grid-template-columns:repeat(4,1fr);gap:.75rem}.mode-card[data-v-44d33970]{display:flex;flex-direction:column;align-items:center;gap:.35rem;padding:1rem .5rem;border:1.5px solid var(--border);border-radius:var(--radius);background:var(--bg-card);cursor:pointer;transition:border-color .15s,background .15s,box-shadow .15s;text-align:center}.mode-card[data-v-44d33970]:hover:not(:disabled){border-color:var(--btn-primary);background:#f8fafc}.mode-card.active[data-v-44d33970]{border-color:var(--btn-primary);background:#eff6ff;box-shadow:0 0 0 3px #2563eb1a}.mode-card.active-clear[data-v-44d33970]{border-color:#94a3b8;background:#f8fafc;box-shadow:0 0 0 3px #94a3b826}.mode-card[data-v-44d33970]:disabled{opacity:.55;cursor:not-allowed}.mode-icon[data-v-44d33970]{width:1.5rem;height:1.5rem;color:var(--text-secondary)}.mode-icon[data-v-44d33970] svg{width:100%;height:100%}.mode-card.active .mode-icon[data-v-44d33970]{color:var(--btn-primary)}.mode-card.active-clear .mode-icon[data-v-44d33970]{color:#64748b}.mode-label[data-v-44d33970]{font-size:.875rem;font-weight:600;color:var(--text-primary)}.mode-desc[data-v-44d33970]{font-size:.78rem;color:var(--text-secondary);line-height:1.3}.ca-download[data-v-44d33970]{margin-top:1rem;padding-top:1rem;border-top:1px solid var(--border)}.ca-label[data-v-44d33970]{display:block;font-size:.875rem;color:var(--text-secondary);margin-bottom:.5rem}.btn-ca[data-v-44d33970]{display:inline-block;padding:.4rem .8rem;font-size:.875rem;background:var(--btn-primary);color:#fff;border-radius:var(--radius);text-decoration:none;margin-bottom:.5rem}.btn-ca[data-v-44d33970]:hover{background:var(--btn-primary-hover);text-decoration:none}.ca-hint[data-v-44d33970]{font-size:.82rem;color:var(--text-secondary);margin:0}.field[data-v-44d33970]{margin-bottom:1rem}.field label[data-v-44d33970]{display:block;font-size:.875rem;margin-bottom:.35rem;color:var(--text-secondary)}.field input[data-v-44d33970]{width:100%;padding:.5rem .75rem;border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem}.upstream-managed[data-v-44d33970]{display:flex;align-items:center;gap:.5rem;padding:.5rem .75rem;background:#eff6ff;border:1px solid #bfdbfe;border-radius:var(--radius);font-size:.875rem;color:#1e40af}.upstream-managed svg[data-v-44d33970]{width:1rem;height:1rem;flex-shrink:0}.upstream-managed a[data-v-44d33970]{color:#2563eb;font-size:.82rem;margin-left:auto}.row[data-v-44d33970]{display:grid;grid-template-columns:1fr 1fr;gap:1rem}.actions[data-v-44d33970]{margin-top:.5rem}.btn.primary[data-v-44d33970]{display:inline-flex;align-items:center;gap:.4rem;background:var(--btn-primary);color:#fff;padding:.5rem 1.25rem;font-size:.9rem;border:none;border-radius:var(--radius);cursor:pointer;transition:background .15s}.btn.primary[data-v-44d33970]:hover:not(:disabled){background:var(--btn-primary-hover)}.btn.primary[data-v-44d33970]:disabled{opacity:.6;cursor:not-allowed}.btn-spinner[data-v-44d33970]{width:.85rem;height:.85rem;border:2px solid rgba(255,255,255,.4);border-top-color:#fff;border-radius:50%;animation:spin-44d33970 .6s linear infinite;flex-shrink:0}@keyframes spin-44d33970{to{transform:rotate(360deg)}}.status-card .status-line[data-v-44d33970]{display:flex;align-items:center;gap:.5rem;margin-bottom:.5rem}.status-dot[data-v-44d33970]{width:8px;height:8px;border-radius:50%;flex-shrink:0}.status-dot.on[data-v-44d33970]{background:var(--switch-on);box-shadow:0 0 0 3px #22c55e33;animation:pulse-44d33970 2s ease-in-out infinite}.status-dot.off[data-v-44d33970]{background:#cbd5e1}@keyframes pulse-44d33970{0%,to{box-shadow:0 0 0 3px #22c55e33}50%{box-shadow:0 0 0 5px #22c55e14}}.status-on[data-v-44d33970]{color:var(--switch-on);font-weight:600;font-size:.9rem}.status-off[data-v-44d33970]{color:var(--text-secondary);font-size:.9rem}.status-port[data-v-44d33970]{color:var(--text-secondary);font-size:.875rem}.hint[data-v-44d33970]{font-size:.875rem;color:var(--text-secondary);margin-top:.25rem;line-height:1.5}.hint strong[data-v-44d33970]{color:var(--text-primary)}.hint.error[data-v-44d33970]{color:#dc2626}.pac-rules[data-v-abe2d935]{display:flex;flex-direction:column;gap:1.25rem}.card[data-v-abe2d935]{background:var(--bg-card);border-radius:var(--radius);padding:1.5rem;box-shadow:0 1px 3px #0000000f}.card-header[data-v-abe2d935]{margin-bottom:1rem}.card-header h3[data-v-abe2d935]{font-size:.95rem;font-weight:600;display:flex;align-items:center;gap:.5rem;margin-bottom:.25rem}.rule-count[data-v-abe2d935]{display:inline-flex;align-items:center;justify-content:center;background:#e0e7ff;color:#3730a3;font-size:.75rem;font-weight:600;min-width:1.4rem;height:1.4rem;padding:0 .35rem;border-radius:999px}.desc[data-v-abe2d935]{color:var(--text-secondary);font-size:.875rem;line-height:1.5}.toolbar[data-v-abe2d935]{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;margin-bottom:1rem}.btn[data-v-abe2d935]{display:inline-flex;align-items:center;gap:.35rem;padding:.45rem .85rem;font-size:.875rem;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg-card);cursor:pointer;color:var(--text-primary);transition:background .15s,border-color .15s;white-space:nowrap}.btn svg[data-v-abe2d935]{width:.9rem;height:.9rem;flex-shrink:0}.btn[data-v-abe2d935]:hover:not(:disabled){background:#f1f5f9;border-color:#cbd5e1}.btn[data-v-abe2d935]:disabled{opacity:.5;cursor:not-allowed}.btn.primary[data-v-abe2d935]{background:var(--btn-primary);color:#fff;border-color:var(--btn-primary)}.btn.primary[data-v-abe2d935]:hover:not(:disabled){background:var(--btn-primary-hover);border-color:var(--btn-primary-hover)}.search-wrap[data-v-abe2d935]{position:relative;margin-left:auto}.search-icon[data-v-abe2d935]{position:absolute;left:.6rem;top:50%;transform:translateY(-50%);width:.9rem;height:.9rem;color:var(--text-secondary);pointer-events:none}.search-input[data-v-abe2d935]{padding:.45rem .75rem .45rem 2rem;border:1px solid var(--border);border-radius:var(--radius);font-size:.875rem;min-width:180px}.table-wrap[data-v-abe2d935]{overflow-x:auto}.table[data-v-abe2d935]{width:100%;border-collapse:collapse}.table th[data-v-abe2d935],.table td[data-v-abe2d935]{padding:.6rem .75rem;text-align:left;border-bottom:1px solid var(--border)}.table th[data-v-abe2d935]{font-weight:600;color:var(--text-secondary);font-size:.82rem;text-transform:uppercase;letter-spacing:.03em}.table tbody tr[data-v-abe2d935]:hover{background:#f8fafc}.table .empty[data-v-abe2d935]{color:var(--text-secondary);text-align:center;padding:2rem;font-size:.9rem}.pattern-cell[data-v-abe2d935]{font-family:Menlo,Consolas,monospace;font-size:.875rem}.priority-cell[data-v-abe2d935]{color:var(--text-secondary);font-size:.875rem}.action-badge[data-v-abe2d935]{display:inline-block;padding:.15rem .5rem;border-radius:999px;font-size:.78rem;font-weight:500}.action-badge.proxy[data-v-abe2d935]{background:#dbeafe;color:#1d4ed8}.action-badge.direct[data-v-abe2d935]{background:#f1f5f9;color:#475569}.ops-cell[data-v-abe2d935]{white-space:nowrap}.icon-btn[data-v-abe2d935]{display:inline-flex;align-items:center;justify-content:center;width:1.75rem;height:1.75rem;border:none;border-radius:6px;background:transparent;cursor:pointer;color:var(--text-secondary);transition:background .15s,color .15s;margin-right:.25rem}.icon-btn svg[data-v-abe2d935]{width:.9rem;height:.9rem}.icon-btn[data-v-abe2d935]:hover{background:#f1f5f9;color:var(--text-primary)}.icon-btn.danger[data-v-abe2d935]:hover{background:#fee2e2;color:#dc2626}.modal[data-v-abe2d935]{position:fixed;top:0;right:0;bottom:0;left:0;background:#0006;display:flex;align-items:center;justify-content:center;z-index:100}.modal-content[data-v-abe2d935]{background:var(--bg-card);border-radius:var(--radius);padding:1.5rem;min-width:360px;max-width:440px;width:100%;box-shadow:0 8px 24px #00000026}.modal-content h3[data-v-abe2d935]{font-size:1rem;font-weight:600;margin-bottom:1.25rem}.field[data-v-abe2d935]{margin-bottom:1rem}.field label[data-v-abe2d935]{display:block;font-size:.875rem;margin-bottom:.35rem;color:var(--text-secondary)}.field input[data-v-abe2d935]{width:100%;padding:.5rem .75rem;border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem}.action-toggle[data-v-abe2d935]{display:flex;gap:0;border:1px solid var(--border);border-radius:var(--radius);overflow:hidden}.toggle-opt[data-v-abe2d935]{flex:1;padding:.45rem 0;font-size:.875rem;border:none;background:transparent;cursor:pointer;color:var(--text-secondary);transition:background .15s,color .15s}.toggle-opt[data-v-abe2d935]:first-child{border-right:1px solid var(--border)}.toggle-opt.active[data-v-abe2d935]{background:var(--btn-primary);color:#fff}.modal-actions[data-v-abe2d935]{display:flex;gap:.75rem;justify-content:flex-end;margin-top:1.25rem}.traffic-page[data-v-4331eb78]{display:flex;flex-direction:column;gap:1.25rem}.card[data-v-4331eb78]{background:var(--bg-card);border-radius:var(--radius);padding:1.5rem;box-shadow:0 1px 3px #0000000f}.capture-card[data-v-4331eb78]{padding:1rem 1.5rem}.capture-banner[data-v-4331eb78]{display:flex;align-items:center;gap:.5rem;background:#eff6ff;color:#1e40af;padding:.5rem .75rem;border-radius:var(--radius);font-size:.875rem;margin-bottom:1rem}.capture-banner svg[data-v-4331eb78]{width:1rem;height:1rem;flex-shrink:0}.toolbar[data-v-4331eb78]{display:flex;flex-wrap:wrap;align-items:center;gap:.5rem;margin-bottom:1rem}.auto-refresh[data-v-4331eb78]{display:flex;align-items:center;gap:.5rem}.toggle-btn[data-v-4331eb78]{width:36px;height:20px;border-radius:10px;background:var(--switch-off);padding:2px;border:none;cursor:pointer;transition:background .2s;position:relative}.toggle-btn.on[data-v-4331eb78]{background:var(--switch-on)}.toggle-knob[data-v-4331eb78]{display:block;width:16px;height:16px;border-radius:50%;background:#fff;transition:transform .2s;box-shadow:0 1px 3px #0003}.toggle-btn.on .toggle-knob[data-v-4331eb78]{transform:translate(16px)}.toggle-label[data-v-4331eb78]{font-size:.875rem;color:var(--text-secondary)}.record-count[data-v-4331eb78]{font-size:.82rem;color:var(--text-secondary);margin-left:auto}.btn[data-v-4331eb78]{display:inline-flex;align-items:center;gap:.35rem;padding:.45rem .85rem;font-size:.875rem;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg-card);cursor:pointer;color:var(--text-primary);transition:background .15s}.btn svg[data-v-4331eb78]{width:.9rem;height:.9rem}.btn[data-v-4331eb78]:hover:not(:disabled){background:#f1f5f9}.btn[data-v-4331eb78]:disabled{opacity:.5;cursor:not-allowed}.btn.small[data-v-4331eb78]{padding:.35rem .75rem;font-size:.82rem}.toolbar .search-input[data-v-4331eb78],.toolbar .filter-select[data-v-4331eb78],.toolbar .status-input[data-v-4331eb78],.toolbar .sort-order-btn[data-v-4331eb78]{height:2.1rem;padding:0 .6rem;border:1px solid var(--border);border-radius:var(--radius);font-size:.875rem;background:#fff}.toolbar .search-input[data-v-4331eb78]{min-width:160px}.toolbar .filter-select[data-v-4331eb78]{min-width:6rem;cursor:pointer}.toolbar .status-input[data-v-4331eb78]{width:72px}.toolbar .sort-order-btn[data-v-4331eb78]{width:2.1rem;padding:0;cursor:pointer;background:#f8fafc}.table-wrap[data-v-4331eb78]{overflow-x:auto}.table[data-v-4331eb78]{width:100%;border-collapse:collapse}.table th[data-v-4331eb78],.table td[data-v-4331eb78]{padding:.55rem .75rem;text-align:left;border-bottom:1px solid var(--border)}.table th[data-v-4331eb78]{font-weight:600;color:var(--text-secondary);font-size:.82rem;text-transform:uppercase;letter-spacing:.03em}.table tbody tr[data-v-4331eb78]:hover{background:#f8fafc}.table .empty[data-v-4331eb78]{color:var(--text-secondary);text-align:center;padding:2rem;font-size:.9rem}.time-cell[data-v-4331eb78]{font-size:.82rem;color:var(--text-secondary);white-space:nowrap}.url-cell[data-v-4331eb78]{max-width:320px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.875rem}.action-badge[data-v-4331eb78]{display:inline-block;padding:.15rem .5rem;border-radius:999px;font-size:.78rem;font-weight:500}.action-badge.proxy[data-v-4331eb78]{background:#dbeafe;color:#1d4ed8}.action-badge.direct[data-v-4331eb78]{background:#f1f5f9;color:#475569}.row-clickable[data-v-4331eb78]{cursor:pointer}.method-tag[data-v-4331eb78]{font-size:.78rem;padding:.15rem .4rem;border-radius:4px;font-weight:500}.method-tag.GET[data-v-4331eb78]{background:#dbeafe;color:#1d4ed8}.method-tag.POST[data-v-4331eb78]{background:#d1fae5;color:#047857}.method-tag.PUT[data-v-4331eb78]{background:#fef3c7;color:#b45309}.method-tag.DELETE[data-v-4331eb78]{background:#fee2e2;color:#b91c1c}.method-tag.HEAD[data-v-4331eb78],.method-tag.OPTIONS[data-v-4331eb78]{background:#f1f5f9;color:#64748b}.method-tag.PATCH[data-v-4331eb78]{background:#fce7f3;color:#9d174d}.status-tag[data-v-4331eb78]{font-size:.82rem;padding:.15rem .4rem;border-radius:4px;font-weight:500}.status-2xx[data-v-4331eb78]{background:#d1fae5;color:#047857}.status-3xx[data-v-4331eb78]{background:#e0e7ff;color:#3730a3}.status-4xx[data-v-4331eb78]{background:#fee2e2;color:#b91c1c}.status-5xx[data-v-4331eb78]{background:#fecaca;color:#991b1b}.btn-link[data-v-4331eb78]{background:none;border:none;color:var(--btn-primary);cursor:pointer;font-size:.875rem;padding:0}.btn-link[data-v-4331eb78]:hover{text-decoration:underline}.pagination[data-v-4331eb78]{margin-top:1rem;display:flex;align-items:center;gap:.75rem}.pagination-info[data-v-4331eb78]{font-size:.875rem;color:var(--text-secondary)}.drawer-mask[data-v-4331eb78]{position:fixed;top:0;right:0;bottom:0;left:0;background:#0000004d;z-index:100}.drawer[data-v-4331eb78]{position:fixed;top:0;right:0;width:min(560px,100vw);height:100vh;background:var(--bg-card);box-shadow:-2px 0 16px #0000001f;z-index:101;display:flex;flex-direction:column;overflow:hidden}.drawer-header[data-v-4331eb78]{display:flex;align-items:center;justify-content:space-between;padding:1rem 1.25rem;border-bottom:1px solid var(--border);flex-shrink:0}.drawer-title[data-v-4331eb78]{font-size:.95rem;font-weight:600}.drawer-close[data-v-4331eb78]{width:2rem;height:2rem;border:none;background:none;font-size:1.5rem;line-height:1;cursor:pointer;color:var(--text-secondary);border-radius:6px}.drawer-close[data-v-4331eb78]:hover{background:#f1f5f9;color:var(--text-primary)}.drawer-url-section[data-v-4331eb78]{margin-bottom:1.25rem;padding-bottom:1rem;border-bottom:1px solid var(--border)}.drawer-url-head[data-v-4331eb78]{display:flex;align-items:center;justify-content:space-between;gap:.5rem;margin-bottom:.35rem}.drawer-method-tag[data-v-4331eb78]{font-size:.78rem;font-weight:600;padding:.15rem .4rem;border-radius:4px}.btn-copy-url[data-v-4331eb78]{padding:.3rem .6rem;font-size:.82rem;border:1px solid var(--border);border-radius:var(--radius);background:#f1f5f9;cursor:pointer}.btn-copy-url[data-v-4331eb78]:hover{background:#e2e8f0}.drawer-url[data-v-4331eb78]{margin:0;padding:.5rem .6rem;background:#f8fafc;border-radius:var(--radius);font-size:.82rem;line-height:1.4;white-space:pre-wrap;word-break:break-all;max-height:5rem;overflow:auto}.drawer-tabs[data-v-4331eb78]{display:flex;gap:.25rem;padding:.5rem 1rem;border-bottom:1px solid var(--border);flex-shrink:0}.drawer-tabs button[data-v-4331eb78]{padding:.4rem .75rem;font-size:.875rem;border:none;background:none;border-radius:var(--radius);cursor:pointer;color:var(--text-secondary)}.drawer-tabs button.active[data-v-4331eb78]{background:var(--btn-primary);color:#fff}.drawer-body[data-v-4331eb78]{flex:1;overflow:auto;padding:1rem 1.25rem}.detail-section[data-v-4331eb78]{margin-bottom:1.25rem}.detail-section h4[data-v-4331eb78]{font-size:.82rem;margin-bottom:.35rem;color:var(--text-secondary);font-weight:600;text-transform:uppercase;letter-spacing:.03em}.headers-pre[data-v-4331eb78],.body-pre[data-v-4331eb78]{margin:0;padding:.75rem;background:#f8fafc;border-radius:var(--radius);font-size:.8rem;line-height:1.4;overflow-x:auto;white-space:pre-wrap;word-break:break-all;max-height:280px;overflow-y:auto}.shadowsocks-page[data-v-2ef4dba1]{display:flex;flex-direction:column;gap:1.25rem}.card[data-v-2ef4dba1]{background:var(--bg-card);border-radius:var(--radius);padding:1.5rem;box-shadow:0 1px 3px #0000000f}.card h3[data-v-2ef4dba1]{font-size:.95rem;font-weight:600;margin-bottom:.35rem}.desc[data-v-2ef4dba1]{color:var(--text-secondary);font-size:.875rem;line-height:1.5}.enable-row[data-v-2ef4dba1]{display:flex;align-items:flex-start;justify-content:space-between;gap:1rem}.toggle-btn[data-v-2ef4dba1]{flex-shrink:0;width:44px;height:24px;border-radius:12px;background:var(--switch-off);padding:2px;border:none;cursor:pointer;transition:background .2s;margin-top:.2rem}.toggle-btn.on[data-v-2ef4dba1]{background:var(--switch-on)}.toggle-btn[data-v-2ef4dba1]:disabled{opacity:.5;cursor:not-allowed}.toggle-knob[data-v-2ef4dba1]{display:block;width:20px;height:20px;border-radius:50%;background:#fff;transition:transform .2s;box-shadow:0 1px 3px #0003}.toggle-btn.on .toggle-knob[data-v-2ef4dba1]{transform:translate(20px)}.fields[data-v-2ef4dba1]{display:grid;grid-template-columns:1fr 1fr;gap:1rem;margin-bottom:1rem}.field-wide[data-v-2ef4dba1]{grid-column:span 2}.field label[data-v-2ef4dba1]{display:block;font-size:.875rem;margin-bottom:.35rem;color:var(--text-secondary)}.field input[data-v-2ef4dba1],.field select[data-v-2ef4dba1]{width:100%;padding:.5rem .75rem;border:1px solid var(--border);border-radius:var(--radius);font-size:.9rem}.password-wrap[data-v-2ef4dba1]{position:relative}.password-wrap input[data-v-2ef4dba1]{padding-right:2.5rem}.eye-btn[data-v-2ef4dba1]{position:absolute;right:.5rem;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;color:var(--text-secondary);padding:.2rem;display:flex;align-items:center}.eye-btn svg[data-v-2ef4dba1]{width:1rem;height:1rem}.eye-btn[data-v-2ef4dba1]:hover{color:var(--text-primary)}.actions[data-v-2ef4dba1]{margin-top:.25rem}.btn[data-v-2ef4dba1]{display:inline-flex;align-items:center;gap:.35rem;padding:.45rem .85rem;font-size:.875rem;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg-card);cursor:pointer;color:var(--text-primary);transition:background .15s}.btn[data-v-2ef4dba1]:hover:not(:disabled){background:#f1f5f9}.btn[data-v-2ef4dba1]:disabled{opacity:.5;cursor:not-allowed}.btn.primary[data-v-2ef4dba1]{background:var(--btn-primary);color:#fff;border-color:var(--btn-primary)}.btn.primary[data-v-2ef4dba1]:hover:not(:disabled){background:var(--btn-primary-hover);border-color:var(--btn-primary-hover)}.btn.danger[data-v-2ef4dba1]{color:#dc2626;border-color:#fecaca}.btn.danger[data-v-2ef4dba1]:hover:not(:disabled){background:#fef2f2}.btn.icon-only[data-v-2ef4dba1]{padding:.45rem}.btn.icon-only svg[data-v-2ef4dba1]{width:.9rem;height:.9rem}.btn-spinner[data-v-2ef4dba1]{width:.85rem;height:.85rem;border:2px solid rgba(255,255,255,.4);border-top-color:#fff;border-radius:50%;animation:spin-2ef4dba1 .6s linear infinite;flex-shrink:0}@keyframes spin-2ef4dba1{to{transform:rotate(360deg)}}.status-header[data-v-2ef4dba1]{display:flex;align-items:center;justify-content:space-between;margin-bottom:.75rem}.status-header h3[data-v-2ef4dba1]{margin-bottom:0}.status-actions[data-v-2ef4dba1]{display:flex;gap:.5rem}.status-line[data-v-2ef4dba1]{display:flex;align-items:center;gap:.5rem;margin-bottom:.35rem}.status-dot[data-v-2ef4dba1]{width:8px;height:8px;border-radius:50%;flex-shrink:0}.status-dot.on[data-v-2ef4dba1]{background:var(--switch-on);box-shadow:0 0 0 3px #22c55e33;animation:pulse-2ef4dba1 2s ease-in-out infinite}.status-dot.off[data-v-2ef4dba1]{background:#cbd5e1}@keyframes pulse-2ef4dba1{0%,to{box-shadow:0 0 0 3px #22c55e33}50%{box-shadow:0 0 0 5px #22c55e14}}.status-on[data-v-2ef4dba1]{color:var(--switch-on);font-weight:600;font-size:.9rem}.status-off[data-v-2ef4dba1]{color:var(--text-secondary);font-size:.9rem}.status-pid[data-v-2ef4dba1],.status-port[data-v-2ef4dba1]{font-size:.82rem;color:var(--text-secondary)}.status-error[data-v-2ef4dba1]{font-size:.875rem;color:#dc2626;margin-top:.35rem}.log-section[data-v-2ef4dba1]{margin-top:1rem;border-top:1px solid var(--border);padding-top:.75rem}.log-toggle[data-v-2ef4dba1]{display:flex;align-items:center;gap:.35rem;background:none;border:none;cursor:pointer;font-size:.875rem;color:var(--text-secondary);padding:0}.log-toggle svg[data-v-2ef4dba1]{width:.9rem;height:.9rem;transition:transform .15s}.log-toggle[data-v-2ef4dba1]:hover{color:var(--text-primary)}.log-count[data-v-2ef4dba1]{font-size:.78rem;background:#f1f5f9;padding:.1rem .4rem;border-radius:999px}.log-pre[data-v-2ef4dba1]{margin-top:.75rem;padding:.75rem;background:#0f172a;color:#94a3b8;border-radius:var(--radius);font-size:.78rem;line-height:1.5;max-height:240px;overflow-y:auto;white-space:pre-wrap;word-break:break-all}:root{--bg-sidebar: #1e293b;--bg-sidebar-hover: #334155;--bg-sidebar-active: #2563eb;--text-sidebar: #e2e8f0;--text-sidebar-muted: #94a3b8;--bg-main: #f8fafc;--bg-card: #ffffff;--text-primary: #0f172a;--text-secondary: #64748b;--border: #e2e8f0;--switch-on: #22c55e;--switch-off: #cbd5e1;--btn-primary: #2563eb;--btn-primary-hover: #1d4ed8;--radius: 8px;--font: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif}*{box-sizing:border-box;margin:0;padding:0}body{font-family:var(--font);background:var(--bg-main);color:var(--text-primary);line-height:1.5;min-height:100vh}#app{min-height:100vh;display:flex;flex-direction:column}button{font-family:inherit;cursor:pointer;border:none;border-radius:var(--radius)}input,select{font-family:inherit;border-radius:var(--radius);outline:none;transition:border-color .15s}input:focus,select:focus{border-color:var(--btn-primary);box-shadow:0 0 0 3px #2563eb1f}a{color:var(--btn-primary);text-decoration:none}a:hover{text-decoration:underline}
|
package/dist/index.html
CHANGED
|
@@ -4,11 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>PAC 代理控制台</title>
|
|
7
|
-
<
|
|
8
|
-
<link rel="
|
|
9
|
-
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700&display=swap" rel="stylesheet" />
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-DD-_gVYC.js"></script>
|
|
11
|
-
<link rel="stylesheet" crossorigin href="/assets/index-IXkmSGx6.css">
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-CweQSJse.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-H0IGlOgE.css">
|
|
12
9
|
</head>
|
|
13
10
|
<body>
|
|
14
11
|
<div id="app"></div>
|
package/lib/index.js
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { createRequire } from 'module';
|
|
4
3
|
import { Command } from 'commander';
|
|
4
|
+
import { createRequire } from 'module';
|
|
5
5
|
import { startServer } from './server.js';
|
|
6
6
|
|
|
7
7
|
const require = createRequire(import.meta.url);
|
|
8
|
-
const pkg = require('../package.json');
|
|
9
8
|
|
|
10
9
|
const program = new Command();
|
|
11
10
|
|
|
12
11
|
program
|
|
13
12
|
.name('pac-proxy')
|
|
14
13
|
.description('PAC/全局代理本地控制台')
|
|
15
|
-
.version(
|
|
14
|
+
.version(require('../package.json').version);
|
|
16
15
|
|
|
17
16
|
program
|
|
18
17
|
.command('serve')
|
package/lib/local-store.js
CHANGED
|
@@ -10,12 +10,21 @@ const DEFAULT_CONFIG = {
|
|
|
10
10
|
proxy: {
|
|
11
11
|
enabled: false,
|
|
12
12
|
mode: 'pac',
|
|
13
|
-
upstream: '',
|
|
13
|
+
upstream: 'socks5://127.0.0.1:1080',
|
|
14
14
|
httpPort: 5175,
|
|
15
15
|
httpsPort: 5176,
|
|
16
16
|
applySystemProxy: true,
|
|
17
17
|
},
|
|
18
18
|
pacRules: [],
|
|
19
|
+
sslocal: {
|
|
20
|
+
enabled: false,
|
|
21
|
+
server: '',
|
|
22
|
+
serverPort: 8388,
|
|
23
|
+
localPort: 1080,
|
|
24
|
+
password: '',
|
|
25
|
+
method: 'aes-256-gcm',
|
|
26
|
+
timeout: 300,
|
|
27
|
+
},
|
|
19
28
|
};
|
|
20
29
|
|
|
21
30
|
function getStoreDir() {
|
|
@@ -51,6 +60,7 @@ export function loadConfig() {
|
|
|
51
60
|
mode,
|
|
52
61
|
proxy: { ...DEFAULT_CONFIG.proxy, ...(data.proxy || {}) },
|
|
53
62
|
pacRules: Array.isArray(data.pacRules) ? data.pacRules : DEFAULT_CONFIG.pacRules,
|
|
63
|
+
sslocal: { ...DEFAULT_CONFIG.sslocal, ...(data.sslocal || {}) },
|
|
54
64
|
};
|
|
55
65
|
} catch {
|
|
56
66
|
return JSON.parse(JSON.stringify(DEFAULT_CONFIG));
|
|
@@ -92,7 +102,14 @@ export function getPacRules() {
|
|
|
92
102
|
}
|
|
93
103
|
|
|
94
104
|
export function setPacRules(rules) {
|
|
95
|
-
const
|
|
105
|
+
const raw = Array.isArray(rules) ? rules : (rules?.rules ?? []);
|
|
106
|
+
const seen = new Set();
|
|
107
|
+
const list = raw.filter((r) => {
|
|
108
|
+
const key = (r.pattern || '').trim();
|
|
109
|
+
if (!key || seen.has(key)) return false;
|
|
110
|
+
seen.add(key);
|
|
111
|
+
return true;
|
|
112
|
+
});
|
|
96
113
|
const config = loadConfig();
|
|
97
114
|
if (config.mode === 'remote') {
|
|
98
115
|
saveRemoteConfig({ pacRules: list });
|
|
@@ -108,6 +125,9 @@ export function getProxyConfig() {
|
|
|
108
125
|
const proxy = { ...DEFAULT_CONFIG.proxy, ...config.proxy };
|
|
109
126
|
proxy.enabled = proxy.enabled === true || proxy.enabled === 'true';
|
|
110
127
|
proxy.applySystemProxy = proxy.applySystemProxy !== false && proxy.applySystemProxy !== 'false';
|
|
128
|
+
if (!proxy.upstream || !proxy.upstream.trim()) {
|
|
129
|
+
proxy.upstream = DEFAULT_CONFIG.proxy.upstream;
|
|
130
|
+
}
|
|
111
131
|
return proxy;
|
|
112
132
|
}
|
|
113
133
|
|
|
@@ -120,3 +140,20 @@ export function setProxyConfig(proxy) {
|
|
|
120
140
|
saveConfig(config);
|
|
121
141
|
return config.proxy;
|
|
122
142
|
}
|
|
143
|
+
|
|
144
|
+
export function getSslocalConfig() {
|
|
145
|
+
const config = loadConfig();
|
|
146
|
+
return { ...DEFAULT_CONFIG.sslocal, ...(config.sslocal || {}) };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function setSslocalConfig(sslocal) {
|
|
150
|
+
const config = loadConfig();
|
|
151
|
+
config.sslocal = { ...DEFAULT_CONFIG.sslocal, ...sslocal };
|
|
152
|
+
config.sslocal.enabled = config.sslocal.enabled === true || config.sslocal.enabled === 'true';
|
|
153
|
+
saveConfig(config);
|
|
154
|
+
return config.sslocal;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function getSslocalConfigPath() {
|
|
158
|
+
return path.join(getStoreDir(), 'sslocal-config.json');
|
|
159
|
+
}
|
package/lib/server.js
CHANGED
|
@@ -5,7 +5,7 @@ import dotenv from 'dotenv';
|
|
|
5
5
|
import express from 'express';
|
|
6
6
|
import cors from 'cors';
|
|
7
7
|
import { createServer } from 'http';
|
|
8
|
-
import { loadConfig, saveConfig, getPacRules, setPacRules, getProxyConfig, setProxyConfig } from './local-store.js';
|
|
8
|
+
import { loadConfig, saveConfig, getPacRules, setPacRules, getProxyConfig, setProxyConfig, getSslocalConfig, setSslocalConfig } from './local-store.js';
|
|
9
9
|
import { getRecords as getTrafficRecords } from './traffic-log.js';
|
|
10
10
|
import { getCaptures } from './capture-log.js';
|
|
11
11
|
import { getPort } from 'get-port-please';
|
|
@@ -13,6 +13,7 @@ import { startProxyServer, stopProxyServer, getProxyStatus } from './proxy-serve
|
|
|
13
13
|
import { startMitmProxyServer, stopMitmProxyServer, getMitmProxyStatus, getCaCertPath, hasCaCert } from './mitm-proxy-server.js';
|
|
14
14
|
import { setSystemProxy, clearSystemProxy } from './system-proxy.js';
|
|
15
15
|
import { generatePacJs } from './pac-file.js';
|
|
16
|
+
import { startSslocal, stopSslocal, getSslocalStatus } from './sslocal-manager.js';
|
|
16
17
|
|
|
17
18
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
19
|
const envDir = path.resolve(__dirname, '..');
|
|
@@ -25,6 +26,19 @@ let controlPanelPort = null;
|
|
|
25
26
|
|
|
26
27
|
async function applyProxyService(proxyOverride) {
|
|
27
28
|
const proxy = proxyOverride ?? getProxyConfig();
|
|
29
|
+
const sslocalCfg = getSslocalConfig();
|
|
30
|
+
|
|
31
|
+
// 若 sslocal 启用,先确保进程运行,并覆盖 upstream
|
|
32
|
+
if (sslocalCfg.enabled && sslocalCfg.server) {
|
|
33
|
+
try {
|
|
34
|
+
const status = getSslocalStatus();
|
|
35
|
+
if (!status.running) await startSslocal(sslocalCfg);
|
|
36
|
+
proxy.upstream = `socks5://127.0.0.1:${sslocalCfg.localPort || 1080}`;
|
|
37
|
+
} catch (e) {
|
|
38
|
+
console.error('sslocal 启动失败:', e.message);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
28
42
|
const isMitm = (proxy.mode || '') === 'mitm';
|
|
29
43
|
const prevStatus = isMitm ? getMitmProxyStatus() : getProxyStatus();
|
|
30
44
|
const wasProxyRunning = prevStatus.running === true;
|
|
@@ -226,9 +240,56 @@ export async function startServer(port) {
|
|
|
226
240
|
res.sendFile(path.join(distPath, 'index.html'));
|
|
227
241
|
});
|
|
228
242
|
|
|
243
|
+
// ---------- sslocal API ----------
|
|
244
|
+
app.get('/api/local/sslocal', (req, res) => {
|
|
245
|
+
try { res.json(getSslocalConfig()); }
|
|
246
|
+
catch (e) { res.status(500).json({ error: String(e.message) }); }
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
app.put('/api/local/sslocal', async (req, res) => {
|
|
250
|
+
try {
|
|
251
|
+
const cfg = setSslocalConfig(req.body);
|
|
252
|
+
// 若已启用且进程未运行则启动;若禁用则停止
|
|
253
|
+
if (cfg.enabled && cfg.server) {
|
|
254
|
+
const status = getSslocalStatus();
|
|
255
|
+
if (!status.running) await startSslocal(cfg);
|
|
256
|
+
} else if (!cfg.enabled) {
|
|
257
|
+
await stopSslocal();
|
|
258
|
+
}
|
|
259
|
+
res.json(cfg);
|
|
260
|
+
} catch (e) { res.status(500).json({ error: String(e.message) }); }
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
app.get('/api/local/sslocal-status', (req, res) => {
|
|
264
|
+
try { res.json(getSslocalStatus()); }
|
|
265
|
+
catch (e) { res.status(500).json({ running: false, pid: null, error: String(e.message), logs: [] }); }
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
app.post('/api/local/sslocal/start', async (req, res) => {
|
|
269
|
+
try {
|
|
270
|
+
const cfg = getSslocalConfig();
|
|
271
|
+
if (!cfg.server) return res.status(400).json({ error: '请先配置服务器地址' });
|
|
272
|
+
await startSslocal(cfg);
|
|
273
|
+
res.json(getSslocalStatus());
|
|
274
|
+
} catch (e) { res.status(500).json({ error: String(e.message) }); }
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
app.post('/api/local/sslocal/stop', async (req, res) => {
|
|
278
|
+
try {
|
|
279
|
+
await stopSslocal();
|
|
280
|
+
res.json({ ok: true });
|
|
281
|
+
} catch (e) { res.status(500).json({ error: String(e.message) }); }
|
|
282
|
+
});
|
|
283
|
+
|
|
229
284
|
const actualPort = await getPort({ port, portRange: [port, port + 100] });
|
|
230
285
|
const server = createServer(app);
|
|
231
286
|
|
|
287
|
+
// 进程退出时清理 sslocal 子进程
|
|
288
|
+
const cleanup = () => { stopSslocal().catch(() => {}); };
|
|
289
|
+
process.once('exit', cleanup);
|
|
290
|
+
process.once('SIGINT', () => { cleanup(); process.exit(0); });
|
|
291
|
+
process.once('SIGTERM', () => { cleanup(); process.exit(0); });
|
|
292
|
+
|
|
232
293
|
server.listen(actualPort, '127.0.0.1', async () => {
|
|
233
294
|
controlPanelPort = actualPort;
|
|
234
295
|
console.log(`控制台已启动: http://127.0.0.1:${actualPort}`);
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import net from 'net';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { createRequire } from 'module';
|
|
6
|
+
import { getSslocalConfigPath } from './local-store.js';
|
|
7
|
+
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
|
|
10
|
+
const LOG_MAX = 200;
|
|
11
|
+
|
|
12
|
+
const PKG_MAP = {
|
|
13
|
+
'win32-x64': 'pac-proxy-sslocal-win32-x64',
|
|
14
|
+
'darwin-x64': 'pac-proxy-sslocal-darwin-x64',
|
|
15
|
+
'linux-x64': 'pac-proxy-sslocal-linux-x64',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
let _proc = null;
|
|
19
|
+
let _logs = [];
|
|
20
|
+
let _lastError = null;
|
|
21
|
+
|
|
22
|
+
function getBinaryPath() {
|
|
23
|
+
const key = `${process.platform}-${process.arch}`;
|
|
24
|
+
const pkgName = PKG_MAP[key];
|
|
25
|
+
if (pkgName) {
|
|
26
|
+
try {
|
|
27
|
+
const pkgDir = path.dirname(require.resolve(`${pkgName}/package.json`));
|
|
28
|
+
const bin = process.platform === 'win32' ? 'sslocal.exe' : 'sslocal';
|
|
29
|
+
const p = path.join(pkgDir, bin);
|
|
30
|
+
if (fs.existsSync(p)) return p;
|
|
31
|
+
} catch (_) {}
|
|
32
|
+
}
|
|
33
|
+
throw new Error(
|
|
34
|
+
`当前平台 ${process.platform}/${process.arch} 没有可用的 sslocal 二进制。\n` +
|
|
35
|
+
`请手动安装:npm install ${PKG_MAP[key] ?? '对应平台包'}`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function ensureExecutable(binPath) {
|
|
40
|
+
if (process.platform !== 'win32') {
|
|
41
|
+
try { fs.chmodSync(binPath, 0o755); } catch (_) {}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function pushLog(line) {
|
|
46
|
+
_logs.push(line);
|
|
47
|
+
if (_logs.length > LOG_MAX) _logs.shift();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function waitForPort(port, timeoutMs = 4000) {
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
const deadline = Date.now() + timeoutMs;
|
|
53
|
+
function attempt() {
|
|
54
|
+
const sock = new net.Socket();
|
|
55
|
+
sock.setTimeout(300);
|
|
56
|
+
sock.connect(port, '127.0.0.1', () => {
|
|
57
|
+
sock.destroy();
|
|
58
|
+
resolve(true);
|
|
59
|
+
});
|
|
60
|
+
sock.on('error', () => {
|
|
61
|
+
sock.destroy();
|
|
62
|
+
if (Date.now() < deadline) setTimeout(attempt, 200);
|
|
63
|
+
else resolve(false);
|
|
64
|
+
});
|
|
65
|
+
sock.on('timeout', () => {
|
|
66
|
+
sock.destroy();
|
|
67
|
+
if (Date.now() < deadline) setTimeout(attempt, 200);
|
|
68
|
+
else resolve(false);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
attempt();
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export async function startSslocal(cfg) {
|
|
76
|
+
if (_proc) await stopSslocal();
|
|
77
|
+
|
|
78
|
+
const binPath = getBinaryPath();
|
|
79
|
+
if (!fs.existsSync(binPath)) {
|
|
80
|
+
throw new Error(`sslocal 二进制不存在: ${binPath}(当前平台: ${process.platform})`);
|
|
81
|
+
}
|
|
82
|
+
ensureExecutable(binPath);
|
|
83
|
+
|
|
84
|
+
// 写入临时 config 文件
|
|
85
|
+
const configPath = getSslocalConfigPath();
|
|
86
|
+
const sslocalCfg = {
|
|
87
|
+
server: cfg.server,
|
|
88
|
+
server_port: Number(cfg.serverPort) || 8388,
|
|
89
|
+
local_address: '127.0.0.1',
|
|
90
|
+
local_port: Number(cfg.localPort) || 1080,
|
|
91
|
+
password: cfg.password,
|
|
92
|
+
method: cfg.method || 'aes-256-gcm',
|
|
93
|
+
timeout: Number(cfg.timeout) || 300,
|
|
94
|
+
};
|
|
95
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
96
|
+
fs.writeFileSync(configPath, JSON.stringify(sslocalCfg, null, 2), 'utf-8');
|
|
97
|
+
|
|
98
|
+
_logs = [];
|
|
99
|
+
_lastError = null;
|
|
100
|
+
|
|
101
|
+
_proc = spawn(binPath, ['-c', configPath], {
|
|
102
|
+
detached: false,
|
|
103
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
_proc.stdout.on('data', (d) => {
|
|
107
|
+
String(d).split('\n').filter(Boolean).forEach(pushLog);
|
|
108
|
+
});
|
|
109
|
+
_proc.stderr.on('data', (d) => {
|
|
110
|
+
String(d).split('\n').filter(Boolean).forEach(pushLog);
|
|
111
|
+
});
|
|
112
|
+
_proc.on('exit', (code, signal) => {
|
|
113
|
+
_lastError = code !== 0 ? `进程退出 code=${code} signal=${signal}` : null;
|
|
114
|
+
_proc = null;
|
|
115
|
+
});
|
|
116
|
+
_proc.on('error', (e) => {
|
|
117
|
+
_lastError = e.message;
|
|
118
|
+
_proc = null;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const localPort = Number(cfg.localPort) || 1080;
|
|
122
|
+
const ready = await waitForPort(localPort);
|
|
123
|
+
if (!ready) {
|
|
124
|
+
const recentLogs = _logs.slice(-10).join('\n');
|
|
125
|
+
throw new Error(`sslocal 启动超时,端口 ${localPort} 未就绪。\n${recentLogs}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function stopSslocal() {
|
|
130
|
+
if (!_proc) return;
|
|
131
|
+
try {
|
|
132
|
+
_proc.kill('SIGTERM');
|
|
133
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
134
|
+
if (_proc) _proc.kill('SIGKILL');
|
|
135
|
+
} catch (_) {}
|
|
136
|
+
_proc = null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function getSslocalStatus() {
|
|
140
|
+
return {
|
|
141
|
+
running: _proc !== null && !_proc.killed,
|
|
142
|
+
pid: _proc?.pid ?? null,
|
|
143
|
+
error: _lastError,
|
|
144
|
+
logs: [..._logs],
|
|
145
|
+
};
|
|
146
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pac-proxy-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "node pac proxy and web control panel",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -16,7 +16,12 @@
|
|
|
16
16
|
"scripts": {
|
|
17
17
|
"dev": "node lib/index.js serve --port 5174",
|
|
18
18
|
"build": "vite build",
|
|
19
|
-
"prepublishOnly": "
|
|
19
|
+
"prepublishOnly": "npm run build"
|
|
20
|
+
},
|
|
21
|
+
"optionalDependencies": {
|
|
22
|
+
"pac-proxy-sslocal-win32-x64": "workspace:*",
|
|
23
|
+
"pac-proxy-sslocal-darwin-x64": "workspace:*",
|
|
24
|
+
"pac-proxy-sslocal-linux-x64": "workspace:*"
|
|
20
25
|
},
|
|
21
26
|
"dependencies": {
|
|
22
27
|
"commander": "^12.0.0",
|