blob: 68ed9bc3cb3843ceb3553e5269be2be6daeb3494 [file] [log] [blame]
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
import base64 from 'base-64';
import FauxtonAPI from '../../core/api';
import {get, post} from '../../core/ajax';
import ActionTypes from './actiontypes';
import Helpers from './helpers';
import MainHelper from '../../helpers';
import Constants from './constants';
import {
supportNewApi,
createReplicationDoc,
fetchReplicateInfo,
fetchReplicationDocs,
decodeFullUrl,
deleteReplicatesApi,
createReplicatorDB
} from './api';
export const initReplicator = (routeLocalSource, localSource) => dispatch => {
if (routeLocalSource && routeLocalSource !== localSource) {
dispatch({
type: ActionTypes.INIT_REPLICATION,
options: {
localSource: routeLocalSource
}
});
}
};
export const getDatabasesList = () => dispatch => {
const url = MainHelper.getServerUrl("/_all_dbs");
get(url)
.then((databases) => {
dispatch({
type: ActionTypes.REPLICATION_DATABASES_LOADED,
options: {
databases
}
});
});
};
export const replicate = (params) => dispatch => {
const replicationDoc = createReplicationDoc(params);
const url = MainHelper.getServerUrl("/_replicator");
const promise = post(url, replicationDoc);
const source = Helpers.getDatabaseLabel(replicationDoc.source);
const target = Helpers.getDatabaseLabel(replicationDoc.target);
dispatch({
type: ActionTypes.REPLICATION_STARTING,
});
const handleError = (json) => {
FauxtonAPI.addNotification({
msg: json.reason,
type: 'error',
clear: true
});
};
// Return promise for testing
return promise
.then(json => {
if (!json.ok) {
throw json;
}
FauxtonAPI.addNotification({
msg: `Replication from <code>${decodeURIComponent(source)}</code> to <code>${decodeURIComponent(target)}</code> has been scheduled.`,
type: 'success',
escape: false,
clear: true
});
dispatch(getReplicationActivity());
FauxtonAPI.navigate('#/replication');
}).catch(json => {
if (json.error && json.error === "not_found") {
return createReplicatorDB().then(() => {
return replicate(params)(dispatch);
}).catch(handleError);
}
handleError(json);
});
};
export const updateFormField = (fieldName, value) => {
return {
type: ActionTypes.REPLICATION_UPDATE_FORM_FIELD,
options: {
fieldName: fieldName,
value: value
}
};
};
export const clearReplicationForm = () => {
return { type: ActionTypes.REPLICATION_CLEAR_FORM };
};
export const getReplicationActivity = () => dispatch => {
dispatch({
type: ActionTypes.REPLICATION_FETCHING_STATUS,
});
supportNewApi()
.then(supportNewApi => {
return fetchReplicationDocs(supportNewApi);
})
.then(docs => {
dispatch({
type: ActionTypes.REPLICATION_STATUS,
options: docs
});
});
};
export const getReplicateActivity = () => dispatch => {
supportNewApi()
.then(newApi => {
if (!newApi) {
return;
}
dispatch({
type: ActionTypes.REPLICATION_FETCHING_REPLICATE_STATUS,
});
fetchReplicateInfo()
.then(replicateInfo => {
dispatch({
type: ActionTypes.REPLICATION_REPLICATE_STATUS,
options: replicateInfo
});
});
});
};
export const filterDocs = (filter) => {
return {
type: ActionTypes.REPLICATION_FILTER_DOCS,
options: filter
};
};
export const filterReplicate = (filter) => {
return {
type: ActionTypes.REPLICATION_FILTER_REPLICATE,
options: filter
};
};
export const selectAllDocs = () => {
return {
type: ActionTypes.REPLICATION_TOGGLE_ALL_DOCS
};
};
export const selectDoc = (id) => {
return {
type: ActionTypes.REPLICATION_TOGGLE_DOC,
options: id
};
};
export const selectAllReplicates = () => {
return {
type: ActionTypes.REPLICATION_TOGGLE_ALL_REPLICATE
};
};
export const selectReplicate = (id) => {
return {
type: ActionTypes.REPLICATION_TOGGLE_REPLICATE,
options: id
};
};
export const clearSelectedDocs = () => {
return {
type: ActionTypes.REPLICATION_CLEAR_SELECTED_DOCS
};
};
export const clearSelectedReplicates = () => {
return {
type: ActionTypes.REPLICATION_CLEAR_SELECTED_REPLICATES
};
};
export const deleteDocs = (docs) => dispatch => {
const bulkDocs = docs.map(({raw: doc}) => {
doc._deleted = true;
return doc;
});
FauxtonAPI.addNotification({
msg: `Deleting doc${bulkDocs.length > 1 ? 's' : ''}.`,
type: 'success',
escape: false,
clear: true
});
const url = MainHelper.getServerUrl('/_replicator/_bulk_docs');
post(url, {docs: bulkDocs}, {raw: true})
.then(resp => {
if (!resp.ok) {
throw resp;
}
return resp;
})
.then(() => {
let msg = 'The selected documents have been deleted.';
if (docs.length === 1) {
msg = `Document <code>${docs[0]._id}</code> has been deleted`;
}
FauxtonAPI.addNotification({
msg: msg,
type: 'success',
escape: false,
clear: true
});
dispatch(clearSelectedDocs());
dispatch(getReplicationActivity());
})
.catch(resp => {
resp.json()
.then(error => {
FauxtonAPI.addNotification({
msg: error.reason,
type: 'error',
clear: true
});
});
});
};
export const deleteReplicates = (replicates) => dispatch => {
FauxtonAPI.addNotification({
msg: `Deleting _replicate${replicates.length > 1 ? 's' : ''}.`,
type: 'success',
escape: false,
clear: true
});
deleteReplicatesApi(replicates)
.then(() => {
let msg = 'The selected replications have been deleted.';
if (replicates.length === 1) {
msg = `Replication <code>${replicates[0]._id}</code> has been deleted`;
}
dispatch(clearSelectedReplicates());
dispatch(getReplicateActivity());
FauxtonAPI.addNotification({
msg: msg,
type: 'success',
escape: false,
clear: true
});
}, (xhr) => {
const errorMessage = JSON.parse(xhr.responseText);
FauxtonAPI.addNotification({
msg: errorMessage.reason,
type: 'error',
clear: true
});
});
};
const getAuthTypeAndCredentials = (repSourceOrTarget) => {
const authTypeAndCreds = {
type: Constants.REPLICATION_AUTH_METHOD.NO_AUTH,
creds: {}
};
if (repSourceOrTarget.headers && repSourceOrTarget.headers.Authorization) {
// Removes 'Basic ' prefix
const encodedCreds = repSourceOrTarget.headers.Authorization.substring(6);
try {
const decodedCreds = base64.decode(encodedCreds);
authTypeAndCreds.type = Constants.REPLICATION_AUTH_METHOD.BASIC;
authTypeAndCreds.creds = {
username: decodedCreds.split(':')[0],
password: decodedCreds.split(':')[1]
};
return authTypeAndCreds;
} catch (ex) {
throw new Error(`Error decoding Authorization header from replication document`);
}
}
// Tries to get creds using one of the custom auth methods
// The extension should provide:
// - 'getCredentials(obj)' method that extracts the credentials from obj which is the 'target'/'source' field of the replication doc.
// - 'typeValue' field with an arbitrary ID representing the auth type the extension supports.
const authExtensions = FauxtonAPI.getExtensions('Replication:Auth');
let credentials = undefined;
let customAuthType = undefined;
if (authExtensions) {
authExtensions.map(ext => {
if (!credentials && ext.getCredentials) {
credentials = ext.getCredentials(repSourceOrTarget);
customAuthType = ext.typeValue;
}
});
}
if (credentials) {
authTypeAndCreds.type = customAuthType;
authTypeAndCreds.creds = credentials;
}
return authTypeAndCreds;
};
const getTargetDatabasePartitioned = (createTargetParams) => {
if (createTargetParams && createTargetParams.partitioned === true) {
return true;
}
return false;
};
export const getReplicationStateFrom = (id) => dispatch => {
dispatch({
type: ActionTypes.REPLICATION_FETCHING_FORM_STATE
});
const url = MainHelper.getServerUrl(`/_replicator/${encodeURIComponent(id)}`);
get(url)
.then((doc) => {
const stateDoc = {
replicationDocName: doc._id,
replicationType: doc.continuous ? Constants.REPLICATION_TYPE.CONTINUOUS : Constants.REPLICATION_TYPE.ONE_TIME,
};
const sourceUrl = _.isObject(doc.source) ? doc.source.url : doc.source;
const targetUrl = _.isObject(doc.target) ? doc.target.url : doc.target;
if (sourceUrl.indexOf(window.location.hostname) > -1) {
const url = new URL(sourceUrl);
stateDoc.replicationSource = Constants.REPLICATION_SOURCE.LOCAL;
stateDoc.localSource = decodeURIComponent(url.pathname.slice(1));
} else {
stateDoc.replicationSource = Constants.REPLICATION_SOURCE.REMOTE;
stateDoc.remoteSource = decodeFullUrl(sourceUrl);
}
const sourceAuth = getAuthTypeAndCredentials(doc.source);
stateDoc.sourceAuthType = sourceAuth.type;
stateDoc.sourceAuth = sourceAuth.creds;
if (targetUrl.indexOf(window.location.hostname) > -1) {
const url = new URL(targetUrl);
stateDoc.replicationTarget = Constants.REPLICATION_TARGET.EXISTING_LOCAL_DATABASE;
stateDoc.localTarget = decodeURIComponent(url.pathname.slice(1));
} else {
stateDoc.replicationTarget = Constants.REPLICATION_TARGET.EXISTING_REMOTE_DATABASE;
stateDoc.remoteTarget = decodeFullUrl(targetUrl);
}
const targetAuth = getAuthTypeAndCredentials(doc.target);
stateDoc.targetAuthType = targetAuth.type;
stateDoc.targetAuth = targetAuth.creds;
stateDoc.targetDatabasePartitioned = getTargetDatabasePartitioned(doc.create_target_params);
dispatch({
type: ActionTypes.REPLICATION_SET_STATE_FROM_DOC,
options: stateDoc
});
})
.catch(error => {
FauxtonAPI.addNotification({
msg: error.reason || error.message,
type: 'error',
clear: true
});
});
};
export const showConflictModal = () => {
return {
type: ActionTypes.REPLICATION_SHOW_CONFLICT_MODAL
};
};
export const hideConflictModal = () => {
return {
type: ActionTypes.REPLICATION_HIDE_CONFLICT_MODAL
};
};
export const changeActivitySort = (sort) => {
return {
type: ActionTypes.REPLICATION_CHANGE_ACTIVITY_SORT,
options: sort
};
};
export const checkForNewApi = () => dispatch => {
supportNewApi().then(newApi => {
dispatch({
type: ActionTypes.REPLICATION_SUPPORT_NEW_API,
options: newApi
});
});
};