1619 lines
61 KiB
HTML
1619 lines
61 KiB
HTML
<style>
|
|
.oc .config-upload-modal-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
z-index: 10000;
|
|
display: none;
|
|
align-items: center;
|
|
justify-content: center;
|
|
backdrop-filter: blur(2px);
|
|
}
|
|
|
|
.oc .config-upload-modal-overlay.show {
|
|
display: flex;
|
|
}
|
|
|
|
.oc .config-upload-modal {
|
|
background: var(--bg-white);
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: var(--shadow-md);
|
|
width: 90vw;
|
|
max-width: 550px;
|
|
min-width: 400px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
border: 1px solid var(--border-light);
|
|
max-height: 85vh;
|
|
transition: all var(--transition-fast);
|
|
}
|
|
|
|
.oc .config-upload-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 20px;
|
|
border-bottom: 1px solid var(--border-light);
|
|
background: var(--bg-gray);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.oc .config-upload-title {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.oc .config-upload-footer {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 20px;
|
|
border-top: 1px solid var(--border-light);
|
|
background: var(--bg-gray);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.oc .config-upload-status {
|
|
font-size: 12px;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.oc .config-upload-buttons {
|
|
display: flex;
|
|
gap: 12px;
|
|
}
|
|
|
|
.oc .config-upload-content {
|
|
padding: 24px;
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.oc .upload-mode-selector {
|
|
margin-bottom: 24px;
|
|
border-radius: var(--radius-md);
|
|
}
|
|
|
|
.oc .mode-tabs {
|
|
display: flex;
|
|
background: var(--bg-gray);
|
|
border-radius: var(--radius-md);
|
|
padding: 4px;
|
|
gap: 4px;
|
|
}
|
|
|
|
.oc .mode-tab {
|
|
flex: 1;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
padding: 12px 16px;
|
|
border: none;
|
|
border-radius: calc(var(--radius-md) - 2px);
|
|
background: transparent;
|
|
color: var(--text-secondary);
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all var(--transition-fast);
|
|
}
|
|
|
|
.oc .mode-tab:hover {
|
|
color: var(--text-primary);
|
|
background: rgba(59, 130, 246, 0.1);
|
|
}
|
|
|
|
.oc .mode-tab.active {
|
|
background: var(--primary-color);
|
|
color: white;
|
|
box-shadow: var(--shadow-sm);
|
|
}
|
|
|
|
.oc .mode-tab svg {
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.oc .upload-mode-content {
|
|
transition: all var(--transition-fast);
|
|
}
|
|
|
|
.oc .upload-zone {
|
|
border: 2px dashed var(--border-color);
|
|
border-radius: var(--radius-md);
|
|
padding: 40px 20px;
|
|
text-align: center;
|
|
cursor: pointer;
|
|
transition: all var(--transition-fast);
|
|
background: var(--bg-light);
|
|
}
|
|
|
|
.oc .upload-zone:hover {
|
|
border-color: var(--primary-color);
|
|
background: rgba(59, 130, 246, 0.05);
|
|
}
|
|
|
|
.oc .upload-zone.dragover {
|
|
border-color: var(--primary-color);
|
|
background: rgba(59, 130, 246, 0.1);
|
|
}
|
|
|
|
.oc .upload-zone.has-file {
|
|
border-color: var(--success-color);
|
|
background: rgba(5, 150, 105, 0.05);
|
|
}
|
|
|
|
.oc .upload-icon {
|
|
margin-bottom: 16px;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.oc .upload-zone.has-file .upload-icon {
|
|
color: var(--success-color);
|
|
}
|
|
|
|
.oc .upload-primary {
|
|
font-size: 16px;
|
|
font-weight: 500;
|
|
color: var(--text-primary);
|
|
margin: 0 0 8px 0;
|
|
}
|
|
|
|
.oc .upload-secondary {
|
|
font-size: 12px;
|
|
color: var(--text-secondary);
|
|
margin: 0;
|
|
}
|
|
|
|
.oc .subscribe-form {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 15px;
|
|
}
|
|
|
|
.oc .form-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
position: relative;
|
|
}
|
|
|
|
.oc .form-group label {
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.oc .form-select-wrapper {
|
|
position: relative;
|
|
}
|
|
|
|
.oc .form-select-wrapper::after {
|
|
content: '';
|
|
position: absolute;
|
|
right: 12px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
width: 0;
|
|
height: 0;
|
|
border-left: 4px solid transparent;
|
|
border-right: 4px solid transparent;
|
|
border-top: 4px solid var(--text-secondary);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.oc .form-input,
|
|
.oc .form-select {
|
|
width: 100%;
|
|
height: 40px;
|
|
padding: 10px 12px;
|
|
border: 1px solid var(--border-light);
|
|
border-radius: var(--radius-sm);
|
|
background: var(--bg-white);
|
|
color: var(--text-primary);
|
|
font-size: 14px;
|
|
transition: all var(--transition-fast);
|
|
-webkit-appearance: none;
|
|
-moz-appearance: none;
|
|
appearance: none;
|
|
}
|
|
|
|
.oc .form-input:focus,
|
|
.oc .form-select:focus {
|
|
outline: none;
|
|
border-color: var(--primary-color);
|
|
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
|
|
}
|
|
|
|
.oc .form-input::placeholder {
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.oc .form-select {
|
|
cursor: pointer;
|
|
}
|
|
|
|
.oc .form-textarea {
|
|
width: 100%;
|
|
min-height: 80px;
|
|
padding: 10px 12px;
|
|
border: 1px solid var(--border-light);
|
|
border-radius: var(--radius-sm);
|
|
background: var(--bg-white);
|
|
color: var(--text-primary);
|
|
font-size: 14px;
|
|
font-family: inherit;
|
|
resize: vertical;
|
|
transition: all var(--transition-fast);
|
|
}
|
|
|
|
.oc .form-textarea:focus {
|
|
outline: none;
|
|
border-color: var(--primary-color);
|
|
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
|
|
}
|
|
|
|
.oc .form-row {
|
|
display: flex;
|
|
gap: 16px;
|
|
padding-top: 5px;
|
|
}
|
|
|
|
.oc .form-half {
|
|
flex: 1;
|
|
}
|
|
|
|
.oc .form-checkbox {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
color: var(--text-primary);
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.oc .form-checkbox input[type="checkbox"] {
|
|
display: none;
|
|
}
|
|
|
|
.oc .checkmark {
|
|
width: 16px;
|
|
height: 16px;
|
|
border: 2px solid var(--border-light);
|
|
border-radius: 3px;
|
|
background: var(--bg-white);
|
|
position: relative;
|
|
transition: all var(--transition-fast);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.oc .form-checkbox input[type="checkbox"]:checked + .checkmark {
|
|
background: var(--primary-color);
|
|
border-color: var(--primary-color);
|
|
}
|
|
|
|
.oc .form-checkbox input[type="checkbox"]:checked + .checkmark::after {
|
|
content: '';
|
|
position: absolute;
|
|
left: 3px;
|
|
top: 0px;
|
|
width: 4px;
|
|
height: 8px;
|
|
border: solid white;
|
|
border-width: 0 2px 2px 0;
|
|
transform: rotate(45deg);
|
|
}
|
|
|
|
.oc .form-checkbox-group {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 12px;
|
|
}
|
|
|
|
.oc .form-help {
|
|
font-size: 12px;
|
|
color: var(--text-secondary);
|
|
margin-top: 0%;
|
|
margin-bottom: 10px;
|
|
line-height: 1.4;
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 6px;
|
|
}
|
|
|
|
.oc .form-help::before {
|
|
content: '';
|
|
display: inline-block;
|
|
width: 12px;
|
|
height: 12px;
|
|
flex-shrink: 0;
|
|
margin-top: 2px;
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpath d='M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3'%3E%3C/path%3E%3Cline x1='12' y1='17' x2='12.01' y2='17'%3E%3C/line%3E%3C/svg%3E");
|
|
background-repeat: no-repeat;
|
|
background-position: center;
|
|
background-size: contain;
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.oc .filename-input-container {
|
|
margin-top: 20px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.oc .filename-input-container label {
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
color: var(--text-primary);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.oc .filename-input-container input {
|
|
flex: 1;
|
|
height: 36px;
|
|
padding: 8px 12px;
|
|
border: 1px solid var(--border-light);
|
|
border-radius: var(--radius-sm);
|
|
background: var(--bg-white);
|
|
color: var(--text-primary);
|
|
font-size: 14px;
|
|
transition: all var(--transition-fast);
|
|
}
|
|
|
|
.oc .filename-input-container input:focus {
|
|
outline: none;
|
|
border-color: var(--primary-color);
|
|
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
|
|
}
|
|
|
|
.oc .filename-extension {
|
|
font-size: 14px;
|
|
color: var(--text-secondary);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.oc .upload-progress {
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.oc .progress-bar {
|
|
height: 8px;
|
|
background: var(--bg-gray);
|
|
border-radius: 4px;
|
|
overflow: hidden;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.oc .progress-fill {
|
|
height: 100%;
|
|
background: var(--primary-color);
|
|
border-radius: 4px;
|
|
transition: width 0.3s ease;
|
|
width: 0%;
|
|
}
|
|
|
|
.oc .progress-text {
|
|
font-size: 12px;
|
|
color: var(--text-secondary);
|
|
text-align: center;
|
|
}
|
|
|
|
.oc .config-upload-buttons .btn {
|
|
padding: 8px 16px;
|
|
border: 1px solid var(--border-light);
|
|
border-radius: var(--radius-sm);
|
|
font-size: 14px;
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
transition: all var(--transition-fast);
|
|
}
|
|
|
|
.oc .cancel-btn {
|
|
background: var(--bg-white);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.oc .cancel-btn:hover {
|
|
background: var(--hover-bg);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.oc .upload-btn {
|
|
background: var(--primary-color);
|
|
color: white;
|
|
border-color: var(--primary-color);
|
|
}
|
|
|
|
.oc .upload-btn:hover:not(:disabled) {
|
|
background: var(--primary-color);
|
|
opacity: 0.9;
|
|
}
|
|
|
|
.oc .upload-btn:disabled {
|
|
background: var(--bg-gray);
|
|
color: var(--text-secondary);
|
|
border-color: var(--border-light);
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.oc .advanced-options-container {
|
|
padding: 16px;
|
|
background: var(--bg-light);
|
|
border-radius: var(--radius-sm);
|
|
border: 1px solid var(--border-light);
|
|
transition: all var(--transition-fast);
|
|
}
|
|
|
|
.oc .advanced-options-container .form-group:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.oc .advanced-options-container .sub-convert-options {
|
|
margin: 12px auto;
|
|
margin-left: 0;
|
|
background: rgba(59, 130, 246, 0.03);
|
|
border-color: rgba(59, 130, 246, 0.2);
|
|
max-width: 100%;
|
|
}
|
|
|
|
.oc .sub-convert-options {
|
|
margin: 16px auto;
|
|
padding: 16px;
|
|
background: var(--bg-light);
|
|
border-radius: var(--radius-sm);
|
|
border: 1px solid var(--border-light);
|
|
max-width: 95%;
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .config-upload-modal {
|
|
background: var(--bg-white);
|
|
border-color: var(--border-light);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .config-upload-header,
|
|
.oc[data-darkmode="true"] .config-upload-footer {
|
|
background: var(--bg-gray);
|
|
border-color: var(--border-light);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .mode-tab {
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .mode-tab:hover {
|
|
color: var(--text-primary);
|
|
background: rgba(96, 165, 250, 0.1);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .mode-tab.active {
|
|
background: var(--primary-color);
|
|
color: white;
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .upload-zone {
|
|
border-color: var(--border-color);
|
|
background: var(--bg-light);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .upload-zone:hover {
|
|
border-color: var(--primary-color);
|
|
background: rgba(96, 165, 250, 0.05);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .upload-zone.dragover {
|
|
border-color: var(--primary-color);
|
|
background: rgba(96, 165, 250, 0.1);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .upload-zone.has-file {
|
|
border-color: var(--success-color);
|
|
background: rgba(52, 211, 153, 0.05);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .upload-zone.has-file .upload-icon {
|
|
color: var(--success-color);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .form-input,
|
|
.oc[data-darkmode="true"] .form-select,
|
|
.oc[data-darkmode="true"] .filename-input-container input {
|
|
background: var(--bg-white);
|
|
border-color: var(--border-light);
|
|
color: var(--text-primary);
|
|
background-color: var(--bg-gray) !important;
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .form-input:focus,
|
|
.oc[data-darkmode="true"] .form-select:focus {
|
|
border-color: var(--primary-color);
|
|
box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.1);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .form-input::placeholder {
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .form-textarea {
|
|
background: var(--bg-gray);
|
|
border-color: var(--border-light);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .form-textarea:focus {
|
|
border-color: var(--primary-color);
|
|
box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.1);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .checkmark {
|
|
border-color: var(--border-light);
|
|
background: var(--bg-gray);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .progress-bar {
|
|
background: var(--bg-gray);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .progress-fill {
|
|
background: var(--primary-color);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .cancel-btn {
|
|
background: var(--bg-white);
|
|
color: var(--text-secondary);
|
|
border-color: var(--border-light);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .cancel-btn:hover {
|
|
background: var(--hover-bg);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .upload-btn {
|
|
background: var(--primary-color);
|
|
border-color: var(--primary-color);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .upload-btn:disabled {
|
|
background: var(--bg-gray);
|
|
color: var(--text-secondary);
|
|
border-color: var(--border-light);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .sub-convert-options {
|
|
background: rgba(96, 165, 250, 0.05);
|
|
border-color: var(--border-light);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .advanced-options-container {
|
|
background: rgba(96, 165, 250, 0.05);
|
|
border-color: var(--border-light);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .advanced-options-container .sub-convert-options {
|
|
background: rgba(96, 165, 250, 0.08);
|
|
border-color: var(--border-light);
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .form-help::before {
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23d0cfcf' stroke-width='2'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpath d='M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3'%3E%3C/path%3E%3Cline x1='12' y1='17' x2='12.01' y2='17'%3E%3C/line%3E%3C/svg%3E");
|
|
opacity: 0.6;
|
|
}
|
|
|
|
@media screen and (max-width: 768px) {
|
|
.oc .form-help {
|
|
font-size: 11px;
|
|
margin-bottom: 10px;
|
|
gap: 5px;
|
|
}
|
|
|
|
.oc .form-help::before {
|
|
width: 11px;
|
|
height: 11px;
|
|
margin-top: 1px;
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='11' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpath d='M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3'%3E%3C/path%3E%3Cline x1='12' y1='17' x2='12.01' y2='17'%3E%3C/line%3E%3C/svg%3E");
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .form-help::before {
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='11' height='11' viewBox='0 0 24 24' fill='none' stroke='%23d0cfcf' stroke-width='2'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpath d='M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3'%3E%3C/path%3E%3Cline x1='12' y1='17' x2='12.01' y2='17'%3E%3C/line%3E%3C/svg%3E");
|
|
}
|
|
|
|
.oc .sub-convert-options {
|
|
margin: 12px auto;
|
|
padding: 12px;
|
|
max-width: 98%;
|
|
}
|
|
}
|
|
|
|
@media screen and (max-width: 575px) {
|
|
.oc .form-help {
|
|
font-size: 10px;
|
|
margin-bottom: 8px;
|
|
gap: 4px;
|
|
}
|
|
|
|
.oc .form-help::before {
|
|
width: 10px;
|
|
height: 10px;
|
|
margin-top: 1px;
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpath d='M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3'%3E%3C/path%3E%3Cline x1='12' y1='17' x2='12.01' y2='17'%3E%3C/line%3E%3C/svg%3E");
|
|
}
|
|
|
|
.oc[data-darkmode="true"] .form-help::before {
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 24 24' fill='none' stroke='%23d0cfcf' stroke-width='2'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cpath d='M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3'%3E%3C/path%3E%3Cline x1='12' y1='17' x2='12.01' y2='17'%3E%3C/line%3E%3C/svg%3E");
|
|
}
|
|
|
|
.oc .sub-convert-options {
|
|
margin: 10px auto;
|
|
padding: 10px;
|
|
max-width: 100%;
|
|
}
|
|
}
|
|
|
|
@media screen and (max-width: 500px) {
|
|
.oc .config-upload-modal {
|
|
width: 95vw;
|
|
min-width: 320px;
|
|
}
|
|
|
|
.oc .config-upload-content {
|
|
padding: 20px;
|
|
}
|
|
|
|
.oc .upload-zone {
|
|
padding: 30px 15px;
|
|
}
|
|
|
|
.oc .mode-tab {
|
|
padding: 10px 12px;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.oc .mode-tab svg {
|
|
width: 14px;
|
|
height: 14px;
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<div class="oc">
|
|
<div class="config-upload-modal-overlay" id="config-upload-overlay">
|
|
<div class="config-upload-modal" id="config-upload-modal">
|
|
<div class="config-upload-header">
|
|
<div class="config-upload-title">
|
|
<span><%:Add Config File%></span>
|
|
</div>
|
|
<div class="config-upload-actions">
|
|
<button type="button" class="icon-btn" id="config-upload-close" title="<%:Close%>">
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="config-upload-content">
|
|
<div class="upload-mode-selector">
|
|
<div class="mode-tabs">
|
|
<button type="button" class="mode-tab active" id="upload-mode-file" data-mode="file">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
|
<polyline points="17,11 12,6 7,11"></polyline>
|
|
<line x1="12" y1="18" x2="12" y2="6"></line>
|
|
</svg>
|
|
<%:Upload File%>
|
|
</button>
|
|
<button type="button" class="mode-tab" id="upload-mode-subscribe" data-mode="subscribe">
|
|
<svg width="15" height="15" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M26.2401 16.373L17.1001 7.23303C14.4388 4.57168 10.0653 4.6303 7.33158 7.36397C4.59791 10.0976 4.53929 14.4712 7.20064 17.1325L15.1359 25.0678" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path>
|
|
<path d="M32.9027 23.0031L40.838 30.9384C43.4994 33.5998 43.4407 37.9733 40.7071 40.707C37.9734 43.4407 33.5999 43.4993 30.9385 40.8379L21.7985 31.6979" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path>
|
|
<path d="M26.1093 26.1416C28.843 23.4079 28.9016 19.0344 26.2403 16.373" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path>
|
|
<path d="M21.7989 21.7984C19.0652 24.5321 19.0066 28.9056 21.6679 31.5669" stroke="currentColor" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"></path>
|
|
</svg>
|
|
<%:Subscribe Link%>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="upload-mode-content" id="mode-file-content">
|
|
<div class="upload-zone" id="upload-zone">
|
|
<div class="upload-icon">
|
|
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1">
|
|
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
|
<polyline points="17,11 12,6 7,11"></polyline>
|
|
<line x1="12" y1="18" x2="12" y2="6"></line>
|
|
</svg>
|
|
</div>
|
|
<div class="upload-text">
|
|
<p class="upload-primary"><%:Click to select file or drag and drop%></p>
|
|
<p class="upload-secondary"><%:Support YAML files, max size 10MB%></p>
|
|
</div>
|
|
<input type="file" id="config-file-input" accept=".yaml,.yml" style="display: none;">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="upload-mode-content" id="mode-subscribe-content" style="display: none;">
|
|
<div class="subscribe-form">
|
|
<div class="form-group">
|
|
<label for="subscribe-url-input"><%:Subscription URL%>:</label>
|
|
<textarea id="subscribe-url-input" placeholder="<%:Enter subscription URL or multiple links (one per line)%>" class="form-textarea" rows="4"></textarea>
|
|
<div class="form-help"><%:URI and subscription address is supported when online subscription conversion enabled, multiple links should be one per line or separated by%> |</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="subscribe-ua-input"><%:User-Agent%> (<%:Optional%>):</label>
|
|
<div class="form-select-wrapper">
|
|
<select id="subscribe-ua-input" class="form-select">
|
|
<option value="clash.meta">clash.meta</option>
|
|
<option value="clash-verge/v1.5.1">clash-verge/v1.5.1</option>
|
|
<option value="Clash">clash</option>
|
|
<option value="custom"><%:Custom%></option>
|
|
</select>
|
|
</div>
|
|
<input type="text" id="subscribe-ua-custom" placeholder="<%:Enter custom User-Agent%>" class="form-input" style="display: none; margin-top: 8px;" />
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-checkbox">
|
|
<input type="checkbox" id="advanced-options-enable">
|
|
<span class="checkmark"></span>
|
|
<%:Advanced Options%>
|
|
</label>
|
|
<div class="form-help"><%:Show more subscription options%></div>
|
|
</div>
|
|
|
|
<div class="advanced-options-container" id="advanced-options-container" style="display: none;">
|
|
<div class="form-group">
|
|
<label class="form-checkbox">
|
|
<input type="checkbox" id="sub-convert-enable">
|
|
<span class="checkmark"></span>
|
|
<%:Subscribe Convert Online%>
|
|
</label>
|
|
<div class="form-help"><%:Convert Subscribe Online With Template%></div>
|
|
</div>
|
|
|
|
<div class="sub-convert-options" id="sub-convert-options" style="display: none;">
|
|
<div class="form-group">
|
|
<label for="convert-address-input"><%:Convert Address%>:</label>
|
|
<div class="form-select-wrapper">
|
|
<select id="convert-address-input" class="form-select">
|
|
<option value="https://api.dler.io/sub">api.dler.io (<%:Default%>)</option>
|
|
<option value="https://api.wcc.best/sub">api.wcc.best</option>
|
|
<option value="custom"><%:Custom%></option>
|
|
</select>
|
|
</div>
|
|
<input type="text" id="convert-address-custom" placeholder="<%:Enter custom convert address%>" class="form-input" style="display: none; margin-top: 8px;" />
|
|
<div class="form-help"><%:Note: There is A Risk of Privacy Leakage in Online Convert%></div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="template-select"><%:Template Name%>:</label>
|
|
<div class="form-select-wrapper">
|
|
<select id="template-select" class="form-select">
|
|
<%
|
|
local file = io.open("/usr/share/openclash/res/sub_ini.list", "r")
|
|
if file then
|
|
for line in file:lines() do
|
|
if line ~= "" and line ~= nil then
|
|
local template_name = string.sub(luci.sys.exec(string.format("echo '%s' |awk -F ',' '{print $1}' 2>/dev/null", line)), 1, -2)
|
|
if template_name and template_name ~= "" then
|
|
%>
|
|
<option value="<%=template_name%>"><%=template_name%></option>
|
|
<%
|
|
end
|
|
end
|
|
end
|
|
file:close()
|
|
%>
|
|
<option value="0"><%:Custom Template%></option>
|
|
<%
|
|
end
|
|
%>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group" id="custom-template-group" style="display: none;">
|
|
<label for="custom-template-input"><%:Custom Template URL%>:</label>
|
|
<input type="text" id="custom-template-input" placeholder="<%:Enter custom template URL%>" class="form-input" />
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<div class="form-group form-half">
|
|
<label class="form-checkbox">
|
|
<input type="checkbox" id="emoji-enable">
|
|
<span class="checkmark"></span>
|
|
<%:Emoji%>
|
|
</label>
|
|
</div>
|
|
<div class="form-group form-half">
|
|
<label class="form-checkbox">
|
|
<input type="checkbox" id="udp-enable">
|
|
<span class="checkmark"></span>
|
|
<%:UDP Enable%>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<div class="form-group form-half">
|
|
<label class="form-checkbox">
|
|
<input type="checkbox" id="skip-cert-verify">
|
|
<span class="checkmark"></span>
|
|
<%:skip-cert-verify%>
|
|
</label>
|
|
</div>
|
|
<div class="form-group form-half">
|
|
<label class="form-checkbox">
|
|
<input type="checkbox" id="sort-enable">
|
|
<span class="checkmark"></span>
|
|
<%:Sort%>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<div class="form-group form-half">
|
|
<label class="form-checkbox">
|
|
<input type="checkbox" id="node-type-enable">
|
|
<span class="checkmark"></span>
|
|
<%:Append Node Type%>
|
|
</label>
|
|
</div>
|
|
<div class="form-group form-half">
|
|
<label class="form-checkbox">
|
|
<input type="checkbox" id="rule-provider-enable">
|
|
<span class="checkmark"></span>
|
|
<%:Use Rule Provider%>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="custom-params-input"><%:Custom Params%>:</label>
|
|
<textarea id="custom-params-input" placeholder="<%:eg: rename=match@replace, one param per line%>" class="form-textarea" rows="3"></textarea>
|
|
<div class="form-help"><%:eg: "rename=match@replace" , "rename=\\s+([2-9])[xX]@ (HIGH:$1)"%></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="keyword-input"><%:Keyword Match%> (<%:Optional%>):</label>
|
|
<textarea id="keyword-input" placeholder="<%:Enter keywords to include nodes (one per line)%>" class="form-textarea" rows="2"></textarea>
|
|
<div class="form-help"><%:eg: hk or tw&bgp%></div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="exclude-keyword-input"><%:Exclude Keyword Match%> (<%:Optional%>):</label>
|
|
<textarea id="exclude-keyword-input" placeholder="<%:Enter keywords to exclude nodes (one per line)%>" class="form-textarea" rows="2"></textarea>
|
|
<div class="form-help"><%:eg: hk or tw&bgp%></div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="exclude-default-select"><%:Exclude Keyword Match Default%>:</label>
|
|
<div class="form-checkbox-group">
|
|
<label class="form-checkbox">
|
|
<input type="checkbox" id="exclude-expire" value="过期时间">
|
|
<span class="checkmark"></span>
|
|
<%:Expire Time%>
|
|
</label>
|
|
<label class="form-checkbox">
|
|
<input type="checkbox" id="exclude-traffic" value="剩余流量">
|
|
<span class="checkmark"></span>
|
|
<%:Remaining Traffic%>
|
|
</label>
|
|
<label class="form-checkbox">
|
|
<input type="checkbox" id="exclude-tg" value="TG群">
|
|
<span class="checkmark"></span>
|
|
<%:TG Group%>
|
|
</label>
|
|
<label class="form-checkbox">
|
|
<input type="checkbox" id="exclude-website" value="官网">
|
|
<span class="checkmark"></span>
|
|
<%:Official Website%>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="filename-input-container">
|
|
<label for="config-filename-input"><%:Config Name%>:</label>
|
|
<input type="text" id="config-filename-input" placeholder="<%:Enter config name (without extension)%>" class="form-input"/>
|
|
<div class="filename-extension">.yaml</div>
|
|
</div>
|
|
|
|
<div class="upload-progress" id="upload-progress" style="display: none;">
|
|
<div class="progress-bar">
|
|
<div class="progress-fill" id="upload-progress-fill"></div>
|
|
</div>
|
|
<div class="progress-text" id="upload-progress-text"><%:Processing...%> 0%</div>
|
|
</div>
|
|
</div>
|
|
<div class="config-upload-footer">
|
|
<div class="config-upload-status">
|
|
<span id="config-upload-status-text"><%:Ready to add config%></span>
|
|
</div>
|
|
<div class="config-upload-buttons">
|
|
<button type="button" class="btn cancel-btn" id="config-upload-cancel"><%:Cancel%></button>
|
|
<button type="button" class="btn upload-btn" id="config-upload-submit" disabled><%:Add Config%></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script type="text/javascript">
|
|
var ConfigUploader = {
|
|
overlay: null,
|
|
modal: null,
|
|
selectedFile: null,
|
|
isProcessing: false,
|
|
currentMode: 'file',
|
|
|
|
init: function() {
|
|
this.overlay = document.getElementById('config-upload-overlay');
|
|
this.modal = document.getElementById('config-upload-modal');
|
|
|
|
if (!this.overlay || !this.modal) {
|
|
return;
|
|
}
|
|
|
|
this.bindEvents();
|
|
},
|
|
|
|
bindEvents: function() {
|
|
var self = this;
|
|
var uploadZone = document.getElementById('upload-zone');
|
|
var fileInput = document.getElementById('config-file-input');
|
|
|
|
document.getElementById('upload-mode-file').addEventListener('click', function() {
|
|
self.switchMode('file');
|
|
});
|
|
|
|
document.getElementById('upload-mode-subscribe').addEventListener('click', function() {
|
|
self.switchMode('subscribe');
|
|
});
|
|
|
|
uploadZone.addEventListener('click', function() {
|
|
if (!self.isProcessing && self.currentMode === 'file') {
|
|
fileInput.click();
|
|
}
|
|
});
|
|
|
|
fileInput.addEventListener('change', function(e) {
|
|
if (e.target.files.length > 0) {
|
|
self.handleFileSelect(e.target.files[0]);
|
|
}
|
|
});
|
|
|
|
uploadZone.addEventListener('dragover', function(e) {
|
|
e.preventDefault();
|
|
if (!self.isProcessing && self.currentMode === 'file') {
|
|
uploadZone.classList.add('dragover');
|
|
}
|
|
});
|
|
|
|
uploadZone.addEventListener('dragleave', function(e) {
|
|
e.preventDefault();
|
|
uploadZone.classList.remove('dragover');
|
|
});
|
|
|
|
uploadZone.addEventListener('drop', function(e) {
|
|
e.preventDefault();
|
|
uploadZone.classList.remove('dragover');
|
|
|
|
if (!self.isProcessing && self.currentMode === 'file' && e.dataTransfer.files.length > 0) {
|
|
self.handleFileSelect(e.dataTransfer.files[0]);
|
|
}
|
|
});
|
|
|
|
var subscribeUrlInput = document.getElementById('subscribe-url-input');
|
|
var filenameInput = document.getElementById('config-filename-input');
|
|
var subscribeUaSelect = document.getElementById('subscribe-ua-input');
|
|
var subscribeUaCustom = document.getElementById('subscribe-ua-custom');
|
|
var subConvertEnable = document.getElementById('sub-convert-enable');
|
|
var subConvertOptions = document.getElementById('sub-convert-options');
|
|
var convertAddressSelect = document.getElementById('convert-address-input');
|
|
var convertAddressCustom = document.getElementById('convert-address-custom');
|
|
var templateSelect = document.getElementById('template-select');
|
|
var customTemplateGroup = document.getElementById('custom-template-group');
|
|
var advancedOptionsEnable = document.getElementById('advanced-options-enable');
|
|
var advancedOptionsContainer = document.getElementById('advanced-options-container');
|
|
|
|
subscribeUrlInput.addEventListener('input', function() {
|
|
self.updateSubmitButton();
|
|
self.autoFillConfigName();
|
|
});
|
|
|
|
filenameInput.addEventListener('input', this.updateSubmitButton.bind(this));
|
|
|
|
subscribeUaSelect.addEventListener('change', function() {
|
|
if (this.value === 'custom') {
|
|
subscribeUaCustom.style.display = 'block';
|
|
} else {
|
|
subscribeUaCustom.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
advancedOptionsEnable.addEventListener('change', function() {
|
|
if (this.checked) {
|
|
advancedOptionsContainer.style.display = 'block';
|
|
} else {
|
|
advancedOptionsContainer.style.display = 'none';
|
|
self.resetAdvancedOptions();
|
|
}
|
|
self.updateSubmitButton();
|
|
});
|
|
|
|
subConvertEnable.addEventListener('change', function() {
|
|
if (this.checked) {
|
|
subConvertOptions.style.display = 'block';
|
|
} else {
|
|
subConvertOptions.style.display = 'none';
|
|
}
|
|
self.updateSubmitButton();
|
|
});
|
|
|
|
convertAddressSelect.addEventListener('change', function() {
|
|
if (this.value === 'custom') {
|
|
convertAddressCustom.style.display = 'block';
|
|
} else {
|
|
convertAddressCustom.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
templateSelect.addEventListener('change', function() {
|
|
if (this.value === '0') {
|
|
customTemplateGroup.style.display = 'block';
|
|
} else {
|
|
customTemplateGroup.style.display = 'none';
|
|
}
|
|
});
|
|
|
|
document.getElementById('config-upload-submit').addEventListener('click', function() {
|
|
if (self.currentMode === 'file') {
|
|
self.uploadFile();
|
|
} else {
|
|
self.processSubscription();
|
|
}
|
|
});
|
|
|
|
document.getElementById('config-upload-cancel').addEventListener('click', function() {
|
|
if (!self.isProcessing) {
|
|
self.hide();
|
|
}
|
|
});
|
|
|
|
document.getElementById('config-upload-close').addEventListener('click', function() {
|
|
if (!self.isProcessing) {
|
|
self.hide();
|
|
}
|
|
});
|
|
|
|
this.overlay.addEventListener('click', function(e) {
|
|
if (e.target === self.overlay && !self.isProcessing) {
|
|
self.hide();
|
|
}
|
|
});
|
|
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Escape' && !self.isProcessing && self.overlay.classList.contains('show')) {
|
|
self.hide();
|
|
}
|
|
});
|
|
},
|
|
|
|
show: function() {
|
|
this.overlay.classList.add('show');
|
|
this.reset();
|
|
},
|
|
|
|
hide: function() {
|
|
this.overlay.classList.remove('show');
|
|
this.reset();
|
|
},
|
|
|
|
resetAdvancedOptions: function() {
|
|
document.getElementById('sub-convert-enable').checked = false;
|
|
document.getElementById('sub-convert-options').style.display = 'none';
|
|
document.getElementById('convert-address-input').value = 'https://api.dler.io/sub';
|
|
document.getElementById('convert-address-custom').style.display = 'none';
|
|
document.getElementById('convert-address-custom').value = '';
|
|
|
|
var templateSelect = document.getElementById('template-select');
|
|
if (templateSelect && templateSelect.options.length > 1) {
|
|
templateSelect.selectedIndex = 0;
|
|
}
|
|
|
|
document.getElementById('custom-template-group').style.display = 'none';
|
|
document.getElementById('custom-template-input').value = '';
|
|
|
|
document.getElementById('emoji-enable').checked = false;
|
|
document.getElementById('udp-enable').checked = false;
|
|
document.getElementById('skip-cert-verify').checked = false;
|
|
document.getElementById('sort-enable').checked = false;
|
|
document.getElementById('node-type-enable').checked = false;
|
|
document.getElementById('rule-provider-enable').checked = false;
|
|
document.getElementById('custom-params-input').value = '';
|
|
|
|
document.getElementById('keyword-input').value = '';
|
|
document.getElementById('exclude-keyword-input').value = '';
|
|
document.getElementById('exclude-expire').checked = false;
|
|
document.getElementById('exclude-traffic').checked = false;
|
|
document.getElementById('exclude-tg').checked = false;
|
|
document.getElementById('exclude-website').checked = false;
|
|
},
|
|
|
|
reset: function() {
|
|
this.selectedFile = null;
|
|
this.isProcessing = false;
|
|
this.currentMode = 'file';
|
|
|
|
this.switchMode('file');
|
|
document.getElementById('config-filename-input').value = '';
|
|
document.getElementById('subscribe-url-input').value = '';
|
|
document.getElementById('subscribe-ua-input').value = 'clash.meta';
|
|
document.getElementById('subscribe-ua-custom').style.display = 'none';
|
|
|
|
document.getElementById('advanced-options-enable').checked = false;
|
|
document.getElementById('advanced-options-container').style.display = 'none';
|
|
this.resetAdvancedOptions();
|
|
|
|
var templateSelect = document.getElementById('template-select');
|
|
if (templateSelect && templateSelect.options.length > 1) {
|
|
templateSelect.selectedIndex = 0;
|
|
}
|
|
|
|
document.getElementById('upload-progress').style.display = 'none';
|
|
document.getElementById('config-upload-status-text').textContent = '<%:Ready to add config%>';
|
|
this.updateSubmitButton();
|
|
},
|
|
|
|
switchMode: function(mode) {
|
|
this.currentMode = mode;
|
|
|
|
var modeFileTab = document.getElementById('upload-mode-file');
|
|
var modeSubscribeTab = document.getElementById('upload-mode-subscribe');
|
|
var modeFileContent = document.getElementById('mode-file-content');
|
|
var modeSubscribeContent = document.getElementById('mode-subscribe-content');
|
|
var statusText = document.getElementById('config-upload-status-text');
|
|
var uploadZone = document.getElementById('upload-zone');
|
|
|
|
if (mode === 'file') {
|
|
modeFileTab.classList.add('active');
|
|
modeSubscribeTab.classList.remove('active');
|
|
modeFileContent.style.display = 'block';
|
|
modeSubscribeContent.style.display = 'none';
|
|
statusText.textContent = '<%:Ready to upload file%>';
|
|
} else {
|
|
modeFileTab.classList.remove('active');
|
|
modeSubscribeTab.classList.add('active');
|
|
modeFileContent.style.display = 'none';
|
|
modeSubscribeContent.style.display = 'block';
|
|
statusText.textContent = '<%:Ready to add subscription%>';
|
|
}
|
|
|
|
this.selectedFile = null;
|
|
uploadZone.classList.remove('has-file');
|
|
uploadZone.querySelector('.upload-primary').textContent = '<%:Click to select file or drag and drop%>';
|
|
uploadZone.querySelector('.upload-secondary').textContent = '<%:Support YAML file, max size 10MB%>';
|
|
|
|
this.updateSubmitButton();
|
|
},
|
|
|
|
handleFileSelect: function(file) {
|
|
this.selectedFile = file;
|
|
var uploadZone = document.getElementById('upload-zone');
|
|
var filenameInput = document.getElementById('config-filename-input');
|
|
var statusText = document.getElementById('config-upload-status-text');
|
|
|
|
if (!file) {
|
|
uploadZone.classList.remove('has-file');
|
|
this.updateSubmitButton();
|
|
statusText.textContent = '<%:Ready to upload file%>';
|
|
return;
|
|
}
|
|
|
|
if (!file.name.match(/\.(yaml|yml)$/i)) {
|
|
alert('<%:Please select a YAML file%>');
|
|
return;
|
|
}
|
|
|
|
if (file.size > 10 * 1024 * 1024) {
|
|
alert('<%:File size exceeds 10MB limit%>');
|
|
return;
|
|
}
|
|
|
|
uploadZone.classList.add('has-file');
|
|
uploadZone.querySelector('.upload-primary').textContent = '<%:File selected:%> ' + file.name;
|
|
uploadZone.querySelector('.upload-secondary').textContent = '<%:Size:%> ' + this.formatFileSize(file.size);
|
|
|
|
var defaultName = file.name.replace(/\.(yaml|yml)$/i, '');
|
|
filenameInput.value = defaultName;
|
|
|
|
this.updateSubmitButton();
|
|
statusText.textContent = '<%:File ready to upload%>';
|
|
},
|
|
|
|
formatFileSize: function(bytes) {
|
|
if (bytes === 0) return '0 B';
|
|
var k = 1024;
|
|
var sizes = ['B', 'KB', 'MB', 'GB'];
|
|
var i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
|
|
},
|
|
|
|
updateSubmitButton: function() {
|
|
var filename = document.getElementById('config-filename-input').value.trim();
|
|
var submitBtn = document.getElementById('config-upload-submit');
|
|
var isValid = false;
|
|
|
|
if (this.currentMode === 'file') {
|
|
isValid = this.selectedFile && filename;
|
|
} else if (this.currentMode === 'subscribe') {
|
|
var url = document.getElementById('subscribe-url-input').value.trim();
|
|
var advancedEnabled = document.getElementById('advanced-options-enable').checked;
|
|
var subConvert = advancedEnabled && document.getElementById('sub-convert-enable').checked;
|
|
|
|
if (url && filename) {
|
|
if (subConvert) {
|
|
if (url.indexOf('\n') !== -1 || url.indexOf('|') !== -1) {
|
|
var links = url.indexOf('\n') !== -1 ? url.split('\n') : url.split('|');
|
|
for (var i = 0; i < links.length; i++) {
|
|
var link = links[i].trim();
|
|
if (link && (/^https?:\/\//.test(link) || /^[a-zA-Z]+:\/\//.test(link))) {
|
|
isValid = true;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
isValid = /^https?:\/\//.test(url) || /^[a-zA-Z]+:\/\//.test(url);
|
|
}
|
|
} else {
|
|
isValid = /^https?:\/\//.test(url) && url.indexOf('\n') === -1 && url.indexOf('|') === -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
submitBtn.disabled = !isValid || this.isProcessing;
|
|
},
|
|
|
|
uploadFile: function() {
|
|
if (!this.selectedFile || this.isProcessing) return;
|
|
|
|
var filename = document.getElementById('config-filename-input').value.trim();
|
|
if (!filename) {
|
|
alert('<%:Please enter a filename%>');
|
|
return;
|
|
}
|
|
|
|
if (!/^[a-zA-Z0-9_\-\s\u4e00-\u9fa5\.]+$/.test(filename)) {
|
|
alert('<%:Filename contains invalid characters%>');
|
|
return;
|
|
}
|
|
|
|
var self = this;
|
|
this.isProcessing = true;
|
|
|
|
var submitBtn = document.getElementById('config-upload-submit');
|
|
var cancelBtn = document.getElementById('config-upload-cancel');
|
|
var statusText = document.getElementById('config-upload-status-text');
|
|
var progressContainer = document.getElementById('upload-progress');
|
|
var progressFill = document.getElementById('upload-progress-fill');
|
|
var progressText = document.getElementById('upload-progress-text');
|
|
|
|
submitBtn.disabled = true;
|
|
cancelBtn.disabled = true;
|
|
statusText.textContent = '<%:Uploading...%>';
|
|
progressContainer.style.display = 'block';
|
|
|
|
var progress = 0;
|
|
var progressInterval = setInterval(function() {
|
|
if (progress < 90) {
|
|
progress += Math.random() * 15;
|
|
progressFill.style.width = Math.min(progress, 90) + '%';
|
|
progressText.textContent = '<%:Uploading...%> ' + Math.floor(Math.min(progress, 90)) + '%';
|
|
}
|
|
}, 100);
|
|
|
|
var reader = new FileReader();
|
|
reader.onload = function(e) {
|
|
var fileContent = e.target.result;
|
|
|
|
var formData = new FormData();
|
|
formData.append('config_file', fileContent);
|
|
formData.append('filename', filename);
|
|
|
|
fetch('<%=luci.dispatcher.build_url("admin", "services", "openclash", "upload_config")%>', {
|
|
method: 'POST',
|
|
body: formData
|
|
})
|
|
.then(function(response) {
|
|
clearInterval(progressInterval);
|
|
|
|
if (!response.ok) {
|
|
throw new Error('HTTP error! status: ' + response.status);
|
|
}
|
|
return response.json();
|
|
})
|
|
.then(function(data) {
|
|
progressFill.style.width = '100%';
|
|
progressText.textContent = '<%:Upload completed%> 100%';
|
|
|
|
if (data.status === 'success') {
|
|
statusText.textContent = '<%:Upload successful%>';
|
|
|
|
setTimeout(function() {
|
|
self.hide();
|
|
if (typeof ConfigFileManager !== 'undefined' && ConfigFileManager.refreshConfigList) {
|
|
ConfigFileManager.refreshConfigList();
|
|
}
|
|
}, 2000);
|
|
} else {
|
|
throw new Error(data.message || '<%:Upload failed%>');
|
|
}
|
|
})
|
|
.catch(function(error) {
|
|
self.handleError('<%:Upload failed:%> ' + error.message);
|
|
});
|
|
};
|
|
|
|
reader.onerror = function() {
|
|
clearInterval(progressInterval);
|
|
self.handleError('<%:Failed to read file%>');
|
|
};
|
|
|
|
reader.readAsText(this.selectedFile, 'UTF-8');
|
|
},
|
|
|
|
processSubscription: function() {
|
|
var url = document.getElementById('subscribe-url-input').value.trim();
|
|
var filename = document.getElementById('config-filename-input').value.trim();
|
|
var userAgent = document.getElementById('subscribe-ua-input').value;
|
|
var subscribeUaCustom = document.getElementById('subscribe-ua-custom');
|
|
|
|
var advancedEnabled = document.getElementById('advanced-options-enable').checked;
|
|
var subConvert = advancedEnabled && document.getElementById('sub-convert-enable').checked;
|
|
|
|
var convertAddress = 'https://api.dler.io/sub';
|
|
var template = '';
|
|
var emoji = false;
|
|
var udp = false;
|
|
var skipCert = false;
|
|
var sort = false;
|
|
var nodeType = false;
|
|
var ruleProvider = false;
|
|
var customParams = '';
|
|
var keywords = '';
|
|
var excludeKeywords = '';
|
|
var excludeDefaults = [];
|
|
|
|
if (advancedEnabled) {
|
|
convertAddress = document.getElementById('convert-address-input').value;
|
|
var convertAddressCustom = document.getElementById('convert-address-custom').value;
|
|
template = document.getElementById('template-select').value;
|
|
var customTemplate = document.getElementById('custom-template-input').value;
|
|
emoji = document.getElementById('emoji-enable').checked;
|
|
udp = document.getElementById('udp-enable').checked;
|
|
skipCert = document.getElementById('skip-cert-verify').checked;
|
|
sort = document.getElementById('sort-enable').checked;
|
|
nodeType = document.getElementById('node-type-enable').checked;
|
|
ruleProvider = document.getElementById('rule-provider-enable').checked;
|
|
customParams = document.getElementById('custom-params-input').value;
|
|
keywords = document.getElementById('keyword-input').value;
|
|
excludeKeywords = document.getElementById('exclude-keyword-input').value;
|
|
|
|
if (document.getElementById('exclude-expire').checked) excludeDefaults.push('过期时间');
|
|
if (document.getElementById('exclude-traffic').checked) excludeDefaults.push('剩余流量');
|
|
if (document.getElementById('exclude-tg').checked) excludeDefaults.push('TG群');
|
|
if (document.getElementById('exclude-website').checked) excludeDefaults.push('官网');
|
|
|
|
if (convertAddress === 'custom') {
|
|
convertAddress = convertAddressCustom.trim() || 'https://api.dler.io/sub';
|
|
}
|
|
|
|
if (template === '0') {
|
|
template = customTemplate.trim();
|
|
}
|
|
}
|
|
|
|
if (userAgent === 'custom') {
|
|
userAgent = subscribeUaCustom.value.trim() || 'clash.meta';
|
|
}
|
|
|
|
if (!url || !filename) {
|
|
alert('<%:Please enter subscription URL and config name%>');
|
|
return;
|
|
}
|
|
|
|
var isValidFormat = false;
|
|
|
|
if (subConvert) {
|
|
if (url.indexOf('\n') !== -1 || url.indexOf('|') !== -1) {
|
|
var links = [];
|
|
if (url.indexOf('\n') !== -1) {
|
|
links = url.split('\n');
|
|
} else {
|
|
links = url.split('|');
|
|
}
|
|
|
|
for (var i = 0; i < links.length; i++) {
|
|
var link = links[i].trim();
|
|
if (link && (/^https?:\/\//.test(link) || /^[a-zA-Z]+:\/\//.test(link))) {
|
|
isValidFormat = true;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
if (/^https?:\/\//.test(url) || /^[a-zA-Z]+:\/\//.test(url)) {
|
|
isValidFormat = true;
|
|
}
|
|
}
|
|
} else {
|
|
if (/^https?:\/\//.test(url) && url.indexOf('\n') === -1 && url.indexOf('|') === -1) {
|
|
isValidFormat = true;
|
|
}
|
|
}
|
|
|
|
if (!isValidFormat) {
|
|
var errorMsg = subConvert ?
|
|
'<%:Invalid subscription URL format. Support HTTP/HTTPS subscription URLs or protocol links, can be separated by newlines or |%>' :
|
|
'<%:Invalid subscription URL format. Only single HTTP/HTTPS subscription URL is supported when subscription conversion is disabled%>';
|
|
alert(errorMsg);
|
|
return;
|
|
}
|
|
|
|
var requestData = {
|
|
name: filename,
|
|
address: url,
|
|
sub_ua: userAgent,
|
|
sub_convert: subConvert ? '1' : '0',
|
|
convert_address: convertAddress,
|
|
template: template,
|
|
emoji: emoji ? 'true' : 'false',
|
|
udp: udp ? 'true' : 'false',
|
|
skip_cert_verify: skipCert ? 'true' : 'false',
|
|
sort: sort ? 'true' : 'false',
|
|
node_type: nodeType ? 'true' : 'false',
|
|
rule_provider: ruleProvider ? 'true' : 'false',
|
|
custom_params: customParams,
|
|
keyword: keywords,
|
|
ex_keyword: excludeKeywords,
|
|
de_ex_keyword: excludeDefaults.join('\n')
|
|
};
|
|
|
|
var self = this;
|
|
this.isProcessing = true;
|
|
|
|
var submitBtn = document.getElementById('config-upload-submit');
|
|
var cancelBtn = document.getElementById('config-upload-cancel');
|
|
var statusText = document.getElementById('config-upload-status-text');
|
|
var progressContainer = document.getElementById('upload-progress');
|
|
var progressFill = document.getElementById('upload-progress-fill');
|
|
var progressText = document.getElementById('upload-progress-text');
|
|
|
|
submitBtn.disabled = true;
|
|
cancelBtn.disabled = true;
|
|
statusText.textContent = '<%:Adding subscription...%>';
|
|
progressContainer.style.display = 'block';
|
|
|
|
var progress = 0;
|
|
var progressInterval = setInterval(function() {
|
|
if (progress < 90) {
|
|
progress += Math.random() * 15;
|
|
progressFill.style.width = Math.min(progress, 90) + '%';
|
|
progressText.textContent = '<%:Processing...%> ' + Math.floor(Math.min(progress, 90)) + '%';
|
|
}
|
|
}, 100);
|
|
|
|
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "add_subscription")%>', {
|
|
name: filename,
|
|
address: url,
|
|
sub_ua: userAgent,
|
|
sub_convert: subConvert ? '1' : '0',
|
|
convert_address: convertAddress,
|
|
template: template,
|
|
emoji: emoji ? 'true' : 'false',
|
|
udp: udp ? 'true' : 'false',
|
|
skip_cert_verify: skipCert ? 'true' : 'false',
|
|
sort: sort ? 'true' : 'false',
|
|
node_type: nodeType ? 'true' : 'false',
|
|
rule_provider: ruleProvider ? 'true' : 'false',
|
|
custom_params: customParams,
|
|
keyword: keywords,
|
|
ex_keyword: excludeKeywords,
|
|
de_ex_keyword: excludeDefaults.join('\n')
|
|
}, function(x, data) {
|
|
if (x && x.status == 200) {
|
|
XHR.get('<%=luci.dispatcher.build_url("admin", "services", "openclash", "update_config")%>', {
|
|
filename: filename
|
|
}, function(x2, data2) {
|
|
clearInterval(progressInterval);
|
|
|
|
if (x2 && x2.status == 200) {
|
|
progressFill.style.width = '100%';
|
|
progressText.textContent = '<%:Subscription added successfully%> 100%';
|
|
statusText.textContent = '<%:Subscription added successfully%>';
|
|
|
|
setTimeout(function() {
|
|
self.hide();
|
|
if (typeof ConfigFileManager !== 'undefined' && ConfigFileManager.refreshConfigList) {
|
|
ConfigFileManager.refreshConfigList();
|
|
}
|
|
}, 2000);
|
|
} else {
|
|
self.handleError('<%:Failed to download subscription config%>');
|
|
}
|
|
});
|
|
} else {
|
|
clearInterval(progressInterval);
|
|
self.handleError('<%:Failed to add subscription%>');
|
|
}
|
|
});
|
|
},
|
|
|
|
autoFillConfigName: function() {
|
|
var url = document.getElementById('subscribe-url-input').value.trim();
|
|
var filenameInput = document.getElementById('config-filename-input');
|
|
|
|
if (!filenameInput.value.trim() && url) {
|
|
try {
|
|
var urlObj = new URL(url);
|
|
var hostname = urlObj.hostname;
|
|
|
|
var configName = hostname
|
|
.replace(/^(www\.|api\.|sub\.|subscribe\.)/, '')
|
|
.replace(/\.(com|net|org|cn|io|me|cc|xyz|top)$/, '')
|
|
.replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, '_')
|
|
.replace(/_{2,}/g, '_')
|
|
.replace(/^_|_$/g, '');
|
|
|
|
if (!configName || configName.length < 2) {
|
|
configName = 'subscription_' + Date.now().toString().slice(-6);
|
|
}
|
|
|
|
if (configName.length > 30) {
|
|
configName = configName.substring(0, 30);
|
|
}
|
|
|
|
filenameInput.value = configName;
|
|
this.updateSubmitButton();
|
|
} catch (e) {
|
|
}
|
|
}
|
|
},
|
|
|
|
handleError: function(message) {
|
|
var statusText = document.getElementById('config-upload-status-text');
|
|
var progressText = document.getElementById('upload-progress-text');
|
|
var progressFill = document.getElementById('upload-progress-fill');
|
|
var submitBtn = document.getElementById('config-upload-submit');
|
|
var cancelBtn = document.getElementById('config-upload-cancel');
|
|
var progressContainer = document.getElementById('upload-progress');
|
|
|
|
statusText.textContent = '<%:Process failed%>';
|
|
progressText.textContent = '<%:Process failed%>';
|
|
progressFill.style.width = '0%';
|
|
|
|
alert(message);
|
|
|
|
this.isProcessing = false;
|
|
submitBtn.disabled = false;
|
|
cancelBtn.disabled = false;
|
|
progressContainer.style.display = 'none';
|
|
}
|
|
};
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
ConfigUploader.init();
|
|
});
|
|
</script> |