| @@ -822,8 +822,16 @@ | ||
| 822 | 822 | var stateEl = document.getElementById('mxchat-pinecone-docs-state'); |
| 823 | 823 | var state = {}; |
| 824 | 824 | try { state = stateEl ? JSON.parse(stateEl.textContent || '{}') : {}; } catch (e) { state = {}; } |
| 825 | 825 | var verifiedHost = String(state.verified_host || '').toLowerCase(); |
| 826 | + // 3e83e4: the LIVE chatbot's index type + host as saved, the migration | |
| 827 | + // state as last known, and the index name the page loaded with (so a | |
| 828 | + // suggested "-docs" name can be undone by switching back to Vector). | |
| 829 | + var liveType = state.index_type === 'document' ? 'document' : 'vector'; | |
| 830 | + var storedHost = String(state.stored_host || '').toLowerCase(); | |
| 831 | + var storedIndex = String(state.stored_index || ''); | |
| 832 | + var originalIndexName = $.trim($('#mxchat_pinecone_index').val() || ''); | |
| 833 | + var migrationState = (state.migration && state.migration.status) ? state.migration : null; | |
| 826 | 834 | var ajaxUrl = (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.ajax_url) ? mxchatAdmin.ajax_url : ajaxurl; |
| 827 | 835 | var nonce = (typeof mxchatAdmin !== 'undefined' && mxchatAdmin.settings_nonce) ? mxchatAdmin.settings_nonce : |
| 828 | 836 | ((typeof mxchatPromptsAdmin !== 'undefined' && mxchatPromptsAdmin.prompts_setting_nonce) ? mxchatPromptsAdmin.prompts_setting_nonce : ''); |
| 829 | 837 | |
| @@ -838,23 +846,80 @@ | ||
| 838 | 846 | function currentType() { return $type.filter(':checked').val() || 'vector'; } |
| 839 | 847 | function hostValue() { |
| 840 | 848 | return $.trim($('#mxchat_pinecone_host').val() || '').replace(/^https?:\/\//i, '').replace(/\/+$/, '').toLowerCase(); |
| 841 | 849 | } |
| 850 | + function fmt(n) { n = parseInt(n, 10) || 0; return n.toLocaleString(); } | |
| 851 | + // Does saving Document right now point the chatbot at an index whose copy is unfinished? | |
| 852 | + function switchBlockedBy() { | |
| 853 | + if (currentType() !== 'document' || liveType === 'document' || !migrationState) { return null; } | |
| 854 | + if (migrationState.status !== 'running') { return null; } | |
| 855 | + if (String(migrationState.target_host || '').toLowerCase() !== hostValue()) { return null; } | |
| 856 | + return migrationState; | |
| 857 | + } | |
| 842 | 858 | function refreshGate() { |
| 843 | 859 | var isDoc = currentType() === 'document'; |
| 844 | 860 | $docs.toggle(isDoc); |
| 845 | 861 | var blocked = isDoc && (hostValue() === '' || hostValue() !== verifiedHost); |
| 862 | + $('#mxchat-pinecone-docs-gate').toggle(blocked); | |
| 863 | + var running = blocked ? null : switchBlockedBy(); | |
| 864 | + var $switchGate = $('#mxchat-pinecone-docs-switch-gate'); | |
| 865 | + if (running) { | |
| 866 | + $('#mxchat-pinecone-docs-switch-gate-text').text( | |
| 867 | + 'The copy into ' + (running.target_name || running.target_host) + ' is still running (' + fmt(running.copied) + ' of ' + fmt(running.total) + | |
| 868 | + ' records). Let it finish before switching the chatbot to the document index.' | |
| 869 | + ); | |
| 870 | + $switchGate.show(); | |
| 871 | + blocked = !$('#mxchat_pinecone_docs_switch_anyway').is(':checked'); | |
| 872 | + } else { | |
| 873 | + $switchGate.hide(); | |
| 874 | + $('#mxchat_pinecone_docs_switch_anyway').prop('checked', false); | |
| 875 | + } | |
| 846 | 876 | $submit.prop('disabled', blocked); |
| 847 | - $('#mxchat-pinecone-docs-gate').toggle(blocked); | |
| 848 | 877 | } |
| 849 | - $type.off('change.pineconeDocs').on('change.pineconeDocs', refreshGate); | |
| 878 | + $('#mxchat_pinecone_docs_switch_anyway').off('change.pineconeDocs').on('change.pineconeDocs', refreshGate); | |
| 850 | 879 | $('#mxchat_pinecone_host').off('input.pineconeDocs change.pineconeDocs').on('input.pineconeDocs change.pineconeDocs', refreshGate); |
| 851 | - refreshGate(); | |
| 852 | 880 | |
| 853 | 881 | function post(data) { |
| 854 | 882 | data.nonce = nonce; |
| 855 | 883 | return $.post(ajaxUrl, data); |
| 856 | 884 | } |
| 885 | + | |
| 886 | + // ---- Index type change: a document index needs its own name, so when | |
| 887 | + // the field still holds the vector index's name (or is empty) suggest a | |
| 888 | + // free "<name>-docs" from the project; Vector restores the original. | |
| 889 | + var suggestedName = ''; | |
| 890 | + function nameIsOurs() { | |
| 891 | + var name = $.trim($('#mxchat_pinecone_index').val() || ''); | |
| 892 | + return name === '' || name === storedIndex || name === originalIndexName || name === suggestedName; | |
| 893 | + } | |
| 894 | + // When the host in the field is a checked document index, the name is | |
| 895 | + // that index's own; otherwise a free "<name>-docs" for the create button. | |
| 896 | + function suggestDocsName() { | |
| 897 | + var name = $.trim($('#mxchat_pinecone_index').val() || ''); | |
| 898 | + var apiKey = $('#mxchat_pinecone_api_key').val(); | |
| 899 | + if (liveType === 'document' || !apiKey || currentType() !== 'document') { return; } | |
| 900 | + if (!nameIsOurs()) { return; } // the owner typed their own | |
| 901 | + var host = (hostValue() !== '' && hostValue() === verifiedHost) ? hostValue() : ''; | |
| 902 | + post({ action: 'mxchat_pinecone_docs_suggest_name', base: name || storedIndex, host: host, api_key: apiKey }).done(function (response) { | |
| 903 | + if (response && response.success && response.data && response.data.name && currentType() === 'document' && nameIsOurs()) { | |
| 904 | + suggestedName = response.data.name; | |
| 905 | + $('#mxchat_pinecone_index').val(suggestedName); | |
| 906 | + } | |
| 907 | + }); | |
| 908 | + } | |
| 909 | + $type.off('change.pineconeDocs').on('change.pineconeDocs', function () { | |
| 910 | + if (currentType() === 'document') { | |
| 911 | + suggestDocsName(); | |
| 912 | + } else if (suggestedName !== '' && $.trim($('#mxchat_pinecone_index').val() || '') === suggestedName) { | |
| 913 | + $('#mxchat_pinecone_index').val(originalIndexName); | |
| 914 | + } | |
| 915 | + refreshGate(); | |
| 916 | + }); | |
| 917 | + // Pasting the checked document host resolves the name to that index's own. | |
| 918 | + $('#mxchat_pinecone_host').on('change.pineconeDocsName', function () { | |
| 919 | + if (currentType() === 'document' && hostValue() !== '' && hostValue() === verifiedHost) { suggestDocsName(); } | |
| 920 | + }); | |
| 921 | + refreshGate(); | |
| 857 | 922 | function failMessage(response, fallback) { |
| 858 | 923 | if (response && response.data) { |
| 859 | 924 | if (typeof response.data === 'string') { return response.data; } |
| 860 | 925 | if (response.data.message) { return response.data.message; } |
| @@ -890,8 +955,13 @@ | ||
| 890 | 955 | setStatus(true, 'Document index checked: ' + response.data.host + ' (' + response.data.dimension + ' dimensions).'); |
| 891 | 956 | notice(response.data.ready ? 'success' : 'info', response.data.message); |
| 892 | 957 | refreshGate(); |
| 893 | 958 | } else { |
| 959 | + // A taken name comes back with a free suggestion — fill it in so the next click works. | |
| 960 | + if (response && response.data && response.data.suggested) { | |
| 961 | + suggestedName = response.data.suggested; | |
| 962 | + $('#mxchat_pinecone_index').val(response.data.suggested); | |
| 963 | + } | |
| 894 | 964 | notice('error', failMessage(response, 'Could not create the index.')); |
| 895 | 965 | } |
| 896 | 966 | }).fail(function () { |
| 897 | 967 | notice('error', 'Could not reach the server to create the index.'); |
| @@ -934,45 +1004,162 @@ | ||
| 934 | 1004 | $btn.prop('disabled', false).text('Check index'); |
| 935 | 1005 | }); |
| 936 | 1006 | }); |
| 937 | 1007 | |
| 938 | - // ---- Migrate (copy, batch by batch, resumable) | |
| 1008 | + // ---- Migrate (copy, page by page, resumable). 3e83e4: the copy runs | |
| 1009 | + // against the document host in the form while the chatbot keeps | |
| 1010 | + // answering from the vector index; a dropped request is retried with | |
| 1011 | + // backoff, and a second click continues from the recorded position. | |
| 939 | 1012 | var $migrateBtn = $('#mxchat-pinecone-docs-migrate'); |
| 1013 | + // e50d2e: "Copy every namespace" — offered once the start response | |
| 1014 | + // reports records in other namespaces; the copy then walks every | |
| 1015 | + // namespace of the source in one run, each into its own namespace on | |
| 1016 | + // the document side. Disabled while a copy runs in this tab (the | |
| 1017 | + // server extends a running copy on the next Migrate click, never | |
| 1018 | + // underneath a step that is in flight). | |
| 1019 | + var $allNs = $('#mxchat_pinecone_docs_all_namespaces'); | |
| 1020 | + var $allNsRow = $('#mxchat-pinecone-docs-all-namespaces'); | |
| 1021 | + var stepRetries = 0; | |
| 1022 | + var STEP_RETRY_DELAYS = [1000, 2000, 4000, 8000, 16000]; | |
| 1023 | + function remainingOf(st) { | |
| 1024 | + var total = parseInt(st.total, 10) || 0, copied = parseInt(st.copied, 10) || 0; | |
| 1025 | + return Math.max(0, total - copied); | |
| 1026 | + } | |
| 1027 | + function otherNamespacesText(st) { | |
| 1028 | + var other = st.other_namespaces || {}; | |
| 1029 | + var parts = []; | |
| 1030 | + $.each(other, function (name, count) { parts.push(name + ' (' + fmt(count) + ')'); }); | |
| 1031 | + return parts.join(', '); | |
| 1032 | + } | |
| 1033 | + function otherNamespacesSummary(st) { | |
| 1034 | + var records = 0, n = 0; | |
| 1035 | + $.each(st.other_namespaces || {}, function (name, count) { records += parseInt(count, 10) || 0; n++; }); | |
| 1036 | + return n ? { records: records, count: n } : null; | |
| 1037 | + } | |
| 1038 | + function namespaceEntries(st) { | |
| 1039 | + var out = []; | |
| 1040 | + $.each(st.per_namespace || {}, function (key, e) { out.push($.extend({ key: key }, e)); }); | |
| 1041 | + return out; | |
| 1042 | + } | |
| 1043 | + function isMulti(st) { return !!st.all_namespaces || namespaceEntries(st).length > 1; } | |
| 1044 | + function namespaceLine(e, sep) { | |
| 1045 | + return e.key + ' ' + fmt(e.copied) + ' of ' + fmt(e.total) + (e.target_count ? (sep + 'the document index reports ' + fmt(e.target_count) + ')') : ''); | |
| 1046 | + } | |
| 940 | 1047 | function renderMigration(st) { |
| 1048 | + migrationState = (st && st.status) ? st : null; | |
| 941 | 1049 | var $progress = $('#mxchat-pinecone-docs-migrate-progress'); |
| 942 | 1050 | if (!st || !st.status) { |
| 943 | 1051 | $progress.hide(); |
| 944 | 1052 | $('#mxchat-pinecone-docs-migrate-counts').text(''); |
| 945 | 1053 | $('#mxchat-pinecone-docs-delete-old').hide(); |
| 1054 | + $allNsRow.hide(); | |
| 1055 | + refreshGate(); | |
| 946 | 1056 | return; |
| 947 | 1057 | } |
| 948 | 1058 | var total = parseInt(st.total, 10) || 0; |
| 949 | 1059 | var copied = parseInt(st.copied, 10) || 0; |
| 950 | 1060 | var pct = total > 0 ? Math.min(100, Math.round(copied * 100 / total)) : (st.status === 'done' ? 100 : 0); |
| 951 | - var suffix = st.status === 'done' ? ' — done' : (st.status === 'source_deleted' ? ' — old index deleted' : (st.status === 'running' ? ' — copying…' : '')); | |
| 1061 | + var multi = isMulti(st); | |
| 1062 | + var entries = namespaceEntries(st); | |
| 1063 | + var others = otherNamespacesSummary(st); | |
| 1064 | + var label, suffix; | |
| 1065 | + if (st.status === 'done') { | |
| 1066 | + suffix = ' — done' + (st.target_count ? ('; the document index reports ' + fmt(st.target_count) + ' records') : ''); | |
| 1067 | + } else if (st.status === 'source_deleted') { | |
| 1068 | + suffix = ' — old index deleted'; | |
| 1069 | + } else if (st.status === 'running') { | |
| 1070 | + suffix = st.error ? (' — stopped: ' + st.error) : (' — copying, ' + fmt(remainingOf(st)) + ' remaining…'); | |
| 1071 | + } else { | |
| 1072 | + suffix = ''; | |
| 1073 | + } | |
| 1074 | + if (multi && st.status === 'running') { | |
| 1075 | + var idx = 0, cur = null; | |
| 1076 | + $.each(entries, function (i, e) { if (e.key === (st.current === '' ? '__default__' : st.current)) { idx = i + 1; cur = e; } }); | |
| 1077 | + label = (cur ? ('Namespace ' + idx + ' of ' + entries.length + ' (' + cur.key + ') — ' + fmt(cur.copied) + ' of ' + fmt(cur.total) + ' records copied; ') : '') + | |
| 1078 | + fmt(copied) + ' of ' + fmt(total) + ' overall (' + pct + '%)' + (st.error ? (' — stopped: ' + st.error) : (', ' + fmt(remainingOf(st)) + ' remaining…')); | |
| 1079 | + } else if (multi) { | |
| 1080 | + label = fmt(copied) + ' of ' + fmt(total) + ' records copied across ' + entries.length + ' namespaces (' + pct + '%)' + suffix; | |
| 1081 | + } else { | |
| 1082 | + label = fmt(copied) + ' of ' + fmt(total) + ' records copied (' + pct + '%)' + suffix; | |
| 1083 | + } | |
| 952 | 1084 | $progress.show().find('.mxch-progress-bar-fill').css('width', pct + '%'); |
| 953 | - $progress.find('.mxch-progress-label').text(copied + ' of ' + total + ' records copied (' + pct + '%)' + suffix); | |
| 954 | - $('#mxchat-pinecone-docs-migrate-counts').text(st.source_name ? ('Source: ' + st.source_name + ' (' + total + ' records)') : ''); | |
| 955 | - $('#mxchat-pinecone-docs-delete-old').toggle(st.status === 'done' && total > 0 && copied >= total); | |
| 1085 | + $progress.find('.mxch-progress-label').text(label); | |
| 1086 | + var counts; | |
| 1087 | + if (multi) { | |
| 1088 | + var lines = $.map(entries, function (e) { return namespaceLine(e, ' ('); }); | |
| 1089 | + counts = (st.source_name ? ('Source: ' + st.source_name + ' (' + fmt(total) + ' records in ' + entries.length + ' namespaces). ') : '') + lines.join(' · ') + '.'; | |
| 1090 | + } else { | |
| 1091 | + counts = st.source_name ? ('Source: ' + st.source_name + ' (' + fmt(total) + ' records' + (st.namespace ? (' in namespace ' + st.namespace) : '') + ')') : ''; | |
| 1092 | + if (others) { | |
| 1093 | + counts += (counts ? '. ' : '') + 'Not copied — ' + fmt(others.records) + ' records in ' + others.count + ' other namespace' + (others.count === 1 ? '' : 's') + ': ' + otherNamespacesText(st) + '. Tick Copy every namespace and click Migrate to include them.'; | |
| 1094 | + } | |
| 1095 | + } | |
| 1096 | + $('#mxchat-pinecone-docs-migrate-counts').text(counts); | |
| 1097 | + // The toggle appears once the source is known to have other namespaces, and stays on for a copy of every namespace. | |
| 1098 | + $allNsRow.toggle(multi || !!others); | |
| 1099 | + if (st.all_namespaces) { $allNs.prop('checked', true); } | |
| 1100 | + $allNs.prop('disabled', $migrateBtn.prop('disabled')); | |
| 1101 | + // Delete old index is never offered while the copy is known to leave namespaces behind (the server refuses too). | |
| 1102 | + $('#mxchat-pinecone-docs-delete-old').toggle(st.status === 'done' && total > 0 && copied >= total && liveType === 'document' && !others); | |
| 1103 | + refreshGate(); | |
| 956 | 1104 | } |
| 1105 | + function migrateFinished(st) { | |
| 1106 | + $migrateBtn.prop('disabled', false).text('Migrate'); | |
| 1107 | + $allNs.prop('disabled', false); | |
| 1108 | + var total = parseInt(st.total, 10) || 0, copied = parseInt(st.copied, 10) || 0; | |
| 1109 | + var reported = st.target_count ? (' The document index reports ' + fmt(st.target_count) + ' records.') : ''; | |
| 1110 | + var stillVector = liveType === 'document' ? '' : ' Your chatbot is still on the vector index — save the Pinecone settings to switch it over.'; | |
| 1111 | + var entries = namespaceEntries(st); | |
| 1112 | + var others = otherNamespacesSummary(st); | |
| 1113 | + if (isMulti(st)) { | |
| 1114 | + var lines = $.map(entries, function (e) { return namespaceLine(e, ' ('); }); | |
| 1115 | + if (copied >= total) { | |
| 1116 | + notice('success', 'Copy finished: ' + fmt(copied) + ' of ' + fmt(total) + ' records across ' + entries.length + ' namespaces are in the document index — ' + lines.join(', ') + '.' + stillVector); | |
| 1117 | + } else { | |
| 1118 | + var short = $.map(entries, function (e) { return (parseInt(e.copied, 10) || 0) < (parseInt(e.total, 10) || 0) ? namespaceLine(e, ' (') : null; }); | |
| 1119 | + notice('warning', 'The copy reached the end of every namespace after ' + fmt(copied) + ' records, but ' + fmt(total) + ' were expected. Short: ' + short.join(', ') + '.' + reported + | |
| 1120 | + (parseInt(st.skipped, 10) ? (' ' + fmt(st.skipped) + ' records had no vector and were skipped.') : '')); | |
| 1121 | + } | |
| 1122 | + return; | |
| 1123 | + } | |
| 1124 | + if (copied >= total) { | |
| 1125 | + notice(others ? 'warning' : 'success', 'Copy finished: ' + fmt(copied) + ' of ' + fmt(total) + ' records are in the document index.' + reported + | |
| 1126 | + (others ? (' ' + fmt(others.records) + ' records in ' + others.count + ' other namespace' + (others.count === 1 ? ' were' : 's were') + ' not copied: ' + otherNamespacesText(st) + '. Tick Copy every namespace and click Migrate to include them.') : '') + stillVector); | |
| 1127 | + } else { | |
| 1128 | + notice('warning', 'The copy reached the end of the source namespace after ' + fmt(copied) + ' records, but ' + fmt(total) + ' were expected.' + reported + | |
| 1129 | + (others ? (' Records in other namespaces were not copied: ' + otherNamespacesText(st) + '.') : '') + | |
| 1130 | + (parseInt(st.skipped, 10) ? (' ' + fmt(st.skipped) + ' records had no vector and were skipped.') : '')); | |
| 1131 | + } | |
| 1132 | + } | |
| 957 | 1133 | function migrateStep() { |
| 958 | - post({ action: 'mxchat_pinecone_docs_migrate', mode: 'step' }).done(function (response) { | |
| 1134 | + post({ action: 'mxchat_pinecone_docs_migrate', mode: 'step', target_host: hostValue(), api_key: $('#mxchat_pinecone_api_key').val() }).done(function (response) { | |
| 1135 | + stepRetries = 0; | |
| 959 | 1136 | if (response && response.success) { |
| 960 | 1137 | renderMigration(response.data.state); |
| 961 | 1138 | if (response.data.state && response.data.state.status === 'running') { |
| 962 | - setTimeout(migrateStep, 150); | |
| 1139 | + setTimeout(migrateStep, 250); | |
| 963 | 1140 | } else { |
| 964 | - $migrateBtn.prop('disabled', false).text('Migrate'); | |
| 965 | - notice('success', 'Copy finished: ' + response.data.state.copied + ' of ' + response.data.state.total + ' records are in the document index.'); | |
| 1141 | + migrateFinished(response.data.state || {}); | |
| 966 | 1142 | } |
| 967 | 1143 | } else { |
| 968 | - if (response && response.data && response.data.state) { renderMigration(response.data.state); } | |
| 969 | - notice('error', failMessage(response, 'The copy stopped.') + ' Click Migrate to resume.'); | |
| 1144 | + var st = (response && response.data && response.data.state) ? response.data.state : null; | |
| 1145 | + if (st) { renderMigration(st); } | |
| 1146 | + notice('error', failMessage(response, 'The copy stopped.') + (st ? (' Click Migrate to continue from record ' + fmt(st.copied) + '.') : ' Click Migrate to resume.')); | |
| 970 | 1147 | $migrateBtn.prop('disabled', false).text('Migrate'); |
| 1148 | + $allNs.prop('disabled', false); | |
| 971 | 1149 | } |
| 972 | - }).fail(function () { | |
| 973 | - notice('error', 'Could not reach the server. Click Migrate to resume.'); | |
| 1150 | + }).fail(function (xhr) { | |
| 1151 | + // The server did not answer (timeout, 5xx, rate limit): wait and try the same step again. | |
| 1152 | + if (stepRetries < STEP_RETRY_DELAYS.length) { | |
| 1153 | + var delay = STEP_RETRY_DELAYS[stepRetries++]; | |
| 1154 | + notice('info', 'No answer from the server (HTTP ' + (xhr && xhr.status ? xhr.status : 0) + '). Retrying in ' + Math.round(delay / 1000) + ' s… the copy continues from where it stopped.'); | |
| 1155 | + setTimeout(migrateStep, delay); | |
| 1156 | + return; | |
| 1157 | + } | |
| 1158 | + stepRetries = 0; | |
| 1159 | + notice('error', 'The server stopped answering (HTTP ' + (xhr && xhr.status ? xhr.status : 0) + ') after several tries. Nothing is lost — click Migrate to continue from the last record copied.'); | |
| 974 | 1160 | $migrateBtn.prop('disabled', false).text('Migrate'); |
| 1161 | + $allNs.prop('disabled', false); | |
| 975 | 1162 | }); |
| 976 | 1163 | } |
| 977 | 1164 | $migrateBtn.off('click.pineconeDocs').on('click.pineconeDocs', function () { |
| 978 | 1165 | var source = $.trim($('#mxchat_pinecone_vector_host').val() || ''); |
| @@ -979,25 +1166,35 @@ | ||
| 979 | 1166 | if (!source) { |
| 980 | 1167 | notice('error', 'Enter the host of the vector index to copy from.'); |
| 981 | 1168 | return; |
| 982 | 1169 | } |
| 1170 | + if (!hostValue()) { | |
| 1171 | + notice('error', 'Enter the host of the document index to copy into, or create one with Create index for me.'); | |
| 1172 | + return; | |
| 1173 | + } | |
| 983 | 1174 | $migrateBtn.prop('disabled', true).text('Copying…'); |
| 1175 | + $allNs.prop('disabled', true); | |
| 984 | 1176 | $result.hide(); |
| 985 | - post({ action: 'mxchat_pinecone_docs_migrate', mode: 'start', source_host: source }).done(function (response) { | |
| 1177 | + stepRetries = 0; | |
| 1178 | + post({ action: 'mxchat_pinecone_docs_migrate', mode: 'start', source_host: source, target_host: hostValue(), api_key: $('#mxchat_pinecone_api_key').val(), all_namespaces: $allNs.is(':checked') ? 1 : 0 }).done(function (response) { | |
| 986 | 1179 | if (response && response.success) { |
| 987 | - renderMigration(response.data.state); | |
| 988 | - if (response.data.state && response.data.state.status === 'running') { | |
| 1180 | + var st = response.data.state || {}; | |
| 1181 | + if (st.target_host && !verifiedHost) { verifiedHost = String(st.target_host).toLowerCase(); setStatus(true, 'Document index checked: ' + st.target_host + '.'); } | |
| 1182 | + renderMigration(st); | |
| 1183 | + if (st.status === 'running') { | |
| 989 | 1184 | migrateStep(); |
| 990 | 1185 | } else { |
| 991 | - $migrateBtn.prop('disabled', false).text('Migrate'); | |
| 1186 | + migrateFinished(st); | |
| 992 | 1187 | } |
| 993 | 1188 | } else { |
| 994 | 1189 | notice('error', failMessage(response, 'The copy could not start.')); |
| 995 | 1190 | $migrateBtn.prop('disabled', false).text('Migrate'); |
| 1191 | + $allNs.prop('disabled', false); | |
| 996 | 1192 | } |
| 997 | 1193 | }).fail(function () { |
| 998 | 1194 | notice('error', 'Could not reach the server to start the copy.'); |
| 999 | 1195 | $migrateBtn.prop('disabled', false).text('Migrate'); |
| 1196 | + $allNs.prop('disabled', false); | |
| 1000 | 1197 | }); |
| 1001 | 1198 | }); |
| 1002 | 1199 | renderMigration(state.migration || null); |
| 1003 | 1200 | |